github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/taint_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package command 5 6 import ( 7 "os" 8 "strings" 9 "testing" 10 11 "github.com/google/go-cmp/cmp" 12 "github.com/mitchellh/cli" 13 14 "github.com/terramate-io/tf/addrs" 15 "github.com/terramate-io/tf/states" 16 ) 17 18 func TestTaint(t *testing.T) { 19 state := states.BuildState(func(s *states.SyncState) { 20 s.SetResourceInstanceCurrent( 21 addrs.Resource{ 22 Mode: addrs.ManagedResourceMode, 23 Type: "test_instance", 24 Name: "foo", 25 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 26 &states.ResourceInstanceObjectSrc{ 27 AttrsJSON: []byte(`{"id":"bar"}`), 28 Status: states.ObjectReady, 29 }, 30 addrs.AbsProviderConfig{ 31 Provider: addrs.NewDefaultProvider("test"), 32 Module: addrs.RootModule, 33 }, 34 ) 35 }) 36 statePath := testStateFile(t, state) 37 38 ui := new(cli.MockUi) 39 view, _ := testView(t) 40 c := &TaintCommand{ 41 Meta: Meta{ 42 Ui: ui, 43 View: view, 44 }, 45 } 46 47 args := []string{ 48 "-state", statePath, 49 "test_instance.foo", 50 } 51 if code := c.Run(args); code != 0 { 52 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 53 } 54 55 testStateOutput(t, statePath, testTaintStr) 56 } 57 58 func TestTaint_lockedState(t *testing.T) { 59 state := states.BuildState(func(s *states.SyncState) { 60 s.SetResourceInstanceCurrent( 61 addrs.Resource{ 62 Mode: addrs.ManagedResourceMode, 63 Type: "test_instance", 64 Name: "foo", 65 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 66 &states.ResourceInstanceObjectSrc{ 67 AttrsJSON: []byte(`{"id":"bar"}`), 68 Status: states.ObjectReady, 69 }, 70 addrs.AbsProviderConfig{ 71 Provider: addrs.NewDefaultProvider("test"), 72 Module: addrs.RootModule, 73 }, 74 ) 75 }) 76 statePath := testStateFile(t, state) 77 78 unlock, err := testLockState(t, testDataDir, statePath) 79 if err != nil { 80 t.Fatal(err) 81 } 82 defer unlock() 83 ui := new(cli.MockUi) 84 view, _ := testView(t) 85 c := &TaintCommand{ 86 Meta: Meta{ 87 Ui: ui, 88 View: view, 89 }, 90 } 91 92 args := []string{ 93 "-state", statePath, 94 "test_instance.foo", 95 } 96 if code := c.Run(args); code == 0 { 97 t.Fatal("expected error") 98 } 99 100 output := ui.ErrorWriter.String() 101 if !strings.Contains(output, "lock") { 102 t.Fatal("command output does not look like a lock error:", output) 103 } 104 } 105 106 func TestTaint_backup(t *testing.T) { 107 // Get a temp cwd 108 testCwd(t) 109 110 // Write the temp state 111 state := states.BuildState(func(s *states.SyncState) { 112 s.SetResourceInstanceCurrent( 113 addrs.Resource{ 114 Mode: addrs.ManagedResourceMode, 115 Type: "test_instance", 116 Name: "foo", 117 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 118 &states.ResourceInstanceObjectSrc{ 119 AttrsJSON: []byte(`{"id":"bar"}`), 120 Status: states.ObjectReady, 121 }, 122 addrs.AbsProviderConfig{ 123 Provider: addrs.NewDefaultProvider("test"), 124 Module: addrs.RootModule, 125 }, 126 ) 127 }) 128 testStateFileDefault(t, state) 129 130 ui := new(cli.MockUi) 131 view, _ := testView(t) 132 c := &TaintCommand{ 133 Meta: Meta{ 134 Ui: ui, 135 View: view, 136 }, 137 } 138 139 args := []string{ 140 "test_instance.foo", 141 } 142 if code := c.Run(args); code != 0 { 143 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 144 } 145 146 testStateOutput(t, DefaultStateFilename+".backup", testTaintDefaultStr) 147 testStateOutput(t, DefaultStateFilename, testTaintStr) 148 } 149 150 func TestTaint_backupDisable(t *testing.T) { 151 // Get a temp cwd 152 testCwd(t) 153 154 // Write the temp state 155 state := states.BuildState(func(s *states.SyncState) { 156 s.SetResourceInstanceCurrent( 157 addrs.Resource{ 158 Mode: addrs.ManagedResourceMode, 159 Type: "test_instance", 160 Name: "foo", 161 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 162 &states.ResourceInstanceObjectSrc{ 163 AttrsJSON: []byte(`{"id":"bar"}`), 164 Status: states.ObjectReady, 165 }, 166 addrs.AbsProviderConfig{ 167 Provider: addrs.NewDefaultProvider("test"), 168 Module: addrs.RootModule, 169 }, 170 ) 171 }) 172 testStateFileDefault(t, state) 173 174 ui := new(cli.MockUi) 175 view, _ := testView(t) 176 c := &TaintCommand{ 177 Meta: Meta{ 178 Ui: ui, 179 View: view, 180 }, 181 } 182 183 args := []string{ 184 "-backup", "-", 185 "test_instance.foo", 186 } 187 if code := c.Run(args); code != 0 { 188 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 189 } 190 191 if _, err := os.Stat(DefaultStateFilename + ".backup"); err == nil { 192 t.Fatal("backup path should not exist") 193 } 194 195 testStateOutput(t, DefaultStateFilename, testTaintStr) 196 } 197 198 func TestTaint_badState(t *testing.T) { 199 ui := new(cli.MockUi) 200 view, _ := testView(t) 201 c := &TaintCommand{ 202 Meta: Meta{ 203 Ui: ui, 204 View: view, 205 }, 206 } 207 208 args := []string{ 209 "-state", "i-should-not-exist-ever", 210 "foo", 211 } 212 if code := c.Run(args); code != 1 { 213 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 214 } 215 } 216 217 func TestTaint_defaultState(t *testing.T) { 218 // Get a temp cwd 219 testCwd(t) 220 221 // Write the temp state 222 state := states.BuildState(func(s *states.SyncState) { 223 s.SetResourceInstanceCurrent( 224 addrs.Resource{ 225 Mode: addrs.ManagedResourceMode, 226 Type: "test_instance", 227 Name: "foo", 228 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 229 &states.ResourceInstanceObjectSrc{ 230 AttrsJSON: []byte(`{"id":"bar"}`), 231 Status: states.ObjectReady, 232 }, 233 addrs.AbsProviderConfig{ 234 Provider: addrs.NewDefaultProvider("test"), 235 Module: addrs.RootModule, 236 }, 237 ) 238 }) 239 testStateFileDefault(t, state) 240 241 ui := new(cli.MockUi) 242 view, _ := testView(t) 243 c := &TaintCommand{ 244 Meta: Meta{ 245 Ui: ui, 246 View: view, 247 }, 248 } 249 250 args := []string{ 251 "test_instance.foo", 252 } 253 if code := c.Run(args); code != 0 { 254 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 255 } 256 257 testStateOutput(t, DefaultStateFilename, testTaintStr) 258 } 259 260 func TestTaint_defaultWorkspaceState(t *testing.T) { 261 // Get a temp cwd 262 testCwd(t) 263 264 state := states.BuildState(func(s *states.SyncState) { 265 s.SetResourceInstanceCurrent( 266 addrs.Resource{ 267 Mode: addrs.ManagedResourceMode, 268 Type: "test_instance", 269 Name: "foo", 270 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 271 &states.ResourceInstanceObjectSrc{ 272 AttrsJSON: []byte(`{"id":"bar"}`), 273 Status: states.ObjectReady, 274 }, 275 addrs.AbsProviderConfig{ 276 Provider: addrs.NewDefaultProvider("test"), 277 Module: addrs.RootModule, 278 }, 279 ) 280 }) 281 testWorkspace := "development" 282 path := testStateFileWorkspaceDefault(t, testWorkspace, state) 283 284 ui := new(cli.MockUi) 285 view, _ := testView(t) 286 meta := Meta{Ui: ui, View: view} 287 meta.SetWorkspace(testWorkspace) 288 c := &TaintCommand{ 289 Meta: meta, 290 } 291 292 args := []string{ 293 "test_instance.foo", 294 } 295 if code := c.Run(args); code != 0 { 296 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 297 } 298 299 testStateOutput(t, path, testTaintStr) 300 } 301 302 func TestTaint_missing(t *testing.T) { 303 state := states.BuildState(func(s *states.SyncState) { 304 s.SetResourceInstanceCurrent( 305 addrs.Resource{ 306 Mode: addrs.ManagedResourceMode, 307 Type: "test_instance", 308 Name: "foo", 309 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 310 &states.ResourceInstanceObjectSrc{ 311 AttrsJSON: []byte(`{"id":"bar"}`), 312 Status: states.ObjectReady, 313 }, 314 addrs.AbsProviderConfig{ 315 Provider: addrs.NewDefaultProvider("test"), 316 Module: addrs.RootModule, 317 }, 318 ) 319 }) 320 statePath := testStateFile(t, state) 321 322 ui := new(cli.MockUi) 323 view, _ := testView(t) 324 c := &TaintCommand{ 325 Meta: Meta{ 326 Ui: ui, 327 View: view, 328 }, 329 } 330 331 args := []string{ 332 "-state", statePath, 333 "test_instance.bar", 334 } 335 if code := c.Run(args); code == 0 { 336 t.Fatalf("bad: %d\n\n%s", code, ui.OutputWriter.String()) 337 } 338 } 339 340 func TestTaint_missingAllow(t *testing.T) { 341 state := states.BuildState(func(s *states.SyncState) { 342 s.SetResourceInstanceCurrent( 343 addrs.Resource{ 344 Mode: addrs.ManagedResourceMode, 345 Type: "test_instance", 346 Name: "foo", 347 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 348 &states.ResourceInstanceObjectSrc{ 349 AttrsJSON: []byte(`{"id":"bar"}`), 350 Status: states.ObjectReady, 351 }, 352 addrs.AbsProviderConfig{ 353 Provider: addrs.NewDefaultProvider("test"), 354 Module: addrs.RootModule, 355 }, 356 ) 357 }) 358 statePath := testStateFile(t, state) 359 360 ui := new(cli.MockUi) 361 view, _ := testView(t) 362 c := &TaintCommand{ 363 Meta: Meta{ 364 Ui: ui, 365 View: view, 366 }, 367 } 368 369 args := []string{ 370 "-allow-missing", 371 "-state", statePath, 372 "test_instance.bar", 373 } 374 if code := c.Run(args); code != 0 { 375 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 376 } 377 378 // Check for the warning 379 actual := strings.TrimSpace(ui.ErrorWriter.String()) 380 expected := strings.TrimSpace(` 381 Warning: No such resource instance 382 383 Resource instance test_instance.bar was not found, but this is not an error 384 because -allow-missing was set. 385 386 `) 387 if diff := cmp.Diff(expected, actual); diff != "" { 388 t.Fatalf("wrong output\n%s", diff) 389 } 390 } 391 392 func TestTaint_stateOut(t *testing.T) { 393 // Get a temp cwd 394 testCwd(t) 395 396 // Write the temp state 397 state := states.BuildState(func(s *states.SyncState) { 398 s.SetResourceInstanceCurrent( 399 addrs.Resource{ 400 Mode: addrs.ManagedResourceMode, 401 Type: "test_instance", 402 Name: "foo", 403 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 404 &states.ResourceInstanceObjectSrc{ 405 AttrsJSON: []byte(`{"id":"bar"}`), 406 Status: states.ObjectReady, 407 }, 408 addrs.AbsProviderConfig{ 409 Provider: addrs.NewDefaultProvider("test"), 410 Module: addrs.RootModule, 411 }, 412 ) 413 }) 414 testStateFileDefault(t, state) 415 416 ui := new(cli.MockUi) 417 view, _ := testView(t) 418 c := &TaintCommand{ 419 Meta: Meta{ 420 Ui: ui, 421 View: view, 422 }, 423 } 424 425 args := []string{ 426 "-state-out", "foo", 427 "test_instance.foo", 428 } 429 if code := c.Run(args); code != 0 { 430 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 431 } 432 433 testStateOutput(t, DefaultStateFilename, testTaintDefaultStr) 434 testStateOutput(t, "foo", testTaintStr) 435 } 436 437 func TestTaint_module(t *testing.T) { 438 state := states.BuildState(func(s *states.SyncState) { 439 s.SetResourceInstanceCurrent( 440 addrs.Resource{ 441 Mode: addrs.ManagedResourceMode, 442 Type: "test_instance", 443 Name: "foo", 444 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 445 &states.ResourceInstanceObjectSrc{ 446 AttrsJSON: []byte(`{"id":"bar"}`), 447 Status: states.ObjectReady, 448 }, 449 addrs.AbsProviderConfig{ 450 Provider: addrs.NewDefaultProvider("test"), 451 Module: addrs.RootModule, 452 }, 453 ) 454 s.SetResourceInstanceCurrent( 455 addrs.Resource{ 456 Mode: addrs.ManagedResourceMode, 457 Type: "test_instance", 458 Name: "blah", 459 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)), 460 &states.ResourceInstanceObjectSrc{ 461 AttrsJSON: []byte(`{"id":"blah"}`), 462 Status: states.ObjectReady, 463 }, 464 addrs.AbsProviderConfig{ 465 Provider: addrs.NewDefaultProvider("test"), 466 Module: addrs.RootModule, 467 }, 468 ) 469 }) 470 statePath := testStateFile(t, state) 471 472 ui := new(cli.MockUi) 473 view, _ := testView(t) 474 c := &TaintCommand{ 475 Meta: Meta{ 476 Ui: ui, 477 View: view, 478 }, 479 } 480 481 args := []string{ 482 "-state", statePath, 483 "module.child.test_instance.blah", 484 } 485 if code := c.Run(args); code != 0 { 486 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 487 } 488 489 testStateOutput(t, statePath, testTaintModuleStr) 490 } 491 492 func TestTaint_checkRequiredVersion(t *testing.T) { 493 // Create a temporary working directory that is empty 494 td := t.TempDir() 495 testCopyDir(t, testFixturePath("command-check-required-version"), td) 496 defer testChdir(t, td)() 497 498 // Write the temp state 499 state := states.BuildState(func(s *states.SyncState) { 500 s.SetResourceInstanceCurrent( 501 addrs.Resource{ 502 Mode: addrs.ManagedResourceMode, 503 Type: "test_instance", 504 Name: "foo", 505 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 506 &states.ResourceInstanceObjectSrc{ 507 AttrsJSON: []byte(`{"id":"bar"}`), 508 Status: states.ObjectReady, 509 }, 510 addrs.AbsProviderConfig{ 511 Provider: addrs.NewDefaultProvider("test"), 512 Module: addrs.RootModule, 513 }, 514 ) 515 }) 516 path := testStateFile(t, state) 517 518 ui := cli.NewMockUi() 519 view, _ := testView(t) 520 c := &TaintCommand{ 521 Meta: Meta{ 522 testingOverrides: metaOverridesForProvider(testProvider()), 523 Ui: ui, 524 View: view, 525 }, 526 } 527 528 args := []string{"test_instance.foo"} 529 if code := c.Run(args); code != 1 { 530 t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) 531 } 532 533 // State is unchanged 534 testStateOutput(t, path, testTaintDefaultStr) 535 536 // Required version diags are correct 537 errStr := ui.ErrorWriter.String() 538 if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) { 539 t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) 540 } 541 if strings.Contains(errStr, `required_version = ">= 0.13.0"`) { 542 t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr) 543 } 544 } 545 546 const testTaintStr = ` 547 test_instance.foo: (tainted) 548 ID = bar 549 provider = provider["registry.terraform.io/hashicorp/test"] 550 ` 551 552 const testTaintDefaultStr = ` 553 test_instance.foo: 554 ID = bar 555 provider = provider["registry.terraform.io/hashicorp/test"] 556 ` 557 558 const testTaintModuleStr = ` 559 test_instance.foo: 560 ID = bar 561 provider = provider["registry.terraform.io/hashicorp/test"] 562 563 module.child: 564 test_instance.blah: (tainted) 565 ID = blah 566 provider = provider["registry.terraform.io/hashicorp/test"] 567 `