github.com/pdecat/terraform@v0.11.9-beta1/backend/remote/backend_plan_test.go (about) 1 package remote 2 3 import ( 4 "context" 5 "os" 6 "os/signal" 7 "strings" 8 "syscall" 9 "testing" 10 "time" 11 12 tfe "github.com/hashicorp/go-tfe" 13 "github.com/hashicorp/terraform/backend" 14 "github.com/hashicorp/terraform/config/module" 15 "github.com/hashicorp/terraform/terraform" 16 "github.com/mitchellh/cli" 17 ) 18 19 func testOperationPlan() *backend.Operation { 20 return &backend.Operation{ 21 ModuleDepth: defaultModuleDepth, 22 Parallelism: defaultParallelism, 23 PlanRefresh: true, 24 Type: backend.OperationTypePlan, 25 } 26 } 27 28 func TestRemote_planBasic(t *testing.T) { 29 b := testBackendDefault(t) 30 31 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 32 defer modCleanup() 33 34 op := testOperationPlan() 35 op.Module = mod 36 op.Workspace = backend.DefaultStateName 37 38 run, err := b.Operation(context.Background(), op) 39 if err != nil { 40 t.Fatalf("error starting operation: %v", err) 41 } 42 43 <-run.Done() 44 if run.Err != nil { 45 t.Fatalf("error running operation: %v", run.Err) 46 } 47 if run.PlanEmpty { 48 t.Fatal("expected a non-empty plan") 49 } 50 51 output := b.CLI.(*cli.MockUi).OutputWriter.String() 52 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 53 t.Fatalf("missing plan summery in output: %s", output) 54 } 55 } 56 57 func TestRemote_planWithoutPermissions(t *testing.T) { 58 b := testBackendNoDefault(t) 59 60 // Create a named workspace without permissions. 61 w, err := b.client.Workspaces.Create( 62 context.Background(), 63 b.organization, 64 tfe.WorkspaceCreateOptions{ 65 Name: tfe.String(b.prefix + "prod"), 66 }, 67 ) 68 if err != nil { 69 t.Fatalf("error creating named workspace: %v", err) 70 } 71 w.Permissions.CanQueueRun = false 72 73 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 74 defer modCleanup() 75 76 op := testOperationPlan() 77 op.Module = mod 78 op.Workspace = "prod" 79 80 run, err := b.Operation(context.Background(), op) 81 if err != nil { 82 t.Fatalf("error starting operation: %v", err) 83 } 84 85 <-run.Done() 86 if run.Err == nil { 87 t.Fatalf("expected a plan error, got: %v", run.Err) 88 } 89 if !strings.Contains(run.Err.Error(), "insufficient rights to generate a plan") { 90 t.Fatalf("expected a permissions error, got: %v", run.Err) 91 } 92 } 93 94 func TestRemote_planWithModuleDepth(t *testing.T) { 95 b := testBackendDefault(t) 96 97 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 98 defer modCleanup() 99 100 op := testOperationPlan() 101 op.Module = mod 102 op.ModuleDepth = 1 103 op.Workspace = backend.DefaultStateName 104 105 run, err := b.Operation(context.Background(), op) 106 if err != nil { 107 t.Fatalf("error starting operation: %v", err) 108 } 109 110 <-run.Done() 111 if run.Err == nil { 112 t.Fatalf("expected a plan error, got: %v", run.Err) 113 } 114 if !strings.Contains(run.Err.Error(), "module depths are currently not supported") { 115 t.Fatalf("expected a module depth error, got: %v", run.Err) 116 } 117 } 118 119 func TestRemote_planWithParallelism(t *testing.T) { 120 b := testBackendDefault(t) 121 122 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 123 defer modCleanup() 124 125 op := testOperationPlan() 126 op.Module = mod 127 op.Parallelism = 3 128 op.Workspace = backend.DefaultStateName 129 130 run, err := b.Operation(context.Background(), op) 131 if err != nil { 132 t.Fatalf("error starting operation: %v", err) 133 } 134 135 <-run.Done() 136 if run.Err == nil { 137 t.Fatalf("expected a plan error, got: %v", run.Err) 138 } 139 if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") { 140 t.Fatalf("expected a parallelism error, got: %v", run.Err) 141 } 142 } 143 144 func TestRemote_planWithPlan(t *testing.T) { 145 b := testBackendDefault(t) 146 147 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 148 defer modCleanup() 149 150 op := testOperationPlan() 151 op.Module = mod 152 op.Plan = &terraform.Plan{} 153 op.Workspace = backend.DefaultStateName 154 155 run, err := b.Operation(context.Background(), op) 156 if err != nil { 157 t.Fatalf("error starting operation: %v", err) 158 } 159 160 <-run.Done() 161 if run.Err == nil { 162 t.Fatalf("expected a plan error, got: %v", run.Err) 163 } 164 if !run.PlanEmpty { 165 t.Fatalf("expected plan to be empty") 166 } 167 if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") { 168 t.Fatalf("expected a saved plan error, got: %v", run.Err) 169 } 170 } 171 172 func TestRemote_planWithPath(t *testing.T) { 173 b := testBackendDefault(t) 174 175 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 176 defer modCleanup() 177 178 op := testOperationPlan() 179 op.Module = mod 180 op.PlanOutPath = "./test-fixtures/plan" 181 op.Workspace = backend.DefaultStateName 182 183 run, err := b.Operation(context.Background(), op) 184 if err != nil { 185 t.Fatalf("error starting operation: %v", err) 186 } 187 188 <-run.Done() 189 if run.Err == nil { 190 t.Fatalf("expected a plan error, got: %v", run.Err) 191 } 192 if !run.PlanEmpty { 193 t.Fatalf("expected plan to be empty") 194 } 195 if !strings.Contains(run.Err.Error(), "generated plan is currently not supported") { 196 t.Fatalf("expected a generated plan error, got: %v", run.Err) 197 } 198 } 199 200 func TestRemote_planWithoutRefresh(t *testing.T) { 201 b := testBackendDefault(t) 202 203 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 204 defer modCleanup() 205 206 op := testOperationPlan() 207 op.Module = mod 208 op.PlanRefresh = false 209 op.Workspace = backend.DefaultStateName 210 211 run, err := b.Operation(context.Background(), op) 212 if err != nil { 213 t.Fatalf("error starting operation: %v", err) 214 } 215 216 <-run.Done() 217 if run.Err == nil { 218 t.Fatalf("expected a plan error, got: %v", run.Err) 219 } 220 if !strings.Contains(run.Err.Error(), "refresh is currently not supported") { 221 t.Fatalf("expected a refresh error, got: %v", run.Err) 222 } 223 } 224 225 func TestRemote_planWithTarget(t *testing.T) { 226 b := testBackendDefault(t) 227 228 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 229 defer modCleanup() 230 231 op := testOperationPlan() 232 op.Module = mod 233 op.Targets = []string{"null_resource.foo"} 234 op.Workspace = backend.DefaultStateName 235 236 run, err := b.Operation(context.Background(), op) 237 if err != nil { 238 t.Fatalf("error starting operation: %v", err) 239 } 240 241 <-run.Done() 242 if run.Err == nil { 243 t.Fatalf("expected a plan error, got: %v", run.Err) 244 } 245 if !run.PlanEmpty { 246 t.Fatalf("expected plan to be empty") 247 } 248 if !strings.Contains(run.Err.Error(), "targeting is currently not supported") { 249 t.Fatalf("expected a targeting error, got: %v", run.Err) 250 } 251 } 252 253 func TestRemote_planWithVariables(t *testing.T) { 254 b := testBackendDefault(t) 255 256 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 257 defer modCleanup() 258 259 op := testOperationPlan() 260 op.Module = mod 261 op.Variables = map[string]interface{}{"foo": "bar"} 262 op.Workspace = backend.DefaultStateName 263 264 run, err := b.Operation(context.Background(), op) 265 if err != nil { 266 t.Fatalf("error starting operation: %v", err) 267 } 268 269 <-run.Done() 270 if run.Err == nil { 271 t.Fatalf("expected an plan error, got: %v", run.Err) 272 } 273 if !strings.Contains(run.Err.Error(), "variables are currently not supported") { 274 t.Fatalf("expected a variables error, got: %v", run.Err) 275 } 276 } 277 278 func TestRemote_planNoConfig(t *testing.T) { 279 b := testBackendDefault(t) 280 281 op := testOperationPlan() 282 op.Module = nil 283 op.Workspace = backend.DefaultStateName 284 285 run, err := b.Operation(context.Background(), op) 286 if err != nil { 287 t.Fatalf("error starting operation: %v", err) 288 } 289 290 <-run.Done() 291 if run.Err == nil { 292 t.Fatalf("expected a plan error, got: %v", run.Err) 293 } 294 if !run.PlanEmpty { 295 t.Fatalf("expected plan to be empty") 296 } 297 if !strings.Contains(run.Err.Error(), "configuration files found") { 298 t.Fatalf("expected configuration files error, got: %v", run.Err) 299 } 300 } 301 302 func TestRemote_planLockTimeout(t *testing.T) { 303 b := testBackendDefault(t) 304 ctx := context.Background() 305 306 // Retrieve the workspace used to run this operation in. 307 w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace) 308 if err != nil { 309 t.Fatalf("error retrieving workspace: %v", err) 310 } 311 312 // Create a new configuration version. 313 c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{}) 314 if err != nil { 315 t.Fatalf("error creating configuration version: %v", err) 316 } 317 318 // Create a pending run to block this run. 319 _, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{ 320 ConfigurationVersion: c, 321 Workspace: w, 322 }) 323 if err != nil { 324 t.Fatalf("error creating pending run: %v", err) 325 } 326 327 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 328 defer modCleanup() 329 330 input := testInput(t, map[string]string{ 331 "cancel": "yes", 332 "approve": "yes", 333 }) 334 335 op := testOperationPlan() 336 op.StateLockTimeout = 5 * time.Second 337 op.Module = mod 338 op.UIIn = input 339 op.UIOut = b.CLI 340 op.Workspace = backend.DefaultStateName 341 342 _, err = b.Operation(context.Background(), op) 343 if err != nil { 344 t.Fatalf("error starting operation: %v", err) 345 } 346 347 sigint := make(chan os.Signal, 1) 348 signal.Notify(sigint, syscall.SIGINT) 349 select { 350 case <-sigint: 351 // Stop redirecting SIGINT signals. 352 signal.Stop(sigint) 353 case <-time.After(10 * time.Second): 354 t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds") 355 } 356 357 if len(input.answers) != 2 { 358 t.Fatalf("expected unused answers, got: %v", input.answers) 359 } 360 361 output := b.CLI.(*cli.MockUi).OutputWriter.String() 362 if !strings.Contains(output, "Lock timeout exceeded") { 363 t.Fatalf("missing lock timout error in output: %s", output) 364 } 365 if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 366 t.Fatalf("unexpected plan summery in output: %s", output) 367 } 368 } 369 370 func TestRemote_planDestroy(t *testing.T) { 371 b := testBackendDefault(t) 372 373 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 374 defer modCleanup() 375 376 op := testOperationPlan() 377 op.Destroy = true 378 op.Module = mod 379 op.Workspace = backend.DefaultStateName 380 381 run, err := b.Operation(context.Background(), op) 382 if err != nil { 383 t.Fatalf("error starting operation: %v", err) 384 } 385 386 <-run.Done() 387 if run.Err != nil { 388 t.Fatalf("unexpected plan error: %v", run.Err) 389 } 390 if run.PlanEmpty { 391 t.Fatalf("expected a non-empty plan") 392 } 393 } 394 395 func TestRemote_planDestroyNoConfig(t *testing.T) { 396 b := testBackendDefault(t) 397 398 op := testOperationPlan() 399 op.Destroy = true 400 op.Module = nil 401 op.Workspace = backend.DefaultStateName 402 403 run, err := b.Operation(context.Background(), op) 404 if err != nil { 405 t.Fatalf("error starting operation: %v", err) 406 } 407 408 <-run.Done() 409 if run.Err != nil { 410 t.Fatalf("unexpected plan error: %v", run.Err) 411 } 412 if run.PlanEmpty { 413 t.Fatalf("expected a non-empty plan") 414 } 415 } 416 417 func TestRemote_planWithWorkingDirectory(t *testing.T) { 418 b := testBackendDefault(t) 419 420 options := tfe.WorkspaceUpdateOptions{ 421 WorkingDirectory: tfe.String("terraform"), 422 } 423 424 // Configure the workspace to use a custom working direcrtory. 425 _, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options) 426 if err != nil { 427 t.Fatalf("error configuring working directory: %v", err) 428 } 429 430 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-working-directory/terraform") 431 defer modCleanup() 432 433 op := testOperationPlan() 434 op.Module = mod 435 op.Workspace = backend.DefaultStateName 436 437 run, err := b.Operation(context.Background(), op) 438 if err != nil { 439 t.Fatalf("error starting operation: %v", err) 440 } 441 442 <-run.Done() 443 if run.Err != nil { 444 t.Fatalf("error running operation: %v", run.Err) 445 } 446 if run.PlanEmpty { 447 t.Fatalf("expected a non-empty plan") 448 } 449 450 output := b.CLI.(*cli.MockUi).OutputWriter.String() 451 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 452 t.Fatalf("missing plan summery in output: %s", output) 453 } 454 } 455 456 func TestRemote_planWithRemoteError(t *testing.T) { 457 b := testBackendDefault(t) 458 459 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-error") 460 defer modCleanup() 461 462 op := testOperationPlan() 463 op.Module = mod 464 op.Workspace = backend.DefaultStateName 465 466 run, err := b.Operation(context.Background(), op) 467 if err != nil { 468 t.Fatalf("error starting operation: %v", err) 469 } 470 471 <-run.Done() 472 if run.Err != nil { 473 t.Fatalf("error running operation: %v", run.Err) 474 } 475 if run.ExitCode != 1 { 476 t.Fatalf("expected exit code 1, got %d", run.ExitCode) 477 } 478 479 output := b.CLI.(*cli.MockUi).OutputWriter.String() 480 if !strings.Contains(output, "null_resource.foo: 1 error") { 481 t.Fatalf("missing plan error in output: %s", output) 482 } 483 }