Allow enhanced backends to pass custom exit codes
In some cases this is needed to keep the UX clean and to make sure any remote exit codes are passed through to the local process. The most obvious example for this is when using the "remote" backend. This backend runs Terraform remotely and stream the output back to the local terminal. When an error occurs during the remote execution, all the needed error information will already be in the streamed output. So if we then return an error ourselves, users will get the same errors twice. By allowing the backend to specify the correct exit code, the UX remains the same while preserving the correct exit codes.
This commit is contained in:
parent
e0b7475984
commit
b1fdbd7db8
|
@ -188,6 +188,10 @@ type RunningOperation struct {
|
||||||
// the operation has completed.
|
// the operation has completed.
|
||||||
Err error
|
Err error
|
||||||
|
|
||||||
|
// ExitCode can be used to set a custom exit code. This enables enhanced
|
||||||
|
// backends to set specific exit codes that miror any remote exit codes.
|
||||||
|
ExitCode int
|
||||||
|
|
||||||
// PlanEmpty is populated after a Plan operation completes without error
|
// PlanEmpty is populated after a Plan operation completes without error
|
||||||
// to note whether a plan is empty or has changes.
|
// to note whether a plan is empty or has changes.
|
||||||
PlanEmpty bool
|
PlanEmpty bool
|
||||||
|
|
|
@ -417,6 +417,7 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend
|
||||||
runningCtx, done := context.WithCancel(context.Background())
|
runningCtx, done := context.WithCancel(context.Background())
|
||||||
runningOp := &backend.RunningOperation{
|
runningOp := &backend.RunningOperation{
|
||||||
Context: runningCtx,
|
Context: runningCtx,
|
||||||
|
PlanEmpty: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopCtx wraps the context passed in, and is used to signal a graceful Stop.
|
// stopCtx wraps the context passed in, and is used to signal a graceful Stop.
|
||||||
|
@ -436,13 +437,30 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend
|
||||||
|
|
||||||
defer b.opLock.Unlock()
|
defer b.opLock.Unlock()
|
||||||
|
|
||||||
r, err := f(stopCtx, cancelCtx, op)
|
r, opErr := f(stopCtx, cancelCtx, op)
|
||||||
if err != nil && err != context.Canceled {
|
if opErr != nil && opErr != context.Canceled {
|
||||||
runningOp.Err = err
|
runningOp.Err = opErr
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil && err == context.Canceled {
|
if r != nil {
|
||||||
runningOp.Err = b.cancel(cancelCtx, op, r.ID)
|
// Retrieve the run to get its current status.
|
||||||
|
r, err := b.client.Runs.Read(cancelCtx, r.ID)
|
||||||
|
if err != nil {
|
||||||
|
runningOp.Err = generalError("error retrieving run", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record if there are any changes.
|
||||||
|
runningOp.PlanEmpty = !r.HasChanges
|
||||||
|
|
||||||
|
if opErr == context.Canceled {
|
||||||
|
runningOp.Err = b.cancel(cancelCtx, op, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runningOp.Err == nil && r.Status == tfe.RunErrored {
|
||||||
|
runningOp.ExitCode = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -450,13 +468,7 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend
|
||||||
return runningOp, nil
|
return runningOp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Remote) cancel(cancelCtx context.Context, op *backend.Operation, runID string) error {
|
func (b *Remote) cancel(cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error {
|
||||||
// Retrieve the run to get its current status.
|
|
||||||
r, err := b.client.Runs.Read(cancelCtx, runID)
|
|
||||||
if err != nil {
|
|
||||||
return generalError("error cancelling run", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Status == tfe.RunPending && r.Actions.IsCancelable {
|
if r.Status == tfe.RunPending && r.Actions.IsCancelable {
|
||||||
// Only ask if the remote operation should be canceled
|
// Only ask if the remote operation should be canceled
|
||||||
// if the auto approve flag is not set.
|
// if the auto approve flag is not set.
|
||||||
|
@ -483,7 +495,7 @@ func (b *Remote) cancel(cancelCtx context.Context, op *backend.Operation, runID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to cancel the remote operation.
|
// Try to cancel the remote operation.
|
||||||
err = b.client.Runs.Cancel(cancelCtx, r.ID, tfe.RunCancelOptions{})
|
err := b.client.Runs.Cancel(cancelCtx, r.ID, tfe.RunCancelOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return generalError("error cancelling run", err)
|
return generalError("error cancelling run", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,9 @@ func TestRemote_applyBasic(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) > 0 {
|
if len(input.answers) > 0 {
|
||||||
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
||||||
|
@ -132,6 +135,9 @@ func TestRemote_applyWithVCS(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "not allowed for workspaces with a VCS") {
|
if !strings.Contains(run.Err.Error(), "not allowed for workspaces with a VCS") {
|
||||||
t.Fatalf("expected a VCS error, got: %v", run.Err)
|
t.Fatalf("expected a VCS error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -182,6 +188,9 @@ func TestRemote_applyWithPlan(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") {
|
if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") {
|
||||||
t.Fatalf("expected a saved plan error, got: %v", run.Err)
|
t.Fatalf("expected a saved plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -232,6 +241,9 @@ func TestRemote_applyWithTarget(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "targeting is currently not supported") {
|
if !strings.Contains(run.Err.Error(), "targeting is currently not supported") {
|
||||||
t.Fatalf("expected a targeting error, got: %v", run.Err)
|
t.Fatalf("expected a targeting error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -278,6 +290,9 @@ func TestRemote_applyNoConfig(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "configuration files found") {
|
if !strings.Contains(run.Err.Error(), "configuration files found") {
|
||||||
t.Fatalf("expected configuration files error, got: %v", run.Err)
|
t.Fatalf("expected configuration files error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -302,6 +317,9 @@ func TestRemote_applyNoChanges(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
|
|
||||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
|
if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
|
||||||
|
@ -334,6 +352,9 @@ func TestRemote_applyNoApprove(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "Apply discarded") {
|
if !strings.Contains(run.Err.Error(), "Apply discarded") {
|
||||||
t.Fatalf("expected an apply discarded error, got: %v", run.Err)
|
t.Fatalf("expected an apply discarded error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -368,6 +389,9 @@ func TestRemote_applyAutoApprove(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) != 1 {
|
if len(input.answers) != 1 {
|
||||||
t.Fatalf("expected an unused answer, got: %v", input.answers)
|
t.Fatalf("expected an unused answer, got: %v", input.answers)
|
||||||
|
@ -479,6 +503,9 @@ func TestRemote_applyDestroy(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) > 0 {
|
if len(input.answers) > 0 {
|
||||||
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
||||||
|
@ -516,6 +543,9 @@ func TestRemote_applyDestroyNoConfig(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("unexpected apply error: %v", run.Err)
|
t.Fatalf("unexpected apply error: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) > 0 {
|
if len(input.answers) > 0 {
|
||||||
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
||||||
|
@ -547,6 +577,9 @@ func TestRemote_applyPolicyPass(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) > 0 {
|
if len(input.answers) > 0 {
|
||||||
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
||||||
|
@ -589,6 +622,9 @@ func TestRemote_applyPolicyHardFail(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "hard failed") {
|
if !strings.Contains(run.Err.Error(), "hard failed") {
|
||||||
t.Fatalf("expected a policy check error, got: %v", run.Err)
|
t.Fatalf("expected a policy check error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -634,6 +670,9 @@ func TestRemote_applyPolicySoftFail(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) > 0 {
|
if len(input.answers) > 0 {
|
||||||
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
||||||
|
@ -677,6 +716,9 @@ func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
if len(input.answers) > 0 {
|
if len(input.answers) > 0 {
|
||||||
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
t.Fatalf("expected no unused answers, got: %v", input.answers)
|
||||||
|
@ -693,3 +735,32 @@ func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
|
||||||
t.Fatalf("missing apply summery in output: %s", output)
|
t.Fatalf("missing apply summery in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemote_applyWithRemoteError(t *testing.T) {
|
||||||
|
b := testBackendDefault(t)
|
||||||
|
|
||||||
|
mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-with-error")
|
||||||
|
defer modCleanup()
|
||||||
|
|
||||||
|
op := testOperationApply()
|
||||||
|
op.Module = mod
|
||||||
|
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("error running operation: %v", run.Err)
|
||||||
|
}
|
||||||
|
if run.ExitCode != 1 {
|
||||||
|
t.Fatalf("expected exit code 1, got %d", run.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
|
if !strings.Contains(output, "null_resource.foo: 1 error") {
|
||||||
|
t.Fatalf("missing apply error in output: %s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -609,9 +609,9 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
|
||||||
|
|
||||||
r := &tfe.Run{
|
r := &tfe.Run{
|
||||||
ID: generateID("run-"),
|
ID: generateID("run-"),
|
||||||
Actions: &tfe.RunActions{},
|
Actions: &tfe.RunActions{IsCancelable: true},
|
||||||
Apply: a,
|
Apply: a,
|
||||||
HasChanges: true,
|
HasChanges: false,
|
||||||
Permissions: &tfe.RunPermissions{},
|
Permissions: &tfe.RunPermissions{},
|
||||||
Plan: p,
|
Plan: p,
|
||||||
Status: tfe.RunPending,
|
Status: tfe.RunPending,
|
||||||
|
@ -625,14 +625,6 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
|
||||||
r.IsDestroy = *options.IsDestroy
|
r.IsDestroy = *options.IsDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
logs, _ := ioutil.ReadFile(m.client.Plans.logs[p.LogReadURL])
|
|
||||||
if r.IsDestroy || !bytes.Contains(logs, []byte("No changes. Infrastructure is up-to-date.")) {
|
|
||||||
r.Actions.IsCancelable = true
|
|
||||||
r.Actions.IsConfirmable = true
|
|
||||||
r.HasChanges = true
|
|
||||||
r.Permissions.CanApply = true
|
|
||||||
}
|
|
||||||
|
|
||||||
m.runs[r.ID] = r
|
m.runs[r.ID] = r
|
||||||
m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r)
|
m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r)
|
||||||
|
|
||||||
|
@ -653,12 +645,28 @@ func (m *mockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pending {
|
if !pending && r.Status == tfe.RunPending {
|
||||||
// Only update the status if there are no other pending runs.
|
// Only update the status if there are no other pending runs.
|
||||||
r.Status = tfe.RunPlanning
|
r.Status = tfe.RunPlanning
|
||||||
r.Plan.Status = tfe.PlanRunning
|
r.Plan.Status = tfe.PlanRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logs, _ := ioutil.ReadFile(m.client.Plans.logs[r.Plan.LogReadURL])
|
||||||
|
if r.Plan.Status == tfe.PlanFinished {
|
||||||
|
if r.IsDestroy || bytes.Contains(logs, []byte("1 to add, 0 to change, 0 to destroy")) {
|
||||||
|
r.Actions.IsCancelable = false
|
||||||
|
r.Actions.IsConfirmable = true
|
||||||
|
r.HasChanges = true
|
||||||
|
r.Permissions.CanApply = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Contains(logs, []byte("null_resource.foo: 1 error")) {
|
||||||
|
r.Actions.IsCancelable = false
|
||||||
|
r.HasChanges = false
|
||||||
|
r.Status = tfe.RunErrored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,9 @@ func TestRemote_planBasic(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatal("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
|
@ -158,6 +161,9 @@ func TestRemote_planWithPlan(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected a plan error, got: %v", run.Err)
|
t.Fatalf("expected a plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") {
|
if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") {
|
||||||
t.Fatalf("expected a saved plan error, got: %v", run.Err)
|
t.Fatalf("expected a saved plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -183,6 +189,9 @@ func TestRemote_planWithPath(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected a plan error, got: %v", run.Err)
|
t.Fatalf("expected a plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "generated plan is currently not supported") {
|
if !strings.Contains(run.Err.Error(), "generated plan is currently not supported") {
|
||||||
t.Fatalf("expected a generated plan error, got: %v", run.Err)
|
t.Fatalf("expected a generated plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -233,6 +242,9 @@ func TestRemote_planWithTarget(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected a plan error, got: %v", run.Err)
|
t.Fatalf("expected a plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "targeting is currently not supported") {
|
if !strings.Contains(run.Err.Error(), "targeting is currently not supported") {
|
||||||
t.Fatalf("expected a targeting error, got: %v", run.Err)
|
t.Fatalf("expected a targeting error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -279,6 +291,9 @@ func TestRemote_planNoConfig(t *testing.T) {
|
||||||
if run.Err == nil {
|
if run.Err == nil {
|
||||||
t.Fatalf("expected a plan error, got: %v", run.Err)
|
t.Fatalf("expected a plan error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if !run.PlanEmpty {
|
||||||
|
t.Fatalf("expected plan to be empty")
|
||||||
|
}
|
||||||
if !strings.Contains(run.Err.Error(), "configuration files found") {
|
if !strings.Contains(run.Err.Error(), "configuration files found") {
|
||||||
t.Fatalf("expected configuration files error, got: %v", run.Err)
|
t.Fatalf("expected configuration files error, got: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
@ -372,6 +387,9 @@ func TestRemote_planDestroy(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("unexpected plan error: %v", run.Err)
|
t.Fatalf("unexpected plan error: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemote_planDestroyNoConfig(t *testing.T) {
|
func TestRemote_planDestroyNoConfig(t *testing.T) {
|
||||||
|
@ -391,6 +409,9 @@ func TestRemote_planDestroyNoConfig(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("unexpected plan error: %v", run.Err)
|
t.Fatalf("unexpected plan error: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemote_planWithWorkingDirectory(t *testing.T) {
|
func TestRemote_planWithWorkingDirectory(t *testing.T) {
|
||||||
|
@ -422,9 +443,41 @@ func TestRemote_planWithWorkingDirectory(t *testing.T) {
|
||||||
if run.Err != nil {
|
if run.Err != nil {
|
||||||
t.Fatalf("error running operation: %v", run.Err)
|
t.Fatalf("error running operation: %v", run.Err)
|
||||||
}
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("missing plan summery in output: %s", output)
|
t.Fatalf("missing plan summery in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemote_planWithRemoteError(t *testing.T) {
|
||||||
|
b := testBackendDefault(t)
|
||||||
|
|
||||||
|
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-error")
|
||||||
|
defer modCleanup()
|
||||||
|
|
||||||
|
op := testOperationPlan()
|
||||||
|
op.Module = mod
|
||||||
|
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("error running operation: %v", run.Err)
|
||||||
|
}
|
||||||
|
if run.ExitCode != 1 {
|
||||||
|
t.Fatalf("expected exit code 1, got %d", run.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
|
if !strings.Contains(output, "null_resource.foo: 1 error") {
|
||||||
|
t.Fatalf("missing plan error in output: %s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
resource "null_resource" "foo" {
|
||||||
|
triggers {
|
||||||
|
random = "${guid()}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
Terraform v0.11.7
|
||||||
|
|
||||||
|
Configuring remote state backend...
|
||||||
|
Initializing Terraform configuration...
|
||||||
|
|
||||||
|
Error: null_resource.foo: 1 error(s) occurred:
|
||||||
|
|
||||||
|
* null_resource.foo: 1:3: unknown function called: guid in:
|
||||||
|
|
||||||
|
${guid()}
|
|
@ -0,0 +1,5 @@
|
||||||
|
resource "null_resource" "foo" {
|
||||||
|
triggers {
|
||||||
|
random = "${guid()}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
Terraform v0.11.7
|
||||||
|
|
||||||
|
Configuring remote state backend...
|
||||||
|
Initializing Terraform configuration...
|
||||||
|
|
||||||
|
Error: null_resource.foo: 1 error(s) occurred:
|
||||||
|
|
||||||
|
* null_resource.foo: 1:3: unknown function called: guid in:
|
||||||
|
|
||||||
|
${guid()}
|
|
@ -177,7 +177,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return op.ExitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApplyCommand) Help() string {
|
func (c *ApplyCommand) Help() string {
|
||||||
|
|
|
@ -121,7 +121,7 @@ func (c *PlanCommand) Run(args []string) int {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return op.ExitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PlanCommand) Help() string {
|
func (c *PlanCommand) Help() string {
|
||||||
|
|
Loading…
Reference in New Issue