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