From cdbd4f17f2a5101c4dbacc8dae98314addab4642 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 13 Mar 2026 17:34:55 +0100 Subject: [PATCH] command: Add vars to taint command --- internal/command/arguments/taint.go | 9 +++- internal/command/arguments/taint_test.go | 68 +++++++++++++++++++++++- internal/command/taint.go | 26 +++++++++ 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/internal/command/arguments/taint.go b/internal/command/arguments/taint.go index 3be7a20d2e..bf7060af26 100644 --- a/internal/command/arguments/taint.go +++ b/internal/command/arguments/taint.go @@ -11,6 +11,9 @@ import ( // Taint represents the command-line arguments for the taint command. type Taint struct { + // Vars are the variable-related flags (-var, -var-file). + Vars *Vars + // Address is the address of the resource instance to taint. Address string @@ -44,9 +47,11 @@ type Taint struct { // the best effort interpretation of the arguments. func ParseTaint(args []string) (*Taint, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - taint := &Taint{} + taint := &Taint{ + Vars: &Vars{}, + } - cmdFlags := defaultFlagSet("taint") + cmdFlags := extendedFlagSet("taint", nil, nil, taint.Vars) cmdFlags.BoolVar(&taint.AllowMissing, "allow-missing", false, "allow missing") cmdFlags.StringVar(&taint.BackupPath, "backup", "", "path") cmdFlags.BoolVar(&taint.StateLock, "lock", true, "lock state") diff --git a/internal/command/arguments/taint_test.go b/internal/command/arguments/taint_test.go index 7a6a3c07cd..f79da4f6e2 100644 --- a/internal/command/arguments/taint_test.go +++ b/internal/command/arguments/taint_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 TestParseTaint_valid(t *testing.T) { "defaults with address": { []string{"test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", StateLock: true, }, @@ -25,6 +29,7 @@ func TestParseTaint_valid(t *testing.T) { "allow-missing": { []string{"-allow-missing", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", AllowMissing: true, StateLock: true, @@ -33,6 +38,7 @@ func TestParseTaint_valid(t *testing.T) { "backup": { []string{"-backup", "backup.tfstate", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", BackupPath: "backup.tfstate", StateLock: true, @@ -41,12 +47,14 @@ func TestParseTaint_valid(t *testing.T) { "lock disabled": { []string{"-lock=false", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", }, }, "lock-timeout": { []string{"-lock-timeout=10s", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", StateLock: true, StateLockTimeout: 10 * time.Second, @@ -55,6 +63,7 @@ func TestParseTaint_valid(t *testing.T) { "state": { []string{"-state=foo.tfstate", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", StateLock: true, StatePath: "foo.tfstate", @@ -63,6 +72,7 @@ func TestParseTaint_valid(t *testing.T) { "state-out": { []string{"-state-out=foo.tfstate", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", StateLock: true, StateOutPath: "foo.tfstate", @@ -71,6 +81,7 @@ func TestParseTaint_valid(t *testing.T) { "ignore-remote-version": { []string{"-ignore-remote-version", "test_instance.foo"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", StateLock: true, IgnoreRemoteVersion: true, @@ -88,6 +99,7 @@ func TestParseTaint_valid(t *testing.T) { "module.child.test_instance.foo", }, &Taint{ + Vars: &Vars{}, Address: "module.child.test_instance.foo", AllowMissing: true, BackupPath: "backup.tfstate", @@ -99,19 +111,66 @@ func TestParseTaint_valid(t *testing.T) { }, } + cmpOpts := cmpopts.IgnoreUnexported(Vars{}) + for name, tc := range testCases { t.Run(name, func(t *testing.T) { got, diags := ParseTaint(tc.args) if len(diags) > 0 { t.Fatalf("unexpected diags: %v", diags) } - if *got != *tc.want { + if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" { t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want) } }) } } +func TestParseTaint_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 := ParseTaint(tc.args) + if len(diags) > 0 { + t.Fatalf("unexpected diags: %v", diags) + } + if vars := got.Vars.All(); !cmp.Equal(vars, tc.want) { + t.Fatalf("unexpected vars: %#v", vars) + } + }) + } +} + func TestParseTaint_invalid(t *testing.T) { testCases := map[string]struct { args []string @@ -121,6 +180,7 @@ func TestParseTaint_invalid(t *testing.T) { "unknown flag": { []string{"-unknown"}, &Taint{ + Vars: &Vars{}, StateLock: true, }, tfdiags.Diagnostics{ @@ -139,6 +199,7 @@ func TestParseTaint_invalid(t *testing.T) { "missing address": { nil, &Taint{ + Vars: &Vars{}, StateLock: true, }, tfdiags.Diagnostics{ @@ -152,6 +213,7 @@ func TestParseTaint_invalid(t *testing.T) { "too many arguments": { []string{"test_instance.foo", "test_instance.bar"}, &Taint{ + Vars: &Vars{}, Address: "test_instance.foo", StateLock: true, }, @@ -165,10 +227,12 @@ func TestParseTaint_invalid(t *testing.T) { }, } + cmpOpts := cmpopts.IgnoreUnexported(Vars{}) + for name, tc := range testCases { t.Run(name, func(t *testing.T) { got, gotDiags := ParseTaint(tc.args) - if *got != *tc.want { + 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/taint.go b/internal/command/taint.go index ba9ef4c82d..9b50a0508f 100644 --- a/internal/command/taint.go +++ b/internal/command/taint.go @@ -37,6 +37,23 @@ func (c *TaintCommand) Run(rawArgs []string) int { c.Meta.stateOutPath = parsedArgs.StateOutPath c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion + loader, err := c.initConfigLoader() + if err != nil { + var diags tfdiags.Diagnostics + 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 + } + var diags tfdiags.Diagnostics addr, addrDiags := addrs.ParseAbsResourceInstanceStr(parsedArgs.Address) @@ -224,6 +241,15 @@ Options: -ignore-remote-version A rare option used for the remote backend only. See the remote backend documentation for more information. + -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. + -state, state-out, and -backup are legacy options supported for the local backend only. For more information, see the local backend's documentation.