From 67db9da00000bf8df72caf844bb3d8392cae96bd Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Fri, 5 Oct 2018 15:25:17 +0200 Subject: [PATCH] =?UTF-8?q?Add=20checks=20for=20all=20flags=20we=20current?= =?UTF-8?q?ly=20don=E2=80=99t=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For Plan only: -module-depth=n For Plan & Apply -parallelism=m -refresh=false -var “foo=bar” and -var-file=foo --- backend/backend.go | 6 +- backend/remote/backend.go | 6 +- backend/remote/backend_apply.go | 46 +++++++++++- backend/remote/backend_apply_test.go | 79 +++++++++++++++++++- backend/remote/backend_plan.go | 55 +++++++++++++- backend/remote/backend_plan_test.go | 105 ++++++++++++++++++++++++++- command/apply.go | 4 +- command/meta_backend.go | 2 + command/plan.go | 3 +- 9 files changed, 292 insertions(+), 14 deletions(-) diff --git a/backend/backend.go b/backend/backend.go index 14a32e8d5..7f9435e99 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -137,11 +137,13 @@ type Operation struct { // The options below are more self-explanatory and affect the runtime // behavior of the operation. + AutoApprove bool Destroy bool + DestroyForce bool + ModuleDepth int + Parallelism int Targets []string Variables map[string]interface{} - AutoApprove bool - DestroyForce bool // Input/output/control options. UIIn terraform.UIInput diff --git a/backend/remote/backend.go b/backend/remote/backend.go index d29ea06ca..8730904be 100644 --- a/backend/remote/backend.go +++ b/backend/remote/backend.go @@ -24,8 +24,10 @@ import ( ) const ( - defaultHostname = "app.terraform.io" - serviceID = "tfe.v2" + defaultHostname = "app.terraform.io" + defaultModuleDepth = -1 + defaultParallelism = 10 + serviceID = "tfe.v2" ) // Remote is an implementation of EnhancedBackend that performs all diff --git a/backend/remote/backend_apply.go b/backend/remote/backend_apply.go index df5994f56..ab5c1271b 100644 --- a/backend/remote/backend_apply.go +++ b/backend/remote/backend_apply.go @@ -31,14 +31,27 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati return nil, fmt.Errorf(strings.TrimSpace(applyErrVCSNotSupported)) } + if op.Parallelism != defaultParallelism { + return nil, fmt.Errorf(strings.TrimSpace(applyErrParallelismNotSupported)) + } + if op.Plan != nil { return nil, fmt.Errorf(strings.TrimSpace(applyErrPlanNotSupported)) } + if !op.PlanRefresh { + return nil, fmt.Errorf(strings.TrimSpace(applyErrNoRefreshNotSupported)) + } + if op.Targets != nil { return nil, fmt.Errorf(strings.TrimSpace(applyErrTargetsNotSupported)) } + if op.Variables != nil { + return nil, fmt.Errorf(strings.TrimSpace( + fmt.Sprintf(applyErrVariablesNotSupported, b.hostname, b.organization, op.Workspace))) + } + if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy { return nil, fmt.Errorf(strings.TrimSpace(applyErrNoConfig)) } @@ -96,7 +109,7 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati } } return r, fmt.Errorf(strings.TrimSpace( - fmt.Sprint(applyErrNoApplyRights, b.hostname, b.organization, op.Workspace))) + fmt.Sprintf(applyErrNoApplyRights, b.hostname, b.organization, op.Workspace))) } hasUI := op.UIIn != nil && op.UIOut != nil @@ -286,11 +299,25 @@ A workspace that is connected to a VCS requires the VCS-driven workflow to ensure that the VCS remains the single source of truth. ` +const applyErrParallelismNotSupported = ` +Custom parallelism values are currently not supported! + +The "remote" backend does not support setting a custom parallelism +value at this time. +` + const applyErrPlanNotSupported = ` Applying a saved plan is currently not supported! -The "remote" backend currently requires configuration to be present -and does not accept an existing saved plan as an argument at this time. +The "remote" backend currently requires configuration to be present and +does not accept an existing saved plan as an argument at this time. +` + +const applyErrNoRefreshNotSupported = ` +Applying without refresh is currently not supported! + +Currently the "remote" backend will always do an in-memory refresh of +the Terraform state prior to generating the plan. ` const applyErrTargetsNotSupported = ` @@ -299,6 +326,19 @@ Resource targeting is currently not supported! The "remote" backend does not support resource targeting at this time. ` +const applyErrVariablesNotSupported = ` +Run variables are currently not supported! + +The "remote" backend does not support setting run variables at this time. +Currently the only to way to pass variables to the remote backend is by +creating a '*.auto.tfvars' variables file. This file will automatically +be loaded by the "remote" backend when the workspace is configured to use +Terraform v0.10.0 or later. + +Additionally you can also set variables on the workspace in the web UI: +https://%s/app/%s/%s/variables +` + const applyErrNoConfig = ` No configuration files found! diff --git a/backend/remote/backend_apply_test.go b/backend/remote/backend_apply_test.go index 998e323bc..2dbf00fdd 100644 --- a/backend/remote/backend_apply_test.go +++ b/backend/remote/backend_apply_test.go @@ -18,7 +18,9 @@ import ( func testOperationApply() *backend.Operation { return &backend.Operation{ - Type: backend.OperationTypeApply, + Parallelism: defaultParallelism, + PlanRefresh: true, + Type: backend.OperationTypeApply, } } @@ -135,6 +137,31 @@ func TestRemote_applyWithVCS(t *testing.T) { } } +func TestRemote_applyWithParallelism(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") + defer modCleanup() + + op := testOperationApply() + op.Module = mod + op.Parallelism = 3 + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected an apply error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") { + t.Fatalf("expected a parallelism error, got: %v", run.Err) + } +} + func TestRemote_applyWithPlan(t *testing.T) { b := testBackendDefault(t) @@ -160,6 +187,31 @@ func TestRemote_applyWithPlan(t *testing.T) { } } +func TestRemote_applyWithoutRefresh(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") + defer modCleanup() + + op := testOperationApply() + op.Module = mod + op.PlanRefresh = false + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected an apply error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "refresh is currently not supported") { + t.Fatalf("expected a refresh error, got: %v", run.Err) + } +} + func TestRemote_applyWithTarget(t *testing.T) { b := testBackendDefault(t) @@ -185,6 +237,31 @@ func TestRemote_applyWithTarget(t *testing.T) { } } +func TestRemote_applyWithVariables(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") + defer modCleanup() + + op := testOperationApply() + op.Module = mod + op.Variables = map[string]interface{}{"foo": "bar"} + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected an apply error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "variables are currently not supported") { + t.Fatalf("expected a variables error, got: %v", run.Err) + } +} + func TestRemote_applyNoConfig(t *testing.T) { b := testBackendDefault(t) diff --git a/backend/remote/backend_plan.go b/backend/remote/backend_plan.go index df7d76fac..10c5121d0 100644 --- a/backend/remote/backend_plan.go +++ b/backend/remote/backend_plan.go @@ -30,6 +30,14 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio return nil, fmt.Errorf(strings.TrimSpace(fmt.Sprintf(planErrNoQueueRunRights))) } + if op.ModuleDepth != defaultModuleDepth { + return nil, fmt.Errorf(strings.TrimSpace(planErrModuleDepthNotSupported)) + } + + if op.Parallelism != defaultParallelism { + return nil, fmt.Errorf(strings.TrimSpace(planErrParallelismNotSupported)) + } + if op.Plan != nil { return nil, fmt.Errorf(strings.TrimSpace(planErrPlanNotSupported)) } @@ -38,10 +46,19 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio return nil, fmt.Errorf(strings.TrimSpace(planErrOutPathNotSupported)) } + if !op.PlanRefresh { + return nil, fmt.Errorf(strings.TrimSpace(planErrNoRefreshNotSupported)) + } + if op.Targets != nil { return nil, fmt.Errorf(strings.TrimSpace(planErrTargetsNotSupported)) } + if op.Variables != nil { + return nil, fmt.Errorf(strings.TrimSpace( + fmt.Sprintf(planErrVariablesNotSupported, b.hostname, b.organization, op.Workspace))) + } + if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy { return nil, fmt.Errorf(strings.TrimSpace(planErrNoConfig)) } @@ -203,11 +220,25 @@ Insufficient rights to generate a plan! to generate plans, at least plan permissions on the workspace are required.[reset] ` +const planErrModuleDepthNotSupported = ` +Custom module depths are currently not supported! + +The "remote" backend does not support setting a custom module +depth at this time. +` + +const planErrParallelismNotSupported = ` +Custom parallelism values are currently not supported! + +The "remote" backend does not support setting a custom parallelism +value at this time. +` + const planErrPlanNotSupported = ` Displaying a saved plan is currently not supported! -The "remote" backend currently requires configuration to be present -and does not accept an existing saved plan as an argument at this time. +The "remote" backend currently requires configuration to be present and +does not accept an existing saved plan as an argument at this time. ` const planErrOutPathNotSupported = ` @@ -217,12 +248,32 @@ The "remote" backend does not support saving the generated execution plan locally at this time. ` +const planErrNoRefreshNotSupported = ` +Planning without refresh is currently not supported! + +Currently the "remote" backend will always do an in-memory refresh of +the Terraform state prior to generating the plan. +` + const planErrTargetsNotSupported = ` Resource targeting is currently not supported! The "remote" backend does not support resource targeting at this time. ` +const planErrVariablesNotSupported = ` +Run variables are currently not supported! + +The "remote" backend does not support setting run variables at this time. +Currently the only to way to pass variables to the remote backend is by +creating a '*.auto.tfvars' variables file. This file will automatically +be loaded by the "remote" backend when the workspace is configured to use +Terraform v0.10.0 or later. + +Additionally you can also set variables on the workspace in the web UI: +https://%s/app/%s/%s/variables +` + const planErrNoConfig = ` No configuration files found! diff --git a/backend/remote/backend_plan_test.go b/backend/remote/backend_plan_test.go index 3718b6045..68cd6431b 100644 --- a/backend/remote/backend_plan_test.go +++ b/backend/remote/backend_plan_test.go @@ -18,7 +18,10 @@ import ( func testOperationPlan() *backend.Operation { return &backend.Operation{ - Type: backend.OperationTypePlan, + ModuleDepth: defaultModuleDepth, + Parallelism: defaultParallelism, + PlanRefresh: true, + Type: backend.OperationTypePlan, } } @@ -85,6 +88,56 @@ func TestRemote_planWithoutPermissions(t *testing.T) { } } +func TestRemote_planWithModuleDepth(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") + defer modCleanup() + + op := testOperationPlan() + op.Module = mod + op.ModuleDepth = 1 + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected a plan error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "module depths are currently not supported") { + t.Fatalf("expected a module depth error, got: %v", run.Err) + } +} + +func TestRemote_planWithParallelism(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") + defer modCleanup() + + op := testOperationPlan() + op.Module = mod + op.Parallelism = 3 + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected a plan error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") { + t.Fatalf("expected a parallelism error, got: %v", run.Err) + } +} + func TestRemote_planWithPlan(t *testing.T) { b := testBackendDefault(t) @@ -135,6 +188,31 @@ func TestRemote_planWithPath(t *testing.T) { } } +func TestRemote_planWithoutRefresh(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") + defer modCleanup() + + op := testOperationPlan() + op.Module = mod + op.PlanRefresh = false + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected a plan error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "refresh is currently not supported") { + t.Fatalf("expected a refresh error, got: %v", run.Err) + } +} + func TestRemote_planWithTarget(t *testing.T) { b := testBackendDefault(t) @@ -160,6 +238,31 @@ func TestRemote_planWithTarget(t *testing.T) { } } +func TestRemote_planWithVariables(t *testing.T) { + b := testBackendDefault(t) + + mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") + defer modCleanup() + + op := testOperationPlan() + op.Module = mod + op.Variables = map[string]interface{}{"foo": "bar"} + op.Workspace = backend.DefaultStateName + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("error starting operation: %v", err) + } + <-run.Done() + + if run.Err == nil { + t.Fatalf("expected an plan error, got: %v", run.Err) + } + if !strings.Contains(run.Err.Error(), "variables are currently not supported") { + t.Fatalf("expected a variables error, got: %v", run.Err) + } +} + func TestRemote_planNoConfig(t *testing.T) { b := testBackendDefault(t) diff --git a/command/apply.go b/command/apply.go index 8d1736a51..8c1786619 100644 --- a/command/apply.go +++ b/command/apply.go @@ -147,13 +147,13 @@ func (c *ApplyCommand) Run(args []string) int { // Build the operation opReq := c.Operation() + opReq.AutoApprove = autoApprove opReq.Destroy = c.Destroy + opReq.DestroyForce = destroyForce opReq.Module = mod opReq.Plan = plan opReq.PlanRefresh = refresh opReq.Type = backend.OperationTypeApply - opReq.AutoApprove = autoApprove - opReq.DestroyForce = destroyForce op, err := c.RunOperation(b, opReq) if err != nil { diff --git a/command/meta_backend.go b/command/meta_backend.go index 7d46260e1..067f5cc8c 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -167,9 +167,11 @@ func (m *Meta) IsLocalBackend(b backend.Backend) bool { func (m *Meta) Operation() *backend.Operation { return &backend.Operation{ PlanOutBackend: m.backendState, + Parallelism: m.parallelism, Targets: m.targets, UIIn: m.UIInput(), UIOut: m.Ui, + Variables: m.variables, Workspace: m.Workspace(), LockState: m.stateLock, StateLockTimeout: m.stateLockTimeout, diff --git a/command/plan.go b/command/plan.go index 28bcbcb33..0db56fbe4 100644 --- a/command/plan.go +++ b/command/plan.go @@ -100,9 +100,10 @@ func (c *PlanCommand) Run(args []string) int { opReq := c.Operation() opReq.Destroy = destroy opReq.Module = mod + opReq.ModuleDepth = moduleDepth opReq.Plan = plan - opReq.PlanRefresh = refresh opReq.PlanOutPath = outPath + opReq.PlanRefresh = refresh opReq.Type = backend.OperationTypePlan // Perform the operation