From 0fa9e5b4da9b9f18cdcb93eca29775d8a5c0e649 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 13 Mar 2026 17:48:32 +0100 Subject: [PATCH] command: Add vars to get command (and refactor it) --- internal/command/arguments/get.go | 54 +++++++++ internal/command/arguments/get_test.go | 161 +++++++++++++++++++++++++ internal/command/get.go | 48 +++++--- 3 files changed, 249 insertions(+), 14 deletions(-) create mode 100644 internal/command/arguments/get.go create mode 100644 internal/command/arguments/get_test.go diff --git a/internal/command/arguments/get.go b/internal/command/arguments/get.go new file mode 100644 index 0000000000..2e1d125a08 --- /dev/null +++ b/internal/command/arguments/get.go @@ -0,0 +1,54 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package arguments + +import ( + "github.com/hashicorp/terraform/internal/tfdiags" +) + +// Get represents the command-line arguments for the get command. +type Get struct { + // Vars are the variable-related flags (-var, -var-file). + Vars *Vars + + // Update, if true, checks already-downloaded modules for available + // updates and installs the newest versions available. + Update bool + + // TestDirectory is the Terraform test directory. + TestDirectory string +} + +// ParseGet processes CLI arguments, returning a Get value and diagnostics. +// If errors are encountered, a Get value is still returned representing +// the best effort interpretation of the arguments. +func ParseGet(args []string) (*Get, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + get := &Get{ + Vars: &Vars{}, + } + + cmdFlags := extendedFlagSet("get", nil, nil, get.Vars) + cmdFlags.BoolVar(&get.Update, "update", false, "update") + cmdFlags.StringVar(&get.TestDirectory, "test-directory", "tests", "test-directory") + + 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) > 0 { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Too many command line arguments", + "Expected no positional arguments. Did you mean to use -chdir?", + )) + } + + return get, diags +} diff --git a/internal/command/arguments/get_test.go b/internal/command/arguments/get_test.go new file mode 100644 index 0000000000..3aaf5b381e --- /dev/null +++ b/internal/command/arguments/get_test.go @@ -0,0 +1,161 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package arguments + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/hashicorp/terraform/internal/tfdiags" +) + +func TestParseGet_valid(t *testing.T) { + testCases := map[string]struct { + args []string + want *Get + }{ + "defaults": { + nil, + &Get{ + Vars: &Vars{}, + TestDirectory: "tests", + }, + }, + "update": { + []string{"-update"}, + &Get{ + Vars: &Vars{}, + Update: true, + TestDirectory: "tests", + }, + }, + "test-directory": { + []string{"-test-directory", "custom-tests"}, + &Get{ + Vars: &Vars{}, + TestDirectory: "custom-tests", + }, + }, + "all options": { + []string{ + "-update", + "-test-directory", "custom-tests", + }, + &Get{ + Vars: &Vars{}, + Update: true, + TestDirectory: "custom-tests", + }, + }, + } + + cmpOpts := cmp.Options{cmpopts.IgnoreUnexported(Vars{})} + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got, diags := ParseGet(tc.args) + if len(diags) > 0 { + t.Fatalf("unexpected diags: %v", diags) + } + if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" { + t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want) + } + }) + } +} + +func TestParseGet_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 := ParseGet(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 TestParseGet_invalid(t *testing.T) { + testCases := map[string]struct { + args []string + want *Get + wantDiags tfdiags.Diagnostics + }{ + "unknown flag": { + []string{"-boop"}, + &Get{ + Vars: &Vars{}, + TestDirectory: "tests", + }, + tfdiags.Diagnostics{ + tfdiags.Sourceless( + tfdiags.Error, + "Failed to parse command-line flags", + "flag provided but not defined: -boop", + ), + }, + }, + "too many arguments": { + []string{"foo", "bar"}, + &Get{ + Vars: &Vars{}, + TestDirectory: "tests", + }, + tfdiags.Diagnostics{ + tfdiags.Sourceless( + tfdiags.Error, + "Too many command line arguments", + "Expected no positional arguments. Did you mean to use -chdir?", + ), + }, + }, + } + + cmpOpts := cmp.Options{cmpopts.IgnoreUnexported(Vars{})} + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got, gotDiags := ParseGet(tc.args) + 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/get.go b/internal/command/get.go index 7de3f96be6..98e1f7093a 100644 --- a/internal/command/get.go +++ b/internal/command/get.go @@ -5,9 +5,9 @@ package command import ( "context" - "fmt" "strings" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -18,16 +18,26 @@ type GetCommand struct { } func (c *GetCommand) Run(args []string) int { - var update bool - var testsDirectory string - - args = c.Meta.process(args) - cmdFlags := c.Meta.defaultFlagSet("get") - cmdFlags.BoolVar(&update, "update", false, "update") - cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory") - cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } - if err := cmdFlags.Parse(args); err != nil { - c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) + parsedArgs, diags := arguments.ParseGet(c.Meta.process(args)) + if diags.HasErrors() { + c.showDiagnostics(diags) + return 1 + } + + 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) + }) + diags = diags.Append(varDiags) + if diags.HasErrors() { + c.showDiagnostics(diags) return 1 } @@ -35,7 +45,7 @@ func (c *GetCommand) Run(args []string) int { ctx, done := c.InterruptibleContext(c.CommandContext()) defer done() - path, err := ModulePath(cmdFlags.Args()) + path, err := ModulePath(nil) if err != nil { c.Ui.Error(err.Error()) return 1 @@ -43,7 +53,8 @@ func (c *GetCommand) Run(args []string) int { path = c.normalizePath(path) - abort, diags := getModules(ctx, &c.Meta, path, testsDirectory, update) + abort, moreDiags := getModules(ctx, &c.Meta, path, parsedArgs.TestDirectory, parsedArgs.Update) + diags = diags.Append(moreDiags) c.showDiagnostics(diags) if abort || diags.HasErrors() { return 1 @@ -75,7 +86,16 @@ Options: -no-color Disable text coloring in the output. - -test-directory=path Set the Terraform test directory, defaults to "tests". + -test-directory=path Set the Terraform test directory, defaults to "tests". + + -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)