mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-04-30 16:03:37 -04:00
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:
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
58
internal/cloud/e2e/apply_no_input_flag_test.go
Normal file
58
internal/cloud/e2e/apply_no_input_flag_test.go
Normal 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)
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user