diff --git a/internal/command/arguments/modules.go b/internal/command/arguments/modules.go index af76bf01c7..547dd71633 100644 --- a/internal/command/arguments/modules.go +++ b/internal/command/arguments/modules.go @@ -9,6 +9,9 @@ import "github.com/hashicorp/terraform/internal/tfdiags" type Modules struct { // ViewType specifies which output format to use: human, JSON, or "raw" ViewType ViewType + + // Vars are the variable-related flags (-var, -var-file). + Vars *Vars } // ParseModules processes CLI arguments, returning a Modules value and error @@ -18,8 +21,10 @@ func ParseModules(args []string) (*Modules, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var jsonOutput bool - modules := &Modules{} - cmdFlags := defaultFlagSet("modules") + modules := &Modules{ + Vars: &Vars{}, + } + cmdFlags := extendedFlagSet("modules", nil, nil, modules.Vars) cmdFlags.BoolVar(&jsonOutput, "json", false, "json") if err := cmdFlags.Parse(args); err != nil { diff --git a/internal/command/arguments/modules_test.go b/internal/command/arguments/modules_test.go index f4d41b6971..3a6ba941dc 100644 --- a/internal/command/arguments/modules_test.go +++ b/internal/command/arguments/modules_test.go @@ -6,6 +6,8 @@ package arguments import ( "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -18,24 +20,28 @@ func TestParseModules_valid(t *testing.T) { nil, &Modules{ ViewType: ViewHuman, + Vars: &Vars{}, }, }, "json": { []string{"-json"}, &Modules{ ViewType: ViewJSON, + Vars: &Vars{}, }, }, } + cmpOpts := cmpopts.IgnoreUnexported(Vars{}) + for name, tc := range testCases { t.Run(name, func(t *testing.T) { got, diags := ParseModules(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) + if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" { + t.Fatalf("unexpected result\n%s", diff) } }) } @@ -51,6 +57,7 @@ func TestParseModules_invalid(t *testing.T) { []string{"-sauron"}, &Modules{ ViewType: ViewHuman, + Vars: &Vars{}, }, tfdiags.Diagnostics{ tfdiags.Sourceless( @@ -64,6 +71,7 @@ func TestParseModules_invalid(t *testing.T) { []string{"-json", "frodo"}, &Modules{ ViewType: ViewJSON, + Vars: &Vars{}, }, tfdiags.Diagnostics{ tfdiags.Sourceless( @@ -75,13 +83,59 @@ func TestParseModules_invalid(t *testing.T) { }, } + cmpOpts := cmpopts.IgnoreUnexported(Vars{}) + for name, tc := range testCases { t.Run(name, func(t *testing.T) { got, gotDiags := ParseModules(tc.args) - if *got != *tc.want { - t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want) + if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" { + t.Fatalf("unexpected result\n%s", diff) } tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags) }) } } + +func TestParseModules_vars(t *testing.T) { + testCases := map[string]struct { + args []string + want []FlagNameValue + }{ + "var": { + args: []string{"-var", "foo=bar"}, + want: []FlagNameValue{ + {Name: "-var", Value: "foo=bar"}, + }, + }, + "var-file": { + args: []string{"-var-file", "cool.tfvars"}, + want: []FlagNameValue{ + {Name: "-var-file", Value: "cool.tfvars"}, + }, + }, + "both": { + args: []string{ + "-var", "foo=bar", + "-var-file", "cool.tfvars", + "-var", "boop=beep", + }, + 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 := ParseModules(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) + } + }) + } +} diff --git a/internal/command/modules.go b/internal/command/modules.go index cb2972dbc4..95d8acb7ad 100644 --- a/internal/command/modules.go +++ b/internal/command/modules.go @@ -48,6 +48,23 @@ func (c *ModulesCommand) Run(rawArgs []string) int { // Set up the command's view view := views.NewModules(c.viewType, c.View) + loader, err := c.initConfigLoader() + if err != nil { + diags = diags.Append(err) + view.Diagnostics(diags) + return 1 + } + + var varDiags tfdiags.Diagnostics + c.VariableValues, varDiags = args.Vars.CollectValues(func(filename string, src []byte) { + loader.Parser().ForceFileSource(filename, src) + }) + diags = diags.Append(varDiags) + if diags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + rootModPath, err := ModulePath([]string{}) if err != nil { diags = diags.Append(err) @@ -127,6 +144,15 @@ Usage: terraform [global options] modules [options] Options: - -json If specified, output declared Terraform modules and - their resolved versions in a machine-readable format. + -json If specified, output declared Terraform modules and + their resolved versions in a machine-readable format. + + -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. `