github.com/davebizus/terraform-main@v0.11.12-beta1/command/format/plan_test.go (about) 1 package format 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 8 "github.com/davecgh/go-spew/spew" 9 "github.com/hashicorp/terraform/terraform" 10 "github.com/mitchellh/colorstring" 11 ) 12 13 var disabledColorize = &colorstring.Colorize{ 14 Colors: colorstring.DefaultColors, 15 Disable: true, 16 } 17 18 func TestNewPlan(t *testing.T) { 19 tests := map[string]struct { 20 Input *terraform.Plan 21 Want *Plan 22 }{ 23 "nil input": { 24 Input: nil, 25 Want: &Plan{ 26 Resources: nil, 27 }, 28 }, 29 "nil diff": { 30 Input: &terraform.Plan{}, 31 Want: &Plan{ 32 Resources: nil, 33 }, 34 }, 35 "empty diff": { 36 Input: &terraform.Plan{ 37 Diff: &terraform.Diff{ 38 Modules: []*terraform.ModuleDiff{ 39 { 40 Path: []string{"root"}, 41 Resources: map[string]*terraform.InstanceDiff{}, 42 }, 43 }, 44 }, 45 }, 46 Want: &Plan{ 47 Resources: nil, 48 }, 49 }, 50 "create managed resource": { 51 Input: &terraform.Plan{ 52 Diff: &terraform.Diff{ 53 Modules: []*terraform.ModuleDiff{ 54 { 55 Path: []string{"root"}, 56 Resources: map[string]*terraform.InstanceDiff{ 57 "test_resource.foo": { 58 Attributes: map[string]*terraform.ResourceAttrDiff{ 59 "id": { 60 NewComputed: true, 61 RequiresNew: true, 62 }, 63 }, 64 }, 65 }, 66 }, 67 }, 68 }, 69 }, 70 Want: &Plan{ 71 Resources: []*InstanceDiff{ 72 { 73 Addr: mustParseResourceAddress("test_resource.foo"), 74 Action: terraform.DiffCreate, 75 Attributes: []*AttributeDiff{ 76 { 77 Path: "id", 78 Action: terraform.DiffCreate, 79 NewComputed: true, 80 ForcesNew: true, 81 }, 82 }, 83 }, 84 }, 85 }, 86 }, 87 "create managed resource in child module": { 88 Input: &terraform.Plan{ 89 Diff: &terraform.Diff{ 90 Modules: []*terraform.ModuleDiff{ 91 { 92 Path: []string{"root"}, 93 Resources: map[string]*terraform.InstanceDiff{ 94 "test_resource.foo": { 95 Attributes: map[string]*terraform.ResourceAttrDiff{ 96 "id": { 97 NewComputed: true, 98 RequiresNew: true, 99 }, 100 }, 101 }, 102 }, 103 }, 104 { 105 Path: []string{"root", "foo"}, 106 Resources: map[string]*terraform.InstanceDiff{ 107 "test_resource.foo": { 108 Attributes: map[string]*terraform.ResourceAttrDiff{ 109 "id": { 110 NewComputed: true, 111 RequiresNew: true, 112 }, 113 }, 114 }, 115 }, 116 }, 117 }, 118 }, 119 }, 120 Want: &Plan{ 121 Resources: []*InstanceDiff{ 122 { 123 Addr: mustParseResourceAddress("test_resource.foo"), 124 Action: terraform.DiffCreate, 125 Attributes: []*AttributeDiff{ 126 { 127 Path: "id", 128 Action: terraform.DiffCreate, 129 NewComputed: true, 130 ForcesNew: true, 131 }, 132 }, 133 }, 134 { 135 Addr: mustParseResourceAddress("module.foo.test_resource.foo"), 136 Action: terraform.DiffCreate, 137 Attributes: []*AttributeDiff{ 138 { 139 Path: "id", 140 Action: terraform.DiffCreate, 141 NewComputed: true, 142 ForcesNew: true, 143 }, 144 }, 145 }, 146 }, 147 }, 148 }, 149 "create data resource": { 150 Input: &terraform.Plan{ 151 Diff: &terraform.Diff{ 152 Modules: []*terraform.ModuleDiff{ 153 { 154 Path: []string{"root"}, 155 Resources: map[string]*terraform.InstanceDiff{ 156 "data.test_data_source.foo": { 157 Attributes: map[string]*terraform.ResourceAttrDiff{ 158 "id": { 159 NewComputed: true, 160 RequiresNew: true, 161 }, 162 }, 163 }, 164 }, 165 }, 166 }, 167 }, 168 }, 169 Want: &Plan{ 170 Resources: []*InstanceDiff{ 171 { 172 Addr: mustParseResourceAddress("data.test_data_source.foo"), 173 Action: terraform.DiffRefresh, 174 Attributes: []*AttributeDiff{ 175 { 176 Path: "id", 177 Action: terraform.DiffUpdate, 178 NewComputed: true, 179 ForcesNew: true, 180 }, 181 }, 182 }, 183 }, 184 }, 185 }, 186 "destroy managed resource": { 187 Input: &terraform.Plan{ 188 Diff: &terraform.Diff{ 189 Modules: []*terraform.ModuleDiff{ 190 { 191 Path: []string{"root"}, 192 Resources: map[string]*terraform.InstanceDiff{ 193 "test_resource.foo": { 194 Destroy: true, 195 }, 196 }, 197 }, 198 }, 199 }, 200 }, 201 Want: &Plan{ 202 Resources: []*InstanceDiff{ 203 { 204 Addr: mustParseResourceAddress("test_resource.foo"), 205 Action: terraform.DiffDestroy, 206 }, 207 }, 208 }, 209 }, 210 "destroy data resource": { 211 Input: &terraform.Plan{ 212 Diff: &terraform.Diff{ 213 Modules: []*terraform.ModuleDiff{ 214 { 215 Path: []string{"root"}, 216 Resources: map[string]*terraform.InstanceDiff{ 217 "data.test_data_source.foo": { 218 Destroy: true, 219 }, 220 }, 221 }, 222 }, 223 }, 224 }, 225 Want: &Plan{ 226 // Data source destroys are not shown 227 Resources: nil, 228 }, 229 }, 230 "destroy many instances of a resource": { 231 Input: &terraform.Plan{ 232 Diff: &terraform.Diff{ 233 Modules: []*terraform.ModuleDiff{ 234 { 235 Path: []string{"root"}, 236 Resources: map[string]*terraform.InstanceDiff{ 237 "test_resource.foo.0": { 238 Destroy: true, 239 }, 240 "test_resource.foo.1": { 241 Destroy: true, 242 }, 243 "test_resource.foo.10": { 244 Destroy: true, 245 }, 246 "test_resource.foo.2": { 247 Destroy: true, 248 }, 249 "test_resource.foo.3": { 250 Destroy: true, 251 }, 252 "test_resource.foo.4": { 253 Destroy: true, 254 }, 255 "test_resource.foo.5": { 256 Destroy: true, 257 }, 258 "test_resource.foo.6": { 259 Destroy: true, 260 }, 261 "test_resource.foo.7": { 262 Destroy: true, 263 }, 264 "test_resource.foo.8": { 265 Destroy: true, 266 }, 267 "test_resource.foo.9": { 268 Destroy: true, 269 }, 270 }, 271 }, 272 }, 273 }, 274 }, 275 Want: &Plan{ 276 Resources: []*InstanceDiff{ 277 { 278 Addr: mustParseResourceAddress("test_resource.foo[0]"), 279 Action: terraform.DiffDestroy, 280 }, 281 { 282 Addr: mustParseResourceAddress("test_resource.foo[1]"), 283 Action: terraform.DiffDestroy, 284 }, 285 { 286 Addr: mustParseResourceAddress("test_resource.foo[2]"), 287 Action: terraform.DiffDestroy, 288 }, 289 { 290 Addr: mustParseResourceAddress("test_resource.foo[3]"), 291 Action: terraform.DiffDestroy, 292 }, 293 { 294 Addr: mustParseResourceAddress("test_resource.foo[4]"), 295 Action: terraform.DiffDestroy, 296 }, 297 { 298 Addr: mustParseResourceAddress("test_resource.foo[5]"), 299 Action: terraform.DiffDestroy, 300 }, 301 { 302 Addr: mustParseResourceAddress("test_resource.foo[6]"), 303 Action: terraform.DiffDestroy, 304 }, 305 { 306 Addr: mustParseResourceAddress("test_resource.foo[7]"), 307 Action: terraform.DiffDestroy, 308 }, 309 { 310 Addr: mustParseResourceAddress("test_resource.foo[8]"), 311 Action: terraform.DiffDestroy, 312 }, 313 { 314 Addr: mustParseResourceAddress("test_resource.foo[9]"), 315 Action: terraform.DiffDestroy, 316 }, 317 { 318 Addr: mustParseResourceAddress("test_resource.foo[10]"), 319 Action: terraform.DiffDestroy, 320 }, 321 }, 322 }, 323 }, 324 } 325 326 for name, test := range tests { 327 t.Run(name, func(t *testing.T) { 328 got := NewPlan(test.Input) 329 if !reflect.DeepEqual(got, test.Want) { 330 t.Errorf( 331 "wrong result\ninput: %sgot: %swant:%s", 332 spew.Sdump(test.Input), 333 spew.Sdump(got), 334 spew.Sdump(test.Want), 335 ) 336 } 337 }) 338 } 339 } 340 341 func TestPlanStats(t *testing.T) { 342 tests := map[string]struct { 343 Input *Plan 344 Want PlanStats 345 }{ 346 "empty": { 347 &Plan{}, 348 PlanStats{}, 349 }, 350 "destroy": { 351 &Plan{ 352 Resources: []*InstanceDiff{ 353 { 354 Addr: mustParseResourceAddress("test_resource.foo"), 355 Action: terraform.DiffDestroy, 356 }, 357 { 358 Addr: mustParseResourceAddress("test_resource.bar"), 359 Action: terraform.DiffDestroy, 360 }, 361 }, 362 }, 363 PlanStats{ 364 ToDestroy: 2, 365 }, 366 }, 367 "create": { 368 &Plan{ 369 Resources: []*InstanceDiff{ 370 { 371 Addr: mustParseResourceAddress("test_resource.foo"), 372 Action: terraform.DiffCreate, 373 }, 374 { 375 Addr: mustParseResourceAddress("test_resource.bar"), 376 Action: terraform.DiffCreate, 377 }, 378 }, 379 }, 380 PlanStats{ 381 ToAdd: 2, 382 }, 383 }, 384 "update": { 385 &Plan{ 386 Resources: []*InstanceDiff{ 387 { 388 Addr: mustParseResourceAddress("test_resource.foo"), 389 Action: terraform.DiffUpdate, 390 }, 391 { 392 Addr: mustParseResourceAddress("test_resource.bar"), 393 Action: terraform.DiffUpdate, 394 }, 395 }, 396 }, 397 PlanStats{ 398 ToChange: 2, 399 }, 400 }, 401 "data source refresh": { 402 &Plan{ 403 Resources: []*InstanceDiff{ 404 { 405 Addr: mustParseResourceAddress("data.test.foo"), 406 Action: terraform.DiffRefresh, 407 }, 408 }, 409 }, 410 PlanStats{ 411 // data resource refreshes are not counted in our stats 412 }, 413 }, 414 "replace": { 415 &Plan{ 416 Resources: []*InstanceDiff{ 417 { 418 Addr: mustParseResourceAddress("test_resource.foo"), 419 Action: terraform.DiffDestroyCreate, 420 }, 421 { 422 Addr: mustParseResourceAddress("test_resource.bar"), 423 Action: terraform.DiffDestroyCreate, 424 }, 425 }, 426 }, 427 PlanStats{ 428 ToDestroy: 2, 429 ToAdd: 2, 430 }, 431 }, 432 } 433 434 for name, test := range tests { 435 t.Run(name, func(t *testing.T) { 436 got := test.Input.Stats() 437 if !reflect.DeepEqual(got, test.Want) { 438 t.Errorf( 439 "wrong result\ninput: %sgot: %swant:%s", 440 spew.Sdump(test.Input), 441 spew.Sdump(got), 442 spew.Sdump(test.Want), 443 ) 444 } 445 }) 446 } 447 } 448 449 // Test that deposed instances are marked as such 450 func TestPlan_destroyDeposed(t *testing.T) { 451 plan := &terraform.Plan{ 452 Diff: &terraform.Diff{ 453 Modules: []*terraform.ModuleDiff{ 454 &terraform.ModuleDiff{ 455 Path: []string{"root"}, 456 Resources: map[string]*terraform.InstanceDiff{ 457 "aws_instance.foo": &terraform.InstanceDiff{ 458 DestroyDeposed: true, 459 }, 460 }, 461 }, 462 }, 463 }, 464 } 465 dispPlan := NewPlan(plan) 466 actual := dispPlan.Format(disabledColorize) 467 468 expected := strings.TrimSpace(` 469 - aws_instance.foo (deposed) 470 `) 471 if actual != expected { 472 t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual) 473 } 474 } 475 476 // Test that computed fields with an interpolation string get displayed 477 func TestPlan_displayInterpolations(t *testing.T) { 478 plan := &terraform.Plan{ 479 Diff: &terraform.Diff{ 480 Modules: []*terraform.ModuleDiff{ 481 &terraform.ModuleDiff{ 482 Path: []string{"root"}, 483 Resources: map[string]*terraform.InstanceDiff{ 484 "aws_instance.foo": &terraform.InstanceDiff{ 485 Attributes: map[string]*terraform.ResourceAttrDiff{ 486 "computed_field": &terraform.ResourceAttrDiff{ 487 New: "${aws_instance.other.id}", 488 NewComputed: true, 489 }, 490 }, 491 }, 492 }, 493 }, 494 }, 495 }, 496 } 497 dispPlan := NewPlan(plan) 498 out := dispPlan.Format(disabledColorize) 499 lines := strings.Split(out, "\n") 500 if len(lines) != 2 { 501 t.Fatal("expected 2 lines of output, got:\n", out) 502 } 503 504 actual := strings.TrimSpace(lines[1]) 505 expected := `computed_field: "" => "${aws_instance.other.id}"` 506 507 if actual != expected { 508 t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual) 509 } 510 } 511 512 // Ensure that (forces new resource) text is included 513 // https://github.com/hashicorp/terraform/issues/16035 514 func TestPlan_forcesNewResource(t *testing.T) { 515 plan := &terraform.Plan{ 516 Diff: &terraform.Diff{ 517 Modules: []*terraform.ModuleDiff{ 518 &terraform.ModuleDiff{ 519 Path: []string{"root"}, 520 Resources: map[string]*terraform.InstanceDiff{ 521 "test_resource.foo": &terraform.InstanceDiff{ 522 Destroy: true, 523 Attributes: map[string]*terraform.ResourceAttrDiff{ 524 "A": &terraform.ResourceAttrDiff{ 525 New: "B", 526 RequiresNew: true, 527 }, 528 }, 529 }, 530 }, 531 }, 532 }, 533 }, 534 } 535 dispPlan := NewPlan(plan) 536 actual := dispPlan.Format(disabledColorize) 537 538 expected := strings.TrimSpace(` 539 -/+ test_resource.foo (new resource required) 540 A: "" => "B" (forces new resource) 541 `) 542 if actual != expected { 543 t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual) 544 } 545 } 546 547 // Test that a root level data source gets a special plan output on create 548 func TestPlan_rootDataSource(t *testing.T) { 549 plan := &terraform.Plan{ 550 Diff: &terraform.Diff{ 551 Modules: []*terraform.ModuleDiff{ 552 &terraform.ModuleDiff{ 553 Path: []string{"root"}, 554 Resources: map[string]*terraform.InstanceDiff{ 555 "data.type.name": &terraform.InstanceDiff{ 556 Attributes: map[string]*terraform.ResourceAttrDiff{ 557 "A": &terraform.ResourceAttrDiff{ 558 New: "B", 559 RequiresNew: true, 560 }, 561 }, 562 }, 563 }, 564 }, 565 }, 566 }, 567 } 568 dispPlan := NewPlan(plan) 569 actual := dispPlan.Format(disabledColorize) 570 571 expected := strings.TrimSpace(` 572 <= data.type.name 573 A: "B" 574 `) 575 if actual != expected { 576 t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual) 577 } 578 } 579 580 // Test that data sources nested in modules get the same plan output 581 func TestPlan_nestedDataSource(t *testing.T) { 582 plan := &terraform.Plan{ 583 Diff: &terraform.Diff{ 584 Modules: []*terraform.ModuleDiff{ 585 &terraform.ModuleDiff{ 586 Path: []string{"root", "nested"}, 587 Resources: map[string]*terraform.InstanceDiff{ 588 "data.type.name": &terraform.InstanceDiff{ 589 Attributes: map[string]*terraform.ResourceAttrDiff{ 590 "A": &terraform.ResourceAttrDiff{ 591 New: "B", 592 RequiresNew: true, 593 }, 594 }, 595 }, 596 }, 597 }, 598 }, 599 }, 600 } 601 dispPlan := NewPlan(plan) 602 actual := dispPlan.Format(disabledColorize) 603 604 expected := strings.TrimSpace(` 605 <= module.nested.data.type.name 606 A: "B" 607 `) 608 if actual != expected { 609 t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual) 610 } 611 } 612 613 func mustParseResourceAddress(s string) *terraform.ResourceAddress { 614 addr, err := terraform.ParseResourceAddress(s) 615 if err != nil { 616 panic(err) 617 } 618 return addr 619 }