package command import ( "fmt" "io/ioutil" "os" "path/filepath" "reflect" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/backend/local" "github.com/hashicorp/terraform/internal/terraform" "github.com/mitchellh/cli" ) func TestMetaColorize(t *testing.T) { var m *Meta var args, args2 []string // Test basic, color m = new(Meta) m.Color = true args = []string{"foo", "bar"} args2 = []string{"foo", "bar"} args = m.process(args) if !reflect.DeepEqual(args, args2) { t.Fatalf("bad: %#v", args) } if m.Colorize().Disable { t.Fatal("should not be disabled") } // Test basic, no change m = new(Meta) args = []string{"foo", "bar"} args2 = []string{"foo", "bar"} args = m.process(args) if !reflect.DeepEqual(args, args2) { t.Fatalf("bad: %#v", args) } if !m.Colorize().Disable { t.Fatal("should be disabled") } // Test disable #1 m = new(Meta) m.Color = true args = []string{"foo", "-no-color", "bar"} args2 = []string{"foo", "bar"} args = m.process(args) if !reflect.DeepEqual(args, args2) { t.Fatalf("bad: %#v", args) } if !m.Colorize().Disable { t.Fatal("should be disabled") } // Test disable #2 // Verify multiple -no-color options are removed from args slice. // E.g. an additional -no-color arg could be added by TF_CLI_ARGS. m = new(Meta) m.Color = true args = []string{"foo", "-no-color", "bar", "-no-color"} args2 = []string{"foo", "bar"} args = m.process(args) if !reflect.DeepEqual(args, args2) { t.Fatalf("bad: %#v", args) } if !m.Colorize().Disable { t.Fatal("should be disabled") } } func TestMetaInputMode(t *testing.T) { test = false defer func() { test = true }() m := new(Meta) args := []string{} fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } if m.InputMode() != terraform.InputModeStd { t.Fatalf("bad: %#v", m.InputMode()) } } func TestMetaInputMode_envVar(t *testing.T) { test = false defer func() { test = true }() old := os.Getenv(InputModeEnvVar) defer os.Setenv(InputModeEnvVar, old) m := new(Meta) args := []string{} fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } off := terraform.InputMode(0) on := terraform.InputModeStd cases := []struct { EnvVar string Expected terraform.InputMode }{ {"false", off}, {"0", off}, {"true", on}, {"1", on}, } for _, tc := range cases { os.Setenv(InputModeEnvVar, tc.EnvVar) if m.InputMode() != tc.Expected { t.Fatalf("expected InputMode: %#v, got: %#v", tc.Expected, m.InputMode()) } } } func TestMetaInputMode_disable(t *testing.T) { test = false defer func() { test = true }() m := new(Meta) args := []string{"-input=false"} fs := m.extendedFlagSet("foo") if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } if m.InputMode() > 0 { t.Fatalf("bad: %#v", m.InputMode()) } } func TestMeta_initStatePaths(t *testing.T) { m := new(Meta) m.initStatePaths() if m.statePath != DefaultStateFilename { t.Fatalf("bad: %#v", m) } if m.stateOutPath != DefaultStateFilename { t.Fatalf("bad: %#v", m) } if m.backupPath != DefaultStateFilename+DefaultBackupExtension { t.Fatalf("bad: %#v", m) } m = new(Meta) m.statePath = "foo" m.initStatePaths() if m.stateOutPath != "foo" { t.Fatalf("bad: %#v", m) } if m.backupPath != "foo"+DefaultBackupExtension { t.Fatalf("bad: %#v", m) } m = new(Meta) m.stateOutPath = "foo" m.initStatePaths() if m.statePath != DefaultStateFilename { t.Fatalf("bad: %#v", m) } if m.backupPath != "foo"+DefaultBackupExtension { t.Fatalf("bad: %#v", m) } } func TestMeta_Env(t *testing.T) { td := t.TempDir() os.MkdirAll(td, 0755) defer testChdir(t, td)() m := new(Meta) env, err := m.Workspace() if err != nil { t.Fatal(err) } if env != backend.DefaultStateName { t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) } testEnv := "test_env" if err := m.SetWorkspace(testEnv); err != nil { t.Fatal("error setting env:", err) } env, _ = m.Workspace() if env != testEnv { t.Fatalf("expected env %q, got env %q", testEnv, env) } if err := m.SetWorkspace(backend.DefaultStateName); err != nil { t.Fatal("error setting env:", err) } env, _ = m.Workspace() if env != backend.DefaultStateName { t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) } } func TestMeta_Workspace_override(t *testing.T) { defer func(value string) { os.Setenv(WorkspaceNameEnvVar, value) }(os.Getenv(WorkspaceNameEnvVar)) m := new(Meta) testCases := map[string]struct { workspace string err error }{ "": { "default", nil, }, "development": { "development", nil, }, "invalid name": { "", errInvalidWorkspaceNameEnvVar, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { os.Setenv(WorkspaceNameEnvVar, name) workspace, err := m.Workspace() if workspace != tc.workspace { t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", workspace, tc.workspace) } if err != tc.err { t.Errorf("Unexpected error\n got: %s\nwant: %s\n", err, tc.err) } }) } } func TestMeta_Workspace_invalidSelected(t *testing.T) { td := t.TempDir() os.MkdirAll(td, 0755) defer testChdir(t, td)() // this is an invalid workspace name workspace := "test workspace" // create the workspace directories if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, workspace), 0755); err != nil { t.Fatal(err) } // create the workspace file to select it if err := os.MkdirAll(DefaultDataDir, 0755); err != nil { t.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte(workspace), 0644); err != nil { t.Fatal(err) } m := new(Meta) ws, err := m.Workspace() if ws != workspace { t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", ws, workspace) } if err != nil { t.Errorf("Unexpected error: %s", err) } } func TestMeta_process(t *testing.T) { test = false defer func() { test = true }() // Create a temporary directory for our cwd d := t.TempDir() os.MkdirAll(d, 0755) defer testChdir(t, d)() // At one point it was the responsibility of this process function to // insert fake additional -var-file options into the command line // if the automatic tfvars files were present. This is no longer the // responsibility of process (it happens in collectVariableValues instead) // but we're still testing with these files in place to verify that // they _aren't_ being interpreted by process, since that could otherwise // cause them to be added more than once and mess up the precedence order. defaultVarsfile := "terraform.tfvars" err := ioutil.WriteFile( filepath.Join(d, defaultVarsfile), []byte(""), 0644) if err != nil { t.Fatalf("err: %s", err) } fileFirstAlphabetical := "a-file.auto.tfvars" err = ioutil.WriteFile( filepath.Join(d, fileFirstAlphabetical), []byte(""), 0644) if err != nil { t.Fatalf("err: %s", err) } fileLastAlphabetical := "z-file.auto.tfvars" err = ioutil.WriteFile( filepath.Join(d, fileLastAlphabetical), []byte(""), 0644) if err != nil { t.Fatalf("err: %s", err) } // Regular tfvars files will not be autoloaded fileIgnored := "ignored.tfvars" err = ioutil.WriteFile( filepath.Join(d, fileIgnored), []byte(""), 0644) if err != nil { t.Fatalf("err: %s", err) } tests := []struct { GivenArgs []string FilteredArgs []string ExtraCheck func(*testing.T, *Meta) }{ { []string{}, []string{}, func(t *testing.T, m *Meta) { if got, want := m.color, true; got != want { t.Errorf("wrong m.color value %#v; want %#v", got, want) } if got, want := m.Color, true; got != want { t.Errorf("wrong m.Color value %#v; want %#v", got, want) } }, }, { []string{"-no-color"}, []string{}, func(t *testing.T, m *Meta) { if got, want := m.color, false; got != want { t.Errorf("wrong m.color value %#v; want %#v", got, want) } if got, want := m.Color, false; got != want { t.Errorf("wrong m.Color value %#v; want %#v", got, want) } }, }, } for _, test := range tests { t.Run(fmt.Sprintf("%s", test.GivenArgs), func(t *testing.T) { m := new(Meta) m.Color = true // this is the default also for normal use, overridden by -no-color args := test.GivenArgs args = m.process(args) if !cmp.Equal(test.FilteredArgs, args) { t.Errorf("wrong filtered arguments\n%s", cmp.Diff(test.FilteredArgs, args)) } if test.ExtraCheck != nil { test.ExtraCheck(t, m) } }) } } func TestCommand_checkRequiredVersion(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() testCopyDir(t, testFixturePath("command-check-required-version"), td) defer testChdir(t, td)() ui := cli.NewMockUi() meta := Meta{ Ui: ui, } diags := meta.checkRequiredVersion() if diags == nil { t.Fatalf("diagnostics should contain unmet version constraint, but is nil") } meta.showDiagnostics(diags) // Required version diags are correct errStr := ui.ErrorWriter.String() if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) { t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) } if strings.Contains(errStr, `required_version = ">= 0.13.0"`) { t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr) } }