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