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