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