github.com/tompao/terraform@v0.6.10-0.20180215233341-e41b29d0961b/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_planOutPathNoChange(t *testing.T) { 203 b := TestLocal(t) 204 TestLocalProvider(t, b, "test") 205 terraform.TestStateFile(t, b.StatePath, testPlanState()) 206 207 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 208 defer modCleanup() 209 210 outDir := testTempDir(t) 211 defer os.RemoveAll(outDir) 212 planPath := filepath.Join(outDir, "plan.tfplan") 213 214 op := testOperationPlan() 215 op.Module = mod 216 op.PlanOutPath = planPath 217 218 run, err := b.Operation(context.Background(), op) 219 if err != nil { 220 t.Fatalf("bad: %s", err) 221 } 222 <-run.Done() 223 if run.Err != nil { 224 t.Fatalf("err: %s", err) 225 } 226 227 plan := testReadPlan(t, planPath) 228 if !plan.Diff.Empty() { 229 t.Fatalf("expected empty plan to be written") 230 } 231 } 232 233 // TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a 234 // resource count is scaled out. The scaled out node needs to exist in the 235 // graph and run through a plan-style sequence during the refresh phase, but 236 // can conflate the count if its post-diff count hooks are not skipped. This 237 // checks to make sure the correct resource count is ultimately given to the 238 // UI. 239 func TestLocal_planScaleOutNoDupeCount(t *testing.T) { 240 b := TestLocal(t) 241 TestLocalProvider(t, b, "test") 242 state := &terraform.State{ 243 Version: 2, 244 Modules: []*terraform.ModuleState{ 245 &terraform.ModuleState{ 246 Path: []string{"root"}, 247 Resources: map[string]*terraform.ResourceState{ 248 "test_instance.foo.0": &terraform.ResourceState{ 249 Type: "test_instance", 250 Primary: &terraform.InstanceState{ 251 ID: "bar", 252 }, 253 }, 254 "test_instance.foo.1": &terraform.ResourceState{ 255 Type: "test_instance", 256 Primary: &terraform.InstanceState{ 257 ID: "bar", 258 }, 259 }, 260 }, 261 }, 262 }, 263 } 264 terraform.TestStateFile(t, b.StatePath, state) 265 266 actual := new(CountHook) 267 b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual) 268 269 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout") 270 defer modCleanup() 271 272 outDir := testTempDir(t) 273 defer os.RemoveAll(outDir) 274 275 op := testOperationPlan() 276 op.Module = mod 277 op.PlanRefresh = true 278 279 run, err := b.Operation(context.Background(), op) 280 if err != nil { 281 t.Fatalf("bad: %s", err) 282 } 283 <-run.Done() 284 if run.Err != nil { 285 t.Fatalf("err: %s", err) 286 } 287 288 expected := new(CountHook) 289 expected.ToAdd = 1 290 expected.ToChange = 0 291 expected.ToRemoveAndAdd = 0 292 expected.ToRemove = 0 293 294 if !reflect.DeepEqual(expected, actual) { 295 t.Fatalf("Expected %#v, got %#v instead.", 296 expected, actual) 297 } 298 } 299 300 func testOperationPlan() *backend.Operation { 301 return &backend.Operation{ 302 Type: backend.OperationTypePlan, 303 } 304 } 305 306 // testPlanState is just a common state that we use for testing refresh. 307 func testPlanState() *terraform.State { 308 return &terraform.State{ 309 Version: 2, 310 Modules: []*terraform.ModuleState{ 311 &terraform.ModuleState{ 312 Path: []string{"root"}, 313 Resources: map[string]*terraform.ResourceState{ 314 "test_instance.foo": &terraform.ResourceState{ 315 Type: "test_instance", 316 Primary: &terraform.InstanceState{ 317 ID: "bar", 318 }, 319 }, 320 }, 321 }, 322 }, 323 } 324 } 325 326 func testReadPlan(t *testing.T, path string) *terraform.Plan { 327 f, err := os.Open(path) 328 if err != nil { 329 t.Fatalf("err: %s", err) 330 } 331 defer f.Close() 332 333 p, err := terraform.ReadPlan(f) 334 if err != nil { 335 t.Fatalf("err: %s", err) 336 } 337 338 return p 339 }