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