diff --git a/internal/cloud/backend.go b/internal/cloud/backend.go index 6f64a9476c..a5ced6c12e 100644 --- a/internal/cloud/backend.go +++ b/internal/cloud/backend.go @@ -89,6 +89,10 @@ type Cloud struct { ignoreVersionConflict bool runningInAutomation bool + + // input stores the value of the -input flag, since it will be used + // to determine whether or not to ask the user for approval of a run. + input bool } var _ backend.Backend = (*Cloud)(nil) diff --git a/internal/cloud/backend_apply.go b/internal/cloud/backend_apply.go index 85c447e602..ff22dd5a90 100644 --- a/internal/cloud/backend_apply.go +++ b/internal/cloud/backend_apply.go @@ -100,7 +100,7 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backend.Operatio mustConfirm := (op.UIIn != nil && op.UIOut != nil) && !op.AutoApprove - if mustConfirm { + if mustConfirm && b.input { opts := &terraform.InputOpts{Id: "approve"} if op.PlanMode == plans.DestroyMode { @@ -117,6 +117,8 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backend.Operatio if err != nil && err != errRunApproved { return r, err } + } else if mustConfirm && !b.input { + return r, errApplyNeedsUIConfirmation } else { // If we don't need to ask for confirmation, insert a blank // line to separate the ouputs. diff --git a/internal/cloud/backend_cli.go b/internal/cloud/backend_cli.go index cd05496163..62eb834cac 100644 --- a/internal/cloud/backend_cli.go +++ b/internal/cloud/backend_cli.go @@ -16,6 +16,7 @@ func (b *Cloud) CLIInit(opts *backend.CLIOpts) error { b.CLIColor = opts.CLIColor b.ContextOpts = opts.ContextOpts b.runningInAutomation = opts.RunningInAutomation + b.input = opts.Input return nil } diff --git a/internal/cloud/backend_common.go b/internal/cloud/backend_common.go index 05fbadfba1..2f17ff5788 100644 --- a/internal/cloud/backend_common.go +++ b/internal/cloud/backend_common.go @@ -3,7 +3,6 @@ package cloud import ( "bufio" "context" - "errors" "fmt" "io" "math" @@ -17,14 +16,6 @@ import ( "github.com/hashicorp/terraform/internal/terraform" ) -var ( - errApplyDiscarded = errors.New("Apply discarded.") - errDestroyDiscarded = errors.New("Destroy discarded.") - errRunApproved = errors.New("approved using the UI or API") - errRunDiscarded = errors.New("discarded using the UI or API") - errRunOverridden = errors.New("overridden using the UI or API") -) - var ( backoffMin = 1000.0 backoffMax = 3000.0 @@ -388,6 +379,8 @@ func (b *Cloud) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Oper if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil { return generalError(fmt.Sprintf("Failed to override policy check.\n%s", runUrl), err) } + } else if !b.input { + return errPolicyOverrideNeedsUIConfirmation } else { opts := &terraform.InputOpts{ Id: "override", diff --git a/internal/cloud/e2e/apply_no_input_flag_test.go b/internal/cloud/e2e/apply_no_input_flag_test.go new file mode 100644 index 0000000000..1836f730b3 --- /dev/null +++ b/internal/cloud/e2e/apply_no_input_flag_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "testing" +) + +func Test_apply_no_input_flag(t *testing.T) { + t.Parallel() + skipIfMissingEnvVar(t) + + cases := testCases{ + "terraform apply with -input=false": { + operations: []operationSets{ + { + prep: func(t *testing.T, orgName, dir string) { + wsName := "new-workspace" + tfBlock := terraformConfigCloudBackendName(orgName, wsName) + writeMainTF(t, tfBlock, dir) + }, + commands: []tfCommand{ + { + command: []string{"init", "-input=false"}, + expectedCmdOutput: `Terraform Cloud has been successfully initialized`, + }, + { + command: []string{"apply", "-input=false"}, + expectedCmdOutput: `Cannot confirm apply due to -input=false. Please handle run confirmation in the UI.`, + expectError: true, + }, + }, + }, + }, + }, + "terraform apply with auto approve and -input=false": { + operations: []operationSets{ + { + prep: func(t *testing.T, orgName, dir string) { + wsName := "cloud-workspace" + tfBlock := terraformConfigCloudBackendName(orgName, wsName) + writeMainTF(t, tfBlock, dir) + }, + commands: []tfCommand{ + { + command: []string{"init", "-input=false"}, + expectedCmdOutput: `Terraform Cloud has been successfully initialized`, + }, + { + command: []string{"apply", "-auto-approve", "-input=false"}, + expectedCmdOutput: `Apply complete!`, + }, + }, + }, + }, + }, + } + + testRunner(t, cases, 1) +} diff --git a/internal/cloud/errors.go b/internal/cloud/errors.go index f387a37538..cf668516f3 100644 --- a/internal/cloud/errors.go +++ b/internal/cloud/errors.go @@ -1,6 +1,7 @@ package cloud import ( + "errors" "fmt" "strings" @@ -8,6 +9,18 @@ import ( "github.com/zclconf/go-cty/cty" ) +// String based errors +var ( + errApplyDiscarded = errors.New("Apply discarded.") + errDestroyDiscarded = errors.New("Destroy discarded.") + errRunApproved = errors.New("approved using the UI or API") + errRunDiscarded = errors.New("discarded using the UI or API") + errRunOverridden = errors.New("overridden using the UI or API") + errApplyNeedsUIConfirmation = errors.New("Cannot confirm apply due to -input=false. Please handle run confirmation in the UI.") + errPolicyOverrideNeedsUIConfirmation = errors.New("Cannot override soft failed policy checks when -input=false. Please open the run in the UI to override.") +) + +// Diagnostic error messages var ( invalidWorkspaceConfigMissingValues = tfdiags.AttributeValue( tfdiags.Error, diff --git a/internal/cloud/testing.go b/internal/cloud/testing.go index e5dd538066..4297540fca 100644 --- a/internal/cloud/testing.go +++ b/internal/cloud/testing.go @@ -154,6 +154,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Cloud, func()) { // Set local to a local test backend. b.local = testLocalBackend(t, b) + b.input = true ctx := context.Background()