diff --git a/internal/command/arguments/state_rm.go b/internal/command/arguments/state_rm.go index 2421903afd..2271733786 100644 --- a/internal/command/arguments/state_rm.go +++ b/internal/command/arguments/state_rm.go @@ -11,6 +11,9 @@ import ( // StateRm represents the command-line arguments for the state rm command. type StateRm struct { + // Vars are the variable-related flags (-var, -var-file). + Vars *Vars + // DryRun, if true, prints out what would be removed without actually // removing anything. DryRun bool @@ -41,9 +44,11 @@ type StateRm struct { // representing the best effort interpretation of the arguments. func ParseStateRm(args []string) (*StateRm, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - rm := &StateRm{} + rm := &StateRm{ + Vars: &Vars{}, + } - cmdFlags := defaultFlagSet("state rm") + cmdFlags := extendedFlagSet("state rm", nil, nil, rm.Vars) cmdFlags.BoolVar(&rm.DryRun, "dry-run", false, "dry run") cmdFlags.StringVar(&rm.BackupPath, "backup", "-", "backup") cmdFlags.BoolVar(&rm.StateLock, "lock", true, "lock state") diff --git a/internal/command/arguments/state_rm_test.go b/internal/command/arguments/state_rm_test.go index c23887406d..6e11e6490c 100644 --- a/internal/command/arguments/state_rm_test.go +++ b/internal/command/arguments/state_rm_test.go @@ -7,6 +7,9 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -18,6 +21,7 @@ func TestParseStateRm_valid(t *testing.T) { "single address": { []string{"test_instance.foo"}, &StateRm{ + Vars: &Vars{}, BackupPath: "-", StateLock: true, Addrs: []string{"test_instance.foo"}, @@ -26,6 +30,7 @@ func TestParseStateRm_valid(t *testing.T) { "multiple addresses": { []string{"test_instance.foo", "test_instance.bar"}, &StateRm{ + Vars: &Vars{}, BackupPath: "-", StateLock: true, Addrs: []string{"test_instance.foo", "test_instance.bar"}, @@ -34,6 +39,7 @@ func TestParseStateRm_valid(t *testing.T) { "all options": { []string{"-dry-run", "-backup=backup.tfstate", "-lock=false", "-lock-timeout=5s", "-state=state.tfstate", "-ignore-remote-version", "test_instance.foo"}, &StateRm{ + Vars: &Vars{}, DryRun: true, BackupPath: "backup.tfstate", StateLock: false, @@ -45,27 +51,64 @@ func TestParseStateRm_valid(t *testing.T) { }, } + cmpOpts := cmp.Options{ + cmpopts.IgnoreUnexported(Vars{}), + cmpopts.EquateEmpty(), + } + 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 { + if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" { 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)) + }) + } +} + +func TestParseStateRm_vars(t *testing.T) { + testCases := map[string]struct { + args []string + want []FlagNameValue + }{ + "var": { + args: []string{"-var", "foo=bar", "test_instance.foo"}, + want: []FlagNameValue{ + {Name: "-var", Value: "foo=bar"}, + }, + }, + "var-file": { + args: []string{"-var-file", "cool.tfvars", "test_instance.foo"}, + want: []FlagNameValue{ + {Name: "-var-file", Value: "cool.tfvars"}, + }, + }, + "both": { + args: []string{ + "-var", "foo=bar", + "-var-file", "cool.tfvars", + "-var", "boop=beep", + "test_instance.foo", + }, + want: []FlagNameValue{ + {Name: "-var", Value: "foo=bar"}, + {Name: "-var-file", Value: "cool.tfvars"}, + {Name: "-var", Value: "boop=beep"}, + }, + }, + } + + 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) } - 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]) - } + if vars := got.Vars.All(); !cmp.Equal(vars, tc.want) { + t.Fatalf("unexpected vars: %#v", vars) } }) } @@ -74,12 +117,16 @@ func TestParseStateRm_valid(t *testing.T) { func TestParseStateRm_invalid(t *testing.T) { testCases := map[string]struct { args []string - wantAddrs int + want *StateRm wantDiags tfdiags.Diagnostics }{ "no arguments": { nil, - 0, + &StateRm{ + Vars: &Vars{}, + BackupPath: "-", + StateLock: true, + }, tfdiags.Diagnostics{ tfdiags.Sourceless( tfdiags.Error, @@ -90,7 +137,11 @@ func TestParseStateRm_invalid(t *testing.T) { }, "unknown flag": { []string{"-boop"}, - 0, + &StateRm{ + Vars: &Vars{}, + BackupPath: "-", + StateLock: true, + }, tfdiags.Diagnostics{ tfdiags.Sourceless( tfdiags.Error, @@ -106,11 +157,16 @@ func TestParseStateRm_invalid(t *testing.T) { }, } + cmpOpts := cmp.Options{ + cmpopts.IgnoreUnexported(Vars{}), + cmpopts.EquateEmpty(), + } + 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) + if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" { + t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want) } tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags) }) diff --git a/internal/command/state_rm.go b/internal/command/state_rm.go index 1c555497de..b45dff41af 100644 --- a/internal/command/state_rm.go +++ b/internal/command/state_rm.go @@ -34,6 +34,22 @@ func (c *StateRmCommand) Run(args []string) int { c.statePath = parsedArgs.StatePath c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion + loader, err := c.initConfigLoader() + if err != nil { + diags = diags.Append(err) + c.showDiagnostics(diags) + return 1 + } + + var varDiags tfdiags.Diagnostics + c.VariableValues, varDiags = parsedArgs.Vars.CollectValues(func(filename string, src []byte) { + loader.Parser().ForceFileSource(filename, src) + }) + if varDiags.HasErrors() { + c.showDiagnostics(varDiags) + return 1 + } + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 @@ -191,6 +207,15 @@ Options: are incompatible. This may result in an unusable workspace, and should be used with extreme caution. + -var 'foo=bar' Set a value for one of the input variables in the root + module of the configuration. Use this option more than + once to set more than one variable. + + -var-file=filename Load variable values from the given file, in addition + to the default files terraform.tfvars and *.auto.tfvars. + Use this option more than once to include more than one + variables file. + ` return strings.TrimSpace(helpText) }