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