use the arguments package for state commands

modernize-state-commands-to-use-arguments-package
Daniel Schmidt 2 months ago
parent c1f6360120
commit ef4771bba4
No known key found for this signature in database
GPG Key ID: 377C3A4D62FBBBE2

@ -0,0 +1,49 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateList represents the command-line arguments for the state list command.
type StateList struct {
// StatePath is an optional path to a state file, overriding the default.
StatePath string
// ID filters the results to include only instances whose resource types
// have an attribute named "id" whose value equals this string.
ID string
// Addrs are optional resource or module addresses used to filter the
// listed instances.
Addrs []string
}
// ParseStateList processes CLI arguments, returning a StateList value and
// diagnostics. If errors are encountered, a StateList value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateList(args []string) (*StateList, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
list := &StateList{}
var statePath, id string
cmdFlags := defaultFlagSet("state list")
cmdFlags.StringVar(&statePath, "state", "", "path")
cmdFlags.StringVar(&id, "id", "", "Restrict output to paths with a resource having the specified ID.")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
list.StatePath = statePath
list.ID = id
list.Addrs = cmdFlags.Args()
return list, diags
}

@ -0,0 +1,118 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateList_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateList
}{
"defaults": {
nil,
&StateList{
StatePath: "",
ID: "",
Addrs: nil,
},
},
"state path": {
[]string{"-state=foobar.tfstate"},
&StateList{
StatePath: "foobar.tfstate",
ID: "",
Addrs: nil,
},
},
"id filter": {
[]string{"-id=bar"},
&StateList{
StatePath: "",
ID: "bar",
Addrs: nil,
},
},
"with addresses": {
[]string{"module.example", "aws_instance.foo"},
&StateList{
StatePath: "",
ID: "",
Addrs: []string{"module.example", "aws_instance.foo"},
},
},
"all options": {
[]string{"-state=foobar.tfstate", "-id=bar", "module.example"},
&StateList{
StatePath: "foobar.tfstate",
ID: "bar",
Addrs: []string{"module.example"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateList(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if got.StatePath != tc.want.StatePath {
t.Fatalf("unexpected StatePath\n got: %q\nwant: %q", got.StatePath, tc.want.StatePath)
}
if got.ID != tc.want.ID {
t.Fatalf("unexpected ID\n got: %q\nwant: %q", got.ID, tc.want.ID)
}
if len(got.Addrs) != len(tc.want.Addrs) {
t.Fatalf("unexpected Addrs length\n got: %d\nwant: %d", len(got.Addrs), len(tc.want.Addrs))
}
for i := range got.Addrs {
if got.Addrs[i] != tc.want.Addrs[i] {
t.Fatalf("unexpected Addrs[%d]\n got: %q\nwant: %q", i, got.Addrs[i], tc.want.Addrs[i])
}
}
})
}
}
func TestParseStateList_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateList
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-boop"},
&StateList{
StatePath: "",
ID: "",
Addrs: nil,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateList(tc.args)
if got.StatePath != tc.want.StatePath {
t.Fatalf("unexpected StatePath\n got: %q\nwant: %q", got.StatePath, tc.want.StatePath)
}
if got.ID != tc.want.ID {
t.Fatalf("unexpected ID\n got: %q\nwant: %q", got.ID, tc.want.ID)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -0,0 +1,93 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateMv represents the command-line arguments for the state mv command.
type StateMv struct {
// DryRun, if true, prints out what would be moved without actually
// moving anything.
DryRun bool
// BackupPath is the path where Terraform should write the backup state.
BackupPath string
// BackupOutPath is the path where Terraform should write the backup of
// the destination state.
BackupOutPath string
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is an optional path to a local state file.
StatePath string
// StateOutPath is an optional path to write the destination state.
StateOutPath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// SourceAddr is the source resource address.
SourceAddr string
// DestAddr is the destination resource address.
DestAddr string
}
// ParseStateMv processes CLI arguments, returning a StateMv value and
// diagnostics. If errors are encountered, a StateMv value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateMv(args []string) (*StateMv, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
mv := &StateMv{
StateLock: true,
}
cmdFlags := defaultFlagSet("state mv")
cmdFlags.BoolVar(&mv.DryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&mv.BackupPath, "backup", "-", "backup")
cmdFlags.StringVar(&mv.BackupOutPath, "backup-out", "-", "backup")
cmdFlags.BoolVar(&mv.StateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&mv.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&mv.StatePath, "state", "", "path")
cmdFlags.StringVar(&mv.StateOutPath, "state-out", "", "path")
cmdFlags.BoolVar(&mv.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 2 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
))
}
if len(args) > 0 {
mv.SourceAddr = args[0]
}
if len(args) > 1 {
mv.DestAddr = args[1]
}
return mv, diags
}

@ -0,0 +1,174 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateMv_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateMv
}{
"addresses only": {
[]string{"test_instance.foo", "test_instance.bar"},
&StateMv{
DryRun: false,
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
StateLockTimeout: 0,
StatePath: "",
StateOutPath: "",
IgnoreRemoteVersion: false,
SourceAddr: "test_instance.foo",
DestAddr: "test_instance.bar",
},
},
"dry run": {
[]string{"-dry-run", "test_instance.foo", "test_instance.bar"},
&StateMv{
DryRun: true,
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
StateLockTimeout: 0,
StatePath: "",
StateOutPath: "",
IgnoreRemoteVersion: false,
SourceAddr: "test_instance.foo",
DestAddr: "test_instance.bar",
},
},
"all options": {
[]string{
"-dry-run",
"-backup=backup.tfstate",
"-backup-out=backup-out.tfstate",
"-lock=false",
"-lock-timeout=5s",
"-state=state.tfstate",
"-state-out=state-out.tfstate",
"-ignore-remote-version",
"test_instance.foo",
"test_instance.bar",
},
&StateMv{
DryRun: true,
BackupPath: "backup.tfstate",
BackupOutPath: "backup-out.tfstate",
StateLock: false,
StateLockTimeout: 5 * time.Second,
StatePath: "state.tfstate",
StateOutPath: "state-out.tfstate",
IgnoreRemoteVersion: true,
SourceAddr: "test_instance.foo",
DestAddr: "test_instance.bar",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateMv(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStateMv_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateMv
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
"one argument": {
[]string{"test_instance.foo"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
SourceAddr: "test_instance.foo",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
"too many arguments": {
[]string{"a", "b", "c"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
SourceAddr: "a",
DestAddr: "b",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
"unknown flag": {
[]string{"-boop"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateMv(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -0,0 +1,32 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StatePull represents the command-line arguments for the state pull command.
type StatePull struct {
}
// ParseStatePull processes CLI arguments, returning a StatePull value and
// diagnostics. If errors are encountered, a StatePull value is still returned
// representing the best effort interpretation of the arguments.
func ParseStatePull(args []string) (*StatePull, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
pull := &StatePull{}
cmdFlags := defaultFlagSet("state pull")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
return pull, diags
}

@ -0,0 +1,64 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStatePull_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePull
}{
"defaults": {
nil,
&StatePull{},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStatePull(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStatePull_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePull
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-boop"},
&StatePull{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStatePull(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -0,0 +1,70 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StatePush represents the command-line arguments for the state push command.
type StatePush struct {
// Force writes the state even if lineages don't match or the remote
// serial is higher.
Force bool
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// Path is the path to the state file to push, or "-" for stdin.
Path string
}
// ParseStatePush processes CLI arguments, returning a StatePush value and
// diagnostics. If errors are encountered, a StatePush value is still returned
// representing the best effort interpretation of the arguments.
func ParseStatePush(args []string) (*StatePush, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
push := &StatePush{
StateLock: true,
}
cmdFlags := defaultFlagSet("state push")
cmdFlags.BoolVar(&push.Force, "force", false, "")
cmdFlags.BoolVar(&push.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&push.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&push.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
))
}
if len(args) > 0 {
push.Path = args[0]
}
return push, diags
}

@ -0,0 +1,155 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStatePush_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePush
}{
"path only": {
[]string{"replace.tfstate"},
&StatePush{
Force: false,
StateLock: true,
StateLockTimeout: 0,
IgnoreRemoteVersion: false,
Path: "replace.tfstate",
},
},
"stdin": {
[]string{"-"},
&StatePush{
Force: false,
StateLock: true,
StateLockTimeout: 0,
IgnoreRemoteVersion: false,
Path: "-",
},
},
"force": {
[]string{"-force", "replace.tfstate"},
&StatePush{
Force: true,
StateLock: true,
StateLockTimeout: 0,
IgnoreRemoteVersion: false,
Path: "replace.tfstate",
},
},
"lock disabled": {
[]string{"-lock=false", "replace.tfstate"},
&StatePush{
Force: false,
StateLock: false,
StateLockTimeout: 0,
IgnoreRemoteVersion: false,
Path: "replace.tfstate",
},
},
"lock timeout": {
[]string{"-lock-timeout=5s", "replace.tfstate"},
&StatePush{
Force: false,
StateLock: true,
StateLockTimeout: 5 * time.Second,
IgnoreRemoteVersion: false,
Path: "replace.tfstate",
},
},
"ignore remote version": {
[]string{"-ignore-remote-version", "replace.tfstate"},
&StatePush{
Force: false,
StateLock: true,
StateLockTimeout: 0,
IgnoreRemoteVersion: true,
Path: "replace.tfstate",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStatePush(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStatePush_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePush
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StatePush{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
),
},
},
"too many arguments": {
[]string{"foo.tfstate", "bar.tfstate"},
&StatePush{
StateLock: true,
Path: "foo.tfstate",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
),
},
},
"unknown flag": {
[]string{"-boop"},
&StatePush{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStatePush(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -0,0 +1,85 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateReplaceProvider represents the command-line arguments for the state
// replace-provider command.
type StateReplaceProvider struct {
// AutoApprove, if true, skips the interactive approval step.
AutoApprove bool
// BackupPath is the path where Terraform should write the backup state.
BackupPath string
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is an optional path to a local state file.
StatePath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// FromProviderAddr is the provider address to replace.
FromProviderAddr string
// ToProviderAddr is the replacement provider address.
ToProviderAddr string
}
// ParseStateReplaceProvider processes CLI arguments, returning a
// StateReplaceProvider value and diagnostics. If errors are encountered, a
// StateReplaceProvider value is still returned representing the best effort
// interpretation of the arguments.
func ParseStateReplaceProvider(args []string) (*StateReplaceProvider, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rp := &StateReplaceProvider{
StateLock: true,
}
cmdFlags := defaultFlagSet("state replace-provider")
cmdFlags.BoolVar(&rp.AutoApprove, "auto-approve", false, "skip interactive approval of replacements")
cmdFlags.StringVar(&rp.BackupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&rp.StateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&rp.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&rp.StatePath, "state", "", "path")
cmdFlags.BoolVar(&rp.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 2 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the from and to provider addresses.",
))
}
if len(args) > 0 {
rp.FromProviderAddr = args[0]
}
if len(args) > 1 {
rp.ToProviderAddr = args[1]
}
return rp, diags
}

@ -0,0 +1,144 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateReplaceProvider_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateReplaceProvider
}{
"provider addresses only": {
[]string{"hashicorp/aws", "acmecorp/aws"},
&StateReplaceProvider{
AutoApprove: false,
BackupPath: "-",
StateLock: true,
StateLockTimeout: 0,
StatePath: "",
IgnoreRemoteVersion: false,
FromProviderAddr: "hashicorp/aws",
ToProviderAddr: "acmecorp/aws",
},
},
"auto approve": {
[]string{"-auto-approve", "hashicorp/aws", "acmecorp/aws"},
&StateReplaceProvider{
AutoApprove: true,
BackupPath: "-",
StateLock: true,
StateLockTimeout: 0,
StatePath: "",
IgnoreRemoteVersion: false,
FromProviderAddr: "hashicorp/aws",
ToProviderAddr: "acmecorp/aws",
},
},
"all options": {
[]string{
"-auto-approve",
"-backup=backup.tfstate",
"-lock=false",
"-lock-timeout=5s",
"-state=state.tfstate",
"-ignore-remote-version",
"hashicorp/aws",
"acmecorp/aws",
},
&StateReplaceProvider{
AutoApprove: true,
BackupPath: "backup.tfstate",
StateLock: false,
StateLockTimeout: 5 * time.Second,
StatePath: "state.tfstate",
IgnoreRemoteVersion: true,
FromProviderAddr: "hashicorp/aws",
ToProviderAddr: "acmecorp/aws",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateReplaceProvider(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStateReplaceProvider_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateReplaceProvider
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the from and to provider addresses.",
),
},
},
"too many arguments": {
[]string{"a", "b", "c", "d"},
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
FromProviderAddr: "a",
ToProviderAddr: "b",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the from and to provider addresses.",
),
},
},
"unknown flag": {
[]string{"-invalid", "hashicorp/google", "acmecorp/google"},
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
FromProviderAddr: "hashicorp/google",
ToProviderAddr: "acmecorp/google",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -invalid",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateReplaceProvider(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -0,0 +1,76 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateRm represents the command-line arguments for the state rm command.
type StateRm struct {
// DryRun, if true, prints out what would be removed without actually
// removing anything.
DryRun bool
// BackupPath is the path where Terraform should write the backup state.
BackupPath string
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is an optional path to a local state file.
StatePath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// Addrs are the resource instance addresses to remove.
Addrs []string
}
// ParseStateRm processes CLI arguments, returning a StateRm value and
// diagnostics. If errors are encountered, a StateRm value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateRm(args []string) (*StateRm, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rm := &StateRm{
StateLock: true,
}
cmdFlags := defaultFlagSet("state rm")
cmdFlags.BoolVar(&rm.DryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&rm.BackupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&rm.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&rm.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&rm.StatePath, "state", "", "path")
cmdFlags.BoolVar(&rm.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) < 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"At least one address is required.",
))
}
rm.Addrs = args
return rm, diags
}

@ -0,0 +1,126 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateRm_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateRm
}{
"single address": {
[]string{"test_instance.foo"},
&StateRm{
DryRun: false,
BackupPath: "-",
StateLock: true,
StateLockTimeout: 0,
StatePath: "",
IgnoreRemoteVersion: false,
Addrs: []string{"test_instance.foo"},
},
},
"multiple addresses": {
[]string{"test_instance.foo", "test_instance.bar"},
&StateRm{
DryRun: false,
BackupPath: "-",
StateLock: true,
StateLockTimeout: 0,
StatePath: "",
IgnoreRemoteVersion: false,
Addrs: []string{"test_instance.foo", "test_instance.bar"},
},
},
"all options": {
[]string{"-dry-run", "-backup=backup.tfstate", "-lock=false", "-lock-timeout=5s", "-state=state.tfstate", "-ignore-remote-version", "test_instance.foo"},
&StateRm{
DryRun: true,
BackupPath: "backup.tfstate",
StateLock: false,
StateLockTimeout: 5 * time.Second,
StatePath: "state.tfstate",
IgnoreRemoteVersion: true,
Addrs: []string{"test_instance.foo"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateRm(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if got.DryRun != tc.want.DryRun ||
got.BackupPath != tc.want.BackupPath ||
got.StateLock != tc.want.StateLock ||
got.StateLockTimeout != tc.want.StateLockTimeout ||
got.StatePath != tc.want.StatePath ||
got.IgnoreRemoteVersion != tc.want.IgnoreRemoteVersion {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
if len(got.Addrs) != len(tc.want.Addrs) {
t.Fatalf("unexpected Addrs length\n got: %d\nwant: %d", len(got.Addrs), len(tc.want.Addrs))
}
for i := range got.Addrs {
if got.Addrs[i] != tc.want.Addrs[i] {
t.Fatalf("unexpected Addrs[%d]\n got: %q\nwant: %q", i, got.Addrs[i], tc.want.Addrs[i])
}
}
})
}
}
func TestParseStateRm_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
wantAddrs int
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
0,
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"At least one address is required.",
),
},
},
"unknown flag": {
[]string{"-boop"},
0,
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"At least one address is required.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateRm(tc.args)
if len(got.Addrs) != tc.wantAddrs {
t.Fatalf("unexpected Addrs length\n got: %d\nwant: %d", len(got.Addrs), tc.wantAddrs)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -0,0 +1,54 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateShow represents the command-line arguments for the state show command.
type StateShow struct {
// StatePath is an optional path to a state file, overriding the default.
StatePath string
// Address is the resource instance address to show.
Address string
}
// ParseStateShow processes CLI arguments, returning a StateShow value and
// diagnostics. If errors are encountered, a StateShow value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateShow(args []string) (*StateShow, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
show := &StateShow{}
var statePath string
cmdFlags := defaultFlagSet("state show")
cmdFlags.StringVar(&statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
))
}
show.StatePath = statePath
if len(args) > 0 {
show.Address = args[0]
}
return show, diags
}

@ -0,0 +1,110 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateShow_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateShow
}{
"address only": {
[]string{"test_instance.foo"},
&StateShow{
StatePath: "",
Address: "test_instance.foo",
},
},
"with state path": {
[]string{"-state=foobar.tfstate", "test_instance.foo"},
&StateShow{
StatePath: "foobar.tfstate",
Address: "test_instance.foo",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateShow(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStateShow_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateShow
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StateShow{
StatePath: "",
Address: "",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
),
},
},
"too many arguments": {
[]string{"test_instance.foo", "test_instance.bar"},
&StateShow{
StatePath: "",
Address: "test_instance.foo",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
),
},
},
"unknown flag": {
[]string{"-boop"},
&StateShow{
StatePath: "",
Address: "",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateShow(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

@ -7,7 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/states"
@ -21,19 +20,14 @@ type StateListCommand struct {
}
func (c *StateListCommand) Run(args []string) int {
args = c.Meta.process(args)
var statePath string
cmdFlags := c.Meta.defaultFlagSet("state list")
cmdFlags.StringVar(&statePath, "state", "", "path")
lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return cli.RunResultHelp
parsedArgs, diags := arguments.ParseStateList(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if statePath != "" {
c.Meta.statePath = statePath
if parsedArgs.StatePath != "" {
c.Meta.statePath = parsedArgs.StatePath
}
// Load the backend
@ -69,10 +63,10 @@ func (c *StateListCommand) Run(args []string) int {
}
var addrs []addrs.AbsResourceInstance
if len(args) == 0 {
if len(parsedArgs.Addrs) == 0 {
addrs, diags = c.lookupAllResourceInstanceAddrs(state)
} else {
addrs, diags = c.lookupResourceInstanceAddrs(state, args...)
addrs, diags = c.lookupResourceInstanceAddrs(state, parsedArgs.Addrs...)
}
if diags.HasErrors() {
c.showDiagnostics(diags)
@ -81,7 +75,7 @@ func (c *StateListCommand) Run(args []string) int {
for _, addr := range addrs {
if is := state.ResourceInstance(addr); is != nil {
if *lookupId == "" || *lookupId == states.LegacyInstanceObjectID(is.Current) {
if parsedArgs.ID == "" || parsedArgs.ID == states.LegacyInstanceObjectID(is.Current) {
c.Ui.Output(addr.String())
}
}

@ -7,8 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/command/arguments"
@ -26,28 +24,17 @@ type StateMvCommand struct {
}
func (c *StateMvCommand) Run(args []string) int {
args = c.Meta.process(args)
// We create two metas to track the two states
var backupPathOut, statePathOut string
var dryRun bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state mv")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
cmdFlags.StringVar(&statePathOut, "state-out", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, parseDiags := arguments.ParseStateMv(c.Meta.process(args))
if parseDiags.HasErrors() {
c.showDiagnostics(parseDiags)
return 1
}
args = cmdFlags.Args()
if len(args) != 2 {
c.Ui.Error("Exactly two arguments expected.\n")
return cli.RunResultHelp
}
c.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.statePath = parsedArgs.StatePath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -58,7 +45,7 @@ func (c *StateMvCommand) Run(args []string) int {
// and the state option is not set, make sure
// the backend is local
backupOptionSetWithoutStateOption := c.backupPath != "-" && c.statePath == ""
backupOutOptionSetWithoutStateOption := backupPathOut != "-" && c.statePath == ""
backupOutOptionSetWithoutStateOption := parsedArgs.BackupOutPath != "-" && c.statePath == ""
var setLegacyLocalBackendOptions []string
if backupOptionSetWithoutStateOption {
@ -127,9 +114,9 @@ func (c *StateMvCommand) Run(args []string) int {
stateToMgr := stateFromMgr
stateTo := stateFrom
if statePathOut != "" {
c.statePath = statePathOut
c.backupPath = backupPathOut
if parsedArgs.StateOutPath != "" {
c.statePath = parsedArgs.StateOutPath
c.backupPath = parsedArgs.BackupOutPath
stateToMgr, err = c.State(view)
if err != nil {
@ -162,9 +149,9 @@ func (c *StateMvCommand) Run(args []string) int {
}
var diags tfdiags.Diagnostics
sourceAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, args[0])
sourceAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, parsedArgs.SourceAddr)
diags = diags.Append(moreDiags)
destAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, args[1])
destAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, parsedArgs.DestAddr)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
@ -172,7 +159,7 @@ func (c *StateMvCommand) Run(args []string) int {
}
prefix := "Move"
if dryRun {
if parsedArgs.DryRun {
prefix = "Would move"
}
@ -231,7 +218,7 @@ func (c *StateMvCommand) Run(args []string) int {
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String()))
if !dryRun {
if !parsedArgs.DryRun {
ssFrom.RemoveModule(addrFrom)
// Update the address before adding it to the state.
@ -276,7 +263,7 @@ func (c *StateMvCommand) Run(args []string) int {
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String()))
if !dryRun {
if !parsedArgs.DryRun {
ssFrom.RemoveResource(addrFrom)
// Update the address before adding it to the state.
@ -329,8 +316,8 @@ func (c *StateMvCommand) Run(args []string) int {
}
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1]))
if !dryRun {
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), parsedArgs.DestAddr))
if !parsedArgs.DryRun {
fromResourceAddr := addrFrom.ContainingResource()
fromResource := ssFrom.Resource(fromResourceAddr)
fromProviderAddr := fromResource.ProviderConfig
@ -385,7 +372,7 @@ func (c *StateMvCommand) Run(args []string) int {
}
}
if dryRun {
if parsedArgs.DryRun {
if moved == 0 {
c.Ui.Output("Would have moved nothing.")
}

@ -21,10 +21,9 @@ type StatePullCommand struct {
}
func (c *StatePullCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("state pull")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
_, diags := arguments.ParseStatePull(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

@ -9,7 +9,6 @@ import (
"os"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
"github.com/hashicorp/terraform/internal/command/views"
@ -26,22 +25,15 @@ type StatePushCommand struct {
}
func (c *StatePushCommand) Run(args []string) int {
args = c.Meta.process(args)
var flagForce bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state push")
cmdFlags.BoolVar(&flagForce, "force", false, "")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, diags := arguments.ParseStatePush(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("Exactly one argument expected.\n")
return cli.RunResultHelp
}
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -51,8 +43,8 @@ func (c *StatePushCommand) Run(args []string) int {
// Determine our reader for the input state. This is the filepath
// or stdin if "-" is given.
var r io.Reader = os.Stdin
if args[0] != "-" {
f, err := os.Open(args[0])
if parsedArgs.Path != "-" {
f, err := os.Open(parsedArgs.Path)
if err != nil {
c.Ui.Error(err.Error())
return 1
@ -71,7 +63,7 @@ func (c *StatePushCommand) Run(args []string) int {
c.Close()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", parsedArgs.Path, err))
return 1
}
@ -128,7 +120,7 @@ func (c *StatePushCommand) Run(args []string) int {
}
// Import it, forcing through the lineage/serial if requested and possible.
if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil {
if err := statemgr.Import(srcStateFile, stateMgr, parsedArgs.Force); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}

@ -7,7 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
@ -27,25 +26,18 @@ type StateReplaceProviderCommand struct {
}
func (c *StateReplaceProviderCommand) Run(args []string) int {
args = c.Meta.process(args)
var autoApprove bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state replace-provider")
cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of replacements")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return cli.RunResultHelp
}
args = cmdFlags.Args()
if len(args) != 2 {
c.Ui.Error("Exactly two arguments expected.\n")
return cli.RunResultHelp
parsedArgs, parseDiags := arguments.ParseStateReplaceProvider(c.Meta.process(args))
if parseDiags.HasErrors() {
c.showDiagnostics(parseDiags)
return 1
}
c.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.statePath = parsedArgs.StatePath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
@ -54,19 +46,19 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Parse from/to arguments into providers
from, fromDiags := addrs.ParseProviderSourceString(args[0])
from, fromDiags := addrs.ParseProviderSourceString(parsedArgs.FromProviderAddr)
if fromDiags.HasErrors() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
fmt.Sprintf(`Invalid "from" provider %q`, args[0]),
fmt.Sprintf(`Invalid "from" provider %q`, parsedArgs.FromProviderAddr),
fromDiags.Err().Error(),
))
}
to, toDiags := addrs.ParseProviderSourceString(args[1])
to, toDiags := addrs.ParseProviderSourceString(parsedArgs.ToProviderAddr)
if toDiags.HasErrors() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
fmt.Sprintf(`Invalid "to" provider %q`, args[1]),
fmt.Sprintf(`Invalid "to" provider %q`, parsedArgs.ToProviderAddr),
toDiags.Err().Error(),
))
}
@ -144,7 +136,7 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
}
// Confirm
if !autoApprove {
if !parsedArgs.AutoApprove {
c.Ui.Output(colorize.Color(
"\n[bold]Do you want to make these changes?[reset]\n" +
"Only 'yes' will be accepted to continue.\n",

@ -225,7 +225,7 @@ func TestStateReplaceProvider(t *testing.T) {
t.Fatalf("successful exit; want error")
}
if got, want := ui.ErrorWriter.String(), "Error parsing command-line flags"; !strings.Contains(got, want) {
if got, want := ui.ErrorWriter.String(), "Failed to parse command-line flags"; !strings.Contains(got, want) {
t.Fatalf("missing expected error message\nwant: %s\nfull output:\n%s", want, got)
}
})

@ -7,7 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
@ -23,24 +22,17 @@ type StateRmCommand struct {
}
func (c *StateRmCommand) Run(args []string) int {
args = c.Meta.process(args)
var dryRun bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state rm")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, diags := arguments.ParseStateRm(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if len(args) < 1 {
c.Ui.Error("At least one address is required.\n")
return cli.RunResultHelp
}
c.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.statePath = parsedArgs.StatePath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -82,19 +74,19 @@ func (c *StateRmCommand) Run(args []string) int {
// This command primarily works with resource instances, though it will
// also clean up any modules and resources left empty by actions it takes.
var addrs []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, addrStr := range args {
var rmDiags tfdiags.Diagnostics
for _, addrStr := range parsedArgs.Addrs {
moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, true, addrStr)
addrs = append(addrs, moreAddrs...)
diags = diags.Append(moreDiags)
rmDiags = rmDiags.Append(moreDiags)
}
if diags.HasErrors() {
c.showDiagnostics(diags)
if rmDiags.HasErrors() {
c.showDiagnostics(rmDiags)
return 1
}
prefix := "Removed "
if dryRun {
if parsedArgs.DryRun {
prefix = "Would remove "
}
@ -103,13 +95,13 @@ func (c *StateRmCommand) Run(args []string) int {
for _, addr := range addrs {
isCount++
c.Ui.Output(prefix + addr.String())
if !dryRun {
if !parsedArgs.DryRun {
ss.ForgetResourceInstanceAll(addr)
ss.RemoveResourceIfEmpty(addr.ContainingResource())
}
}
if dryRun {
if parsedArgs.DryRun {
if isCount == 0 {
c.Ui.Output("Would have removed nothing.")
}
@ -118,9 +110,9 @@ func (c *StateRmCommand) Run(args []string) int {
// Load the backend
b, backendDiags := c.backend(".", view)
diags = diags.Append(backendDiags)
rmDiags = rmDiags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
c.showDiagnostics(rmDiags)
return 1
}
@ -129,7 +121,7 @@ func (c *StateRmCommand) Run(args []string) int {
if isCloudMode(b) {
var schemaDiags tfdiags.Diagnostics
schemas, schemaDiags = c.MaybeGetSchemas(state, nil)
diags = diags.Append(schemaDiags)
rmDiags = rmDiags.Append(schemaDiags)
}
if err := stateMgr.WriteState(state); err != nil {
@ -141,17 +133,17 @@ func (c *StateRmCommand) Run(args []string) int {
return 1
}
if len(diags) > 0 && isCount != 0 {
c.showDiagnostics(diags)
if len(rmDiags) > 0 && isCount != 0 {
c.showDiagnostics(rmDiags)
}
if isCount == 0 {
diags = diags.Append(tfdiags.Sourceless(
rmDiags = rmDiags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid target address",
"No matching objects found. To view the available instances, use \"terraform state list\". Please modify the address to reference a specific instance.",
))
c.showDiagnostics(diags)
c.showDiagnostics(rmDiags)
return 1
}

@ -8,8 +8,6 @@ import (
"os"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/command/arguments"
@ -27,18 +25,13 @@ type StateShowCommand struct {
}
func (c *StateShowCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("state show")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Streams.Eprintf("Error parsing command-line flags: %s\n", err.Error())
parsedArgs, diags := arguments.ParseStateShow(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Streams.Eprint("Exactly one argument expected.\n")
return cli.RunResultHelp
}
c.Meta.statePath = parsedArgs.StatePath
// Check for user-supplied plugin path
var err error
@ -66,9 +59,9 @@ func (c *StateShowCommand) Run(args []string) int {
c.ignoreRemoteVersionConflict(b)
// Check if the address can be parsed
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(parsedArgs.Address)
if addrDiags.HasErrors() {
c.Streams.Eprintln(fmt.Sprintf(errParsingAddress, args[0]))
c.Streams.Eprintln(fmt.Sprintf(errParsingAddress, parsedArgs.Address))
return 1
}

Loading…
Cancel
Save