github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/backend/local/backend_plan_test.go (about) 1 package local 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "reflect" 8 "strings" 9 "testing" 10 11 "github.com/hashicorp/terraform/backend" 12 "github.com/hashicorp/terraform/config/module" 13 "github.com/hashicorp/terraform/terraform" 14 "github.com/mitchellh/cli" 15 ) 16 17 func TestLocal_planBasic(t *testing.T) { 18 b := TestLocal(t) 19 p := TestLocalProvider(t, b, "test") 20 21 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 22 defer modCleanup() 23 24 op := testOperationPlan() 25 op.Module = mod 26 op.PlanRefresh = true 27 28 run, err := b.Operation(context.Background(), op) 29 if err != nil { 30 t.Fatalf("bad: %s", err) 31 } 32 <-run.Done() 33 if run.Err != nil { 34 t.Fatalf("err: %s", err) 35 } 36 37 if !p.DiffCalled { 38 t.Fatal("diff should be called") 39 } 40 } 41 42 func TestLocal_planInAutomation(t *testing.T) { 43 b := TestLocal(t) 44 TestLocalProvider(t, b, "test") 45 46 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 47 defer modCleanup() 48 49 const msg = `You didn't specify an "-out" parameter` 50 51 // When we're "in automation" we omit certain text from the 52 // plan output. However, testing for the absense of text is 53 // unreliable in the face of future copy changes, so we'll 54 // mitigate that by running both with and without the flag 55 // set so we can ensure that the expected messages _are_ 56 // included the first time. 57 b.RunningInAutomation = false 58 b.CLI = cli.NewMockUi() 59 { 60 op := testOperationPlan() 61 op.Module = mod 62 op.PlanRefresh = true 63 64 run, err := b.Operation(context.Background(), op) 65 if err != nil { 66 t.Fatalf("unexpected error: %s", err) 67 } 68 <-run.Done() 69 if run.Err != nil { 70 t.Fatalf("unexpected error: %s", err) 71 } 72 73 output := b.CLI.(*cli.MockUi).OutputWriter.String() 74 if !strings.Contains(output, msg) { 75 t.Fatalf("missing next-steps message when not in automation") 76 } 77 } 78 79 // On the second run, we expect the next-steps messaging to be absent 80 // since we're now "running in automation". 81 b.RunningInAutomation = true 82 b.CLI = cli.NewMockUi() 83 { 84 op := testOperationPlan() 85 op.Module = mod 86 op.PlanRefresh = true 87 88 run, err := b.Operation(context.Background(), op) 89 if err != nil { 90 t.Fatalf("unexpected error: %s", err) 91 } 92 <-run.Done() 93 if run.Err != nil { 94 t.Fatalf("unexpected error: %s", err) 95 } 96 97 output := b.CLI.(*cli.MockUi).OutputWriter.String() 98 if strings.Contains(output, msg) { 99 t.Fatalf("next-steps message present when in automation") 100 } 101 } 102 103 } 104 105 func TestLocal_planNoConfig(t *testing.T) { 106 b := TestLocal(t) 107 TestLocalProvider(t, b, "test") 108 109 op := testOperationPlan() 110 op.Module = nil 111 op.PlanRefresh = true 112 113 run, err := b.Operation(context.Background(), op) 114 if err != nil { 115 t.Fatalf("bad: %s", err) 116 } 117 <-run.Done() 118 119 err = run.Err 120 if err == nil { 121 t.Fatal("should error") 122 } 123 if !strings.Contains(err.Error(), "configuration") { 124 t.Fatalf("bad: %s", err) 125 } 126 } 127 128 func TestLocal_planRefreshFalse(t *testing.T) { 129 b := TestLocal(t) 130 p := TestLocalProvider(t, b, "test") 131 terraform.TestStateFile(t, b.StatePath, testPlanState()) 132 133 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 134 defer modCleanup() 135 136 op := testOperationPlan() 137 op.Module = mod 138 139 run, err := b.Operation(context.Background(), op) 140 if err != nil { 141 t.Fatalf("bad: %s", err) 142 } 143 <-run.Done() 144 if run.Err != nil { 145 t.Fatalf("err: %s", err) 146 } 147 148 if p.RefreshCalled { 149 t.Fatal("refresh should not be called") 150 } 151 152 if !run.PlanEmpty { 153 t.Fatal("plan should be empty") 154 } 155 } 156 157 func TestLocal_planDestroy(t *testing.T) { 158 b := TestLocal(t) 159 p := TestLocalProvider(t, b, "test") 160 terraform.TestStateFile(t, b.StatePath, testPlanState()) 161 162 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 163 defer modCleanup() 164 165 outDir := testTempDir(t) 166 defer os.RemoveAll(outDir) 167 planPath := filepath.Join(outDir, "plan.tfplan") 168 169 op := testOperationPlan() 170 op.Destroy = true 171 op.PlanRefresh = true 172 op.Module = mod 173 op.PlanOutPath = planPath 174 175 run, err := b.Operation(context.Background(), op) 176 if err != nil { 177 t.Fatalf("bad: %s", err) 178 } 179 <-run.Done() 180 if run.Err != nil { 181 t.Fatalf("err: %s", err) 182 } 183 184 if !p.RefreshCalled { 185 t.Fatal("refresh should be called") 186 } 187 188 if run.PlanEmpty { 189 t.Fatal("plan should not be empty") 190 } 191 192 plan := testReadPlan(t, planPath) 193 for _, m := range plan.Diff.Modules { 194 for _, r := range m.Resources { 195 if !r.Destroy { 196 t.Fatalf("bad: %#v", r) 197 } 198 } 199 } 200 } 201 202 func TestLocal_planDestroyNoConfig(t *testing.T) { 203 b := TestLocal(t) 204 p := TestLocalProvider(t, b, "test") 205 terraform.TestStateFile(t, b.StatePath, testPlanState()) 206 207 outDir := testTempDir(t) 208 defer os.RemoveAll(outDir) 209 planPath := filepath.Join(outDir, "plan.tfplan") 210 211 op := testOperationPlan() 212 op.Destroy = true 213 op.PlanRefresh = true 214 op.Module = nil 215 op.PlanOutPath = planPath 216 217 run, err := b.Operation(context.Background(), op) 218 if err != nil { 219 t.Fatalf("bad: %s", err) 220 } 221 <-run.Done() 222 if run.Err != nil { 223 t.Fatalf("err: %s", err) 224 } 225 226 if !p.RefreshCalled { 227 t.Fatal("refresh should be called") 228 } 229 230 if run.PlanEmpty { 231 t.Fatal("plan should not be empty") 232 } 233 234 plan := testReadPlan(t, planPath) 235 for _, m := range plan.Diff.Modules { 236 for _, r := range m.Resources { 237 if !r.Destroy { 238 t.Fatalf("bad: %#v", r) 239 } 240 } 241 } 242 } 243 244 func TestLocal_planOutPathNoChange(t *testing.T) { 245 b := TestLocal(t) 246 TestLocalProvider(t, b, "test") 247 terraform.TestStateFile(t, b.StatePath, testPlanState()) 248 249 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 250 defer modCleanup() 251 252 outDir := testTempDir(t) 253 defer os.RemoveAll(outDir) 254 planPath := filepath.Join(outDir, "plan.tfplan") 255 256 op := testOperationPlan() 257 op.Module = mod 258 op.PlanOutPath = planPath 259 260 run, err := b.Operation(context.Background(), op) 261 if err != nil { 262 t.Fatalf("bad: %s", err) 263 } 264 <-run.Done() 265 if run.Err != nil { 266 t.Fatalf("err: %s", err) 267 } 268 269 plan := testReadPlan(t, planPath) 270 if !plan.Diff.Empty() { 271 t.Fatalf("expected empty plan to be written") 272 } 273 } 274 275 // TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a 276 // resource count is scaled out. The scaled out node needs to exist in the 277 // graph and run through a plan-style sequence during the refresh phase, but 278 // can conflate the count if its post-diff count hooks are not skipped. This 279 // checks to make sure the correct resource count is ultimately given to the 280 // UI. 281 func TestLocal_planScaleOutNoDupeCount(t *testing.T) { 282 b := TestLocal(t) 283 TestLocalProvider(t, b, "test") 284 state := &terraform.State{ 285 Version: 2, 286 Modules: []*terraform.ModuleState{ 287 &terraform.ModuleState{ 288 Path: []string{"root"}, 289 Resources: map[string]*terraform.ResourceState{ 290 "test_instance.foo.0": &terraform.ResourceState{ 291 Type: "test_instance", 292 Primary: &terraform.InstanceState{ 293 ID: "bar", 294 }, 295 }, 296 "test_instance.foo.1": &terraform.ResourceState{ 297 Type: "test_instance", 298 Primary: &terraform.InstanceState{ 299 ID: "bar", 300 }, 301 }, 302 }, 303 }, 304 }, 305 } 306 terraform.TestStateFile(t, b.StatePath, state) 307 308 actual := new(CountHook) 309 b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual) 310 311 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout") 312 defer modCleanup() 313 314 outDir := testTempDir(t) 315 defer os.RemoveAll(outDir) 316 317 op := testOperationPlan() 318 op.Module = mod 319 op.PlanRefresh = true 320 321 run, err := b.Operation(context.Background(), op) 322 if err != nil { 323 t.Fatalf("bad: %s", err) 324 } 325 <-run.Done() 326 if run.Err != nil { 327 t.Fatalf("err: %s", err) 328 } 329 330 expected := new(CountHook) 331 expected.ToAdd = 1 332 expected.ToChange = 0 333 expected.ToRemoveAndAdd = 0 334 expected.ToRemove = 0 335 336 if !reflect.DeepEqual(expected, actual) { 337 t.Fatalf("Expected %#v, got %#v instead.", 338 expected, actual) 339 } 340 } 341 342 func testOperationPlan() *backend.Operation { 343 return &backend.Operation{ 344 Type: backend.OperationTypePlan, 345 } 346 } 347 348 // testPlanState is just a common state that we use for testing refresh. 349 func testPlanState() *terraform.State { 350 return &terraform.State{ 351 Version: 2, 352 Modules: []*terraform.ModuleState{ 353 &terraform.ModuleState{ 354 Path: []string{"root"}, 355 Resources: map[string]*terraform.ResourceState{ 356 "test_instance.foo": &terraform.ResourceState{ 357 Type: "test_instance", 358 Primary: &terraform.InstanceState{ 359 ID: "bar", 360 }, 361 }, 362 }, 363 }, 364 }, 365 } 366 } 367 368 func testReadPlan(t *testing.T, path string) *terraform.Plan { 369 f, err := os.Open(path) 370 if err != nil { 371 t.Fatalf("err: %s", err) 372 } 373 defer f.Close() 374 375 p, err := terraform.ReadPlan(f) 376 if err != nil { 377 t.Fatalf("err: %s", err) 378 } 379 380 return p 381 }