Handle -input=false in cloud integration

For non-interactive contexts, Terraform is typically executed with the flag -input=false.
However for runs that are not set to auto approve, the cloud integration will prompt a user for
approval input even with input being set to false. This commit enables the cloud integration to know
the value of the input flag and use it to determine whether or not to ask the user for input.

If -input is set to false and the run cannot be auto approved, the cloud integration will throw an error
stating run confirmation can no longer be handled in the CLI and that they must do so through the browser.
This commit is contained in:
Sebastian Rivera
2022-04-13 13:34:11 -04:00
parent c557078704
commit 9d7fdbea2d
7 changed files with 82 additions and 10 deletions

View File

@@ -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)

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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()