github.com/opentofu/opentofu@v1.7.1/internal/command/meta_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package command 7 8 import ( 9 "fmt" 10 "os" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "testing" 15 16 "github.com/google/go-cmp/cmp" 17 18 "github.com/mitchellh/cli" 19 "github.com/opentofu/opentofu/internal/backend" 20 "github.com/opentofu/opentofu/internal/backend/local" 21 "github.com/opentofu/opentofu/internal/tofu" 22 ) 23 24 func TestMetaColorize(t *testing.T) { 25 var m *Meta 26 var args, args2 []string 27 28 // Test basic, color 29 m = new(Meta) 30 m.Color = true 31 args = []string{"foo", "bar"} 32 args2 = []string{"foo", "bar"} 33 args = m.process(args) 34 if !reflect.DeepEqual(args, args2) { 35 t.Fatalf("bad: %#v", args) 36 } 37 if m.Colorize().Disable { 38 t.Fatal("should not be disabled") 39 } 40 41 // Test basic, no change 42 m = new(Meta) 43 args = []string{"foo", "bar"} 44 args2 = []string{"foo", "bar"} 45 args = m.process(args) 46 if !reflect.DeepEqual(args, args2) { 47 t.Fatalf("bad: %#v", args) 48 } 49 if !m.Colorize().Disable { 50 t.Fatal("should be disabled") 51 } 52 53 // Test disable #1 54 m = new(Meta) 55 m.Color = true 56 args = []string{"foo", "-no-color", "bar"} 57 args2 = []string{"foo", "bar"} 58 args = m.process(args) 59 if !reflect.DeepEqual(args, args2) { 60 t.Fatalf("bad: %#v", args) 61 } 62 if !m.Colorize().Disable { 63 t.Fatal("should be disabled") 64 } 65 66 // Test disable #2 67 // Verify multiple -no-color options are removed from args slice. 68 // E.g. an additional -no-color arg could be added by TF_CLI_ARGS. 69 m = new(Meta) 70 m.Color = true 71 args = []string{"foo", "-no-color", "bar", "-no-color"} 72 args2 = []string{"foo", "bar"} 73 args = m.process(args) 74 if !reflect.DeepEqual(args, args2) { 75 t.Fatalf("bad: %#v", args) 76 } 77 if !m.Colorize().Disable { 78 t.Fatal("should be disabled") 79 } 80 } 81 82 func TestMetaInputMode(t *testing.T) { 83 test = false 84 defer func() { test = true }() 85 86 m := new(Meta) 87 args := []string{} 88 89 fs := m.extendedFlagSet("foo") 90 if err := fs.Parse(args); err != nil { 91 t.Fatalf("err: %s", err) 92 } 93 94 if m.InputMode() != tofu.InputModeStd { 95 t.Fatalf("bad: %#v", m.InputMode()) 96 } 97 } 98 99 func TestMetaInputMode_envVar(t *testing.T) { 100 test = false 101 defer func() { test = true }() 102 103 m := new(Meta) 104 args := []string{} 105 106 fs := m.extendedFlagSet("foo") 107 if err := fs.Parse(args); err != nil { 108 t.Fatalf("err: %s", err) 109 } 110 111 off := tofu.InputMode(0) 112 on := tofu.InputModeStd 113 cases := []struct { 114 EnvVar string 115 Expected tofu.InputMode 116 }{ 117 {"false", off}, 118 {"0", off}, 119 {"true", on}, 120 {"1", on}, 121 } 122 123 for _, tc := range cases { 124 t.Setenv(InputModeEnvVar, tc.EnvVar) 125 if m.InputMode() != tc.Expected { 126 t.Fatalf("expected InputMode: %#v, got: %#v", tc.Expected, m.InputMode()) 127 } 128 } 129 } 130 131 func TestMetaInputMode_disable(t *testing.T) { 132 test = false 133 defer func() { test = true }() 134 135 m := new(Meta) 136 args := []string{"-input=false"} 137 138 fs := m.extendedFlagSet("foo") 139 if err := fs.Parse(args); err != nil { 140 t.Fatalf("err: %s", err) 141 } 142 143 if m.InputMode() > 0 { 144 t.Fatalf("bad: %#v", m.InputMode()) 145 } 146 } 147 148 func TestMeta_initStatePaths(t *testing.T) { 149 m := new(Meta) 150 m.initStatePaths() 151 152 if m.statePath != DefaultStateFilename { 153 t.Fatalf("bad: %#v", m) 154 } 155 if m.stateOutPath != DefaultStateFilename { 156 t.Fatalf("bad: %#v", m) 157 } 158 if m.backupPath != DefaultStateFilename+DefaultBackupExtension { 159 t.Fatalf("bad: %#v", m) 160 } 161 162 m = new(Meta) 163 m.statePath = "foo" 164 m.initStatePaths() 165 166 if m.stateOutPath != "foo" { 167 t.Fatalf("bad: %#v", m) 168 } 169 if m.backupPath != "foo"+DefaultBackupExtension { 170 t.Fatalf("bad: %#v", m) 171 } 172 173 m = new(Meta) 174 m.stateOutPath = "foo" 175 m.initStatePaths() 176 177 if m.statePath != DefaultStateFilename { 178 t.Fatalf("bad: %#v", m) 179 } 180 if m.backupPath != "foo"+DefaultBackupExtension { 181 t.Fatalf("bad: %#v", m) 182 } 183 } 184 185 func TestMeta_Env(t *testing.T) { 186 td := t.TempDir() 187 os.MkdirAll(td, 0755) 188 defer testChdir(t, td)() 189 190 m := new(Meta) 191 192 env, err := m.Workspace() 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 if env != backend.DefaultStateName { 198 t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) 199 } 200 201 testEnv := "test_env" 202 if err := m.SetWorkspace(testEnv); err != nil { 203 t.Fatal("error setting env:", err) 204 } 205 206 env, _ = m.Workspace() 207 if env != testEnv { 208 t.Fatalf("expected env %q, got env %q", testEnv, env) 209 } 210 211 if err := m.SetWorkspace(backend.DefaultStateName); err != nil { 212 t.Fatal("error setting env:", err) 213 } 214 215 env, _ = m.Workspace() 216 if env != backend.DefaultStateName { 217 t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) 218 } 219 } 220 221 func TestMeta_Workspace_override(t *testing.T) { 222 m := new(Meta) 223 224 testCases := map[string]struct { 225 workspace string 226 err error 227 }{ 228 "": { 229 "default", 230 nil, 231 }, 232 "development": { 233 "development", 234 nil, 235 }, 236 "invalid name": { 237 "", 238 errInvalidWorkspaceNameEnvVar, 239 }, 240 } 241 242 for name, tc := range testCases { 243 t.Run(name, func(t *testing.T) { 244 t.Setenv(WorkspaceNameEnvVar, name) 245 workspace, err := m.Workspace() 246 if workspace != tc.workspace { 247 t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", workspace, tc.workspace) 248 } 249 if err != tc.err { 250 t.Errorf("Unexpected error\n got: %s\nwant: %s\n", err, tc.err) 251 } 252 }) 253 } 254 } 255 256 func TestMeta_Workspace_invalidSelected(t *testing.T) { 257 td := t.TempDir() 258 os.MkdirAll(td, 0755) 259 defer testChdir(t, td)() 260 261 // this is an invalid workspace name 262 workspace := "test workspace" 263 264 // create the workspace directories 265 if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, workspace), 0755); err != nil { 266 t.Fatal(err) 267 } 268 269 // create the workspace file to select it 270 if err := os.MkdirAll(DefaultDataDir, 0755); err != nil { 271 t.Fatal(err) 272 } 273 if err := os.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte(workspace), 0644); err != nil { 274 t.Fatal(err) 275 } 276 277 m := new(Meta) 278 279 ws, err := m.Workspace() 280 if ws != workspace { 281 t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", ws, workspace) 282 } 283 if err != nil { 284 t.Errorf("Unexpected error: %s", err) 285 } 286 } 287 288 func TestMeta_process(t *testing.T) { 289 test = false 290 defer func() { test = true }() 291 292 // Create a temporary directory for our cwd 293 d := t.TempDir() 294 os.MkdirAll(d, 0755) 295 defer testChdir(t, d)() 296 297 // At one point it was the responsibility of this process function to 298 // insert fake additional -var-file options into the command line 299 // if the automatic tfvars files were present. This is no longer the 300 // responsibility of process (it happens in collectVariableValues instead) 301 // but we're still testing with these files in place to verify that 302 // they _aren't_ being interpreted by process, since that could otherwise 303 // cause them to be added more than once and mess up the precedence order. 304 defaultVarsfile := "terraform.tfvars" 305 err := os.WriteFile( 306 filepath.Join(d, defaultVarsfile), 307 []byte(""), 308 0644) 309 if err != nil { 310 t.Fatalf("err: %s", err) 311 } 312 fileFirstAlphabetical := "a-file.auto.tfvars" 313 err = os.WriteFile( 314 filepath.Join(d, fileFirstAlphabetical), 315 []byte(""), 316 0644) 317 if err != nil { 318 t.Fatalf("err: %s", err) 319 } 320 fileLastAlphabetical := "z-file.auto.tfvars" 321 err = os.WriteFile( 322 filepath.Join(d, fileLastAlphabetical), 323 []byte(""), 324 0644) 325 if err != nil { 326 t.Fatalf("err: %s", err) 327 } 328 // Regular tfvars files will not be autoloaded 329 fileIgnored := "ignored.tfvars" 330 err = os.WriteFile( 331 filepath.Join(d, fileIgnored), 332 []byte(""), 333 0644) 334 if err != nil { 335 t.Fatalf("err: %s", err) 336 } 337 338 tests := []struct { 339 GivenArgs []string 340 FilteredArgs []string 341 ExtraCheck func(*testing.T, *Meta) 342 }{ 343 { 344 []string{}, 345 []string{}, 346 func(t *testing.T, m *Meta) { 347 if got, want := m.color, true; got != want { 348 t.Errorf("wrong m.color value %#v; want %#v", got, want) 349 } 350 if got, want := m.Color, true; got != want { 351 t.Errorf("wrong m.Color value %#v; want %#v", got, want) 352 } 353 }, 354 }, 355 { 356 []string{"-no-color"}, 357 []string{}, 358 func(t *testing.T, m *Meta) { 359 if got, want := m.color, false; got != want { 360 t.Errorf("wrong m.color value %#v; want %#v", got, want) 361 } 362 if got, want := m.Color, false; got != want { 363 t.Errorf("wrong m.Color value %#v; want %#v", got, want) 364 } 365 }, 366 }, 367 } 368 369 for _, test := range tests { 370 t.Run(fmt.Sprintf("%s", test.GivenArgs), func(t *testing.T) { 371 m := new(Meta) 372 m.Color = true // this is the default also for normal use, overridden by -no-color 373 args := test.GivenArgs 374 args = m.process(args) 375 376 if !cmp.Equal(test.FilteredArgs, args) { 377 t.Errorf("wrong filtered arguments\n%s", cmp.Diff(test.FilteredArgs, args)) 378 } 379 380 if test.ExtraCheck != nil { 381 test.ExtraCheck(t, m) 382 } 383 }) 384 } 385 } 386 387 func TestCommand_checkRequiredVersion(t *testing.T) { 388 // Create a temporary working directory that is empty 389 td := t.TempDir() 390 testCopyDir(t, testFixturePath("command-check-required-version"), td) 391 defer testChdir(t, td)() 392 393 ui := cli.NewMockUi() 394 meta := Meta{ 395 Ui: ui, 396 } 397 398 diags := meta.checkRequiredVersion() 399 if diags == nil { 400 t.Fatalf("diagnostics should contain unmet version constraint, but is nil") 401 } 402 403 meta.showDiagnostics(diags) 404 405 // Required version diags are correct 406 errStr := ui.ErrorWriter.String() 407 if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) { 408 t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) 409 } 410 if strings.Contains(errStr, `required_version = ">= 0.13.0"`) { 411 t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr) 412 } 413 }