github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/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 ) 15 16 func TestLocal_planBasic(t *testing.T) { 17 b := TestLocal(t) 18 p := TestLocalProvider(t, b, "test") 19 20 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 21 defer modCleanup() 22 23 op := testOperationPlan() 24 op.Module = mod 25 op.PlanRefresh = true 26 27 run, err := b.Operation(context.Background(), op) 28 if err != nil { 29 t.Fatalf("bad: %s", err) 30 } 31 <-run.Done() 32 if run.Err != nil { 33 t.Fatalf("err: %s", err) 34 } 35 36 if !p.DiffCalled { 37 t.Fatal("diff should be called") 38 } 39 } 40 41 func TestLocal_planNoConfig(t *testing.T) { 42 b := TestLocal(t) 43 TestLocalProvider(t, b, "test") 44 45 op := testOperationPlan() 46 op.Module = nil 47 op.PlanRefresh = true 48 49 run, err := b.Operation(context.Background(), op) 50 if err != nil { 51 t.Fatalf("bad: %s", err) 52 } 53 <-run.Done() 54 55 err = run.Err 56 if err == nil { 57 t.Fatal("should error") 58 } 59 if !strings.Contains(err.Error(), "configuration") { 60 t.Fatalf("bad: %s", err) 61 } 62 } 63 64 func TestLocal_planRefreshFalse(t *testing.T) { 65 b := TestLocal(t) 66 p := TestLocalProvider(t, b, "test") 67 terraform.TestStateFile(t, b.StatePath, testPlanState()) 68 69 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 70 defer modCleanup() 71 72 op := testOperationPlan() 73 op.Module = mod 74 75 run, err := b.Operation(context.Background(), op) 76 if err != nil { 77 t.Fatalf("bad: %s", err) 78 } 79 <-run.Done() 80 if run.Err != nil { 81 t.Fatalf("err: %s", err) 82 } 83 84 if p.RefreshCalled { 85 t.Fatal("refresh should not be called") 86 } 87 88 if !run.PlanEmpty { 89 t.Fatal("plan should be empty") 90 } 91 } 92 93 func TestLocal_planDestroy(t *testing.T) { 94 b := TestLocal(t) 95 p := TestLocalProvider(t, b, "test") 96 terraform.TestStateFile(t, b.StatePath, testPlanState()) 97 98 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 99 defer modCleanup() 100 101 outDir := testTempDir(t) 102 defer os.RemoveAll(outDir) 103 planPath := filepath.Join(outDir, "plan.tfplan") 104 105 op := testOperationPlan() 106 op.Destroy = true 107 op.PlanRefresh = true 108 op.Module = mod 109 op.PlanOutPath = planPath 110 111 run, err := b.Operation(context.Background(), op) 112 if err != nil { 113 t.Fatalf("bad: %s", err) 114 } 115 <-run.Done() 116 if run.Err != nil { 117 t.Fatalf("err: %s", err) 118 } 119 120 if !p.RefreshCalled { 121 t.Fatal("refresh should be called") 122 } 123 124 if run.PlanEmpty { 125 t.Fatal("plan should not be empty") 126 } 127 128 plan := testReadPlan(t, planPath) 129 for _, m := range plan.Diff.Modules { 130 for _, r := range m.Resources { 131 if !r.Destroy { 132 t.Fatalf("bad: %#v", r) 133 } 134 } 135 } 136 } 137 138 func TestLocal_planDestroyNoConfig(t *testing.T) { 139 b := TestLocal(t) 140 p := TestLocalProvider(t, b, "test") 141 terraform.TestStateFile(t, b.StatePath, testPlanState()) 142 143 outDir := testTempDir(t) 144 defer os.RemoveAll(outDir) 145 planPath := filepath.Join(outDir, "plan.tfplan") 146 147 op := testOperationPlan() 148 op.Destroy = true 149 op.PlanRefresh = true 150 op.Module = nil 151 op.PlanOutPath = planPath 152 153 run, err := b.Operation(context.Background(), op) 154 if err != nil { 155 t.Fatalf("bad: %s", err) 156 } 157 <-run.Done() 158 if run.Err != nil { 159 t.Fatalf("err: %s", err) 160 } 161 162 if !p.RefreshCalled { 163 t.Fatal("refresh should be called") 164 } 165 166 if run.PlanEmpty { 167 t.Fatal("plan should not be empty") 168 } 169 170 plan := testReadPlan(t, planPath) 171 for _, m := range plan.Diff.Modules { 172 for _, r := range m.Resources { 173 if !r.Destroy { 174 t.Fatalf("bad: %#v", r) 175 } 176 } 177 } 178 } 179 180 func TestLocal_planOutPathNoChange(t *testing.T) { 181 b := TestLocal(t) 182 TestLocalProvider(t, b, "test") 183 terraform.TestStateFile(t, b.StatePath, testPlanState()) 184 185 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 186 defer modCleanup() 187 188 outDir := testTempDir(t) 189 defer os.RemoveAll(outDir) 190 planPath := filepath.Join(outDir, "plan.tfplan") 191 192 op := testOperationPlan() 193 op.Module = mod 194 op.PlanOutPath = planPath 195 196 run, err := b.Operation(context.Background(), op) 197 if err != nil { 198 t.Fatalf("bad: %s", err) 199 } 200 <-run.Done() 201 if run.Err != nil { 202 t.Fatalf("err: %s", err) 203 } 204 205 plan := testReadPlan(t, planPath) 206 if !plan.Diff.Empty() { 207 t.Fatalf("expected empty plan to be written") 208 } 209 } 210 211 // TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a 212 // resource count is scaled out. The scaled out node needs to exist in the 213 // graph and run through a plan-style sequence during the refresh phase, but 214 // can conflate the count if its post-diff count hooks are not skipped. This 215 // checks to make sure the correct resource count is ultimately given to the 216 // UI. 217 func TestLocal_planScaleOutNoDupeCount(t *testing.T) { 218 b := TestLocal(t) 219 TestLocalProvider(t, b, "test") 220 state := &terraform.State{ 221 Version: 2, 222 Modules: []*terraform.ModuleState{ 223 &terraform.ModuleState{ 224 Path: []string{"root"}, 225 Resources: map[string]*terraform.ResourceState{ 226 "test_instance.foo.0": &terraform.ResourceState{ 227 Type: "test_instance", 228 Primary: &terraform.InstanceState{ 229 ID: "bar", 230 }, 231 }, 232 "test_instance.foo.1": &terraform.ResourceState{ 233 Type: "test_instance", 234 Primary: &terraform.InstanceState{ 235 ID: "bar", 236 }, 237 }, 238 }, 239 }, 240 }, 241 } 242 terraform.TestStateFile(t, b.StatePath, state) 243 244 actual := new(CountHook) 245 b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual) 246 247 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout") 248 defer modCleanup() 249 250 outDir := testTempDir(t) 251 defer os.RemoveAll(outDir) 252 253 op := testOperationPlan() 254 op.Module = mod 255 op.PlanRefresh = true 256 257 run, err := b.Operation(context.Background(), op) 258 if err != nil { 259 t.Fatalf("bad: %s", err) 260 } 261 <-run.Done() 262 if run.Err != nil { 263 t.Fatalf("err: %s", err) 264 } 265 266 expected := new(CountHook) 267 expected.ToAdd = 1 268 expected.ToChange = 0 269 expected.ToRemoveAndAdd = 0 270 expected.ToRemove = 0 271 272 if !reflect.DeepEqual(expected, actual) { 273 t.Fatalf("Expected %#v, got %#v instead.", 274 expected, actual) 275 } 276 } 277 278 func testOperationPlan() *backend.Operation { 279 return &backend.Operation{ 280 Type: backend.OperationTypePlan, 281 } 282 } 283 284 // testPlanState is just a common state that we use for testing refresh. 285 func testPlanState() *terraform.State { 286 return &terraform.State{ 287 Version: 2, 288 Modules: []*terraform.ModuleState{ 289 &terraform.ModuleState{ 290 Path: []string{"root"}, 291 Resources: map[string]*terraform.ResourceState{ 292 "test_instance.foo": &terraform.ResourceState{ 293 Type: "test_instance", 294 Primary: &terraform.InstanceState{ 295 ID: "bar", 296 }, 297 }, 298 }, 299 }, 300 }, 301 } 302 } 303 304 func testReadPlan(t *testing.T, path string) *terraform.Plan { 305 f, err := os.Open(path) 306 if err != nil { 307 t.Fatalf("err: %s", err) 308 } 309 defer f.Close() 310 311 p, err := terraform.ReadPlan(f) 312 if err != nil { 313 t.Fatalf("err: %s", err) 314 } 315 316 return p 317 }