2018-10-31 16:45:03 +01:00
|
|
|
package remote
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-05-02 01:01:36 +02:00
|
|
|
"github.com/google/go-cmp/cmp"
|
2018-10-31 16:45:03 +01:00
|
|
|
tfe "github.com/hashicorp/go-tfe"
|
2021-05-17 21:00:50 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
2021-05-17 17:42:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
2021-05-17 21:07:38 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/clistate"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/views"
|
2019-01-09 03:39:14 +01:00
|
|
|
"github.com/hashicorp/terraform/internal/initwd"
|
2021-05-17 21:33:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
|
|
"github.com/hashicorp/terraform/internal/plans/planfile"
|
2021-05-17 21:43:35 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
2021-02-16 13:19:22 +01:00
|
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
2021-05-17 21:46:19 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/terraform"
|
2018-10-31 16:45:03 +01:00
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
)
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
2018-10-31 16:45:03 +01:00
|
|
|
t.Helper()
|
|
|
|
|
2021-02-16 13:19:22 +01:00
|
|
|
return testOperationPlanWithTimeout(t, configDir, 0)
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
2021-02-16 13:19:22 +01:00
|
|
|
t.Helper()
|
|
|
|
|
2019-01-09 03:39:14 +01:00
|
|
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
|
|
view := views.NewView(streams)
|
|
|
|
stateLockerView := views.NewStateLocker(arguments.ViewHuman, view)
|
|
|
|
operationView := views.NewOperation(arguments.ViewHuman, false, view)
|
2021-02-16 13:19:22 +01:00
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
return &backend.Operation{
|
2021-02-25 16:02:23 +01:00
|
|
|
ConfigDir: configDir,
|
|
|
|
ConfigLoader: configLoader,
|
|
|
|
PlanRefresh: true,
|
|
|
|
StateLocker: clistate.NewLocker(timeout, stateLockerView),
|
|
|
|
Type: backend.OperationTypePlan,
|
|
|
|
View: operationView,
|
|
|
|
}, configCleanup, done
|
2021-02-12 19:59:14 +01:00
|
|
|
}
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
func TestRemote_planBasic(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
2020-08-11 17:23:42 +02:00
|
|
|
|
|
|
|
stateMgr, _ := b.StateMgr(backend.DefaultStateName)
|
|
|
|
// An error suggests that the state was not unlocked after the operation finished
|
|
|
|
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
|
|
|
t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
2019-02-26 19:12:53 +01:00
|
|
|
func TestRemote_planCanceled(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2019-02-26 19:12:53 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2019-02-26 19:12:53 +01:00
|
|
|
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the run to simulate a Ctrl-C.
|
|
|
|
run.Stop()
|
|
|
|
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
2020-08-11 17:23:42 +02:00
|
|
|
|
|
|
|
stateMgr, _ := b.StateMgr(backend.DefaultStateName)
|
|
|
|
// An error suggests that the state was not unlocked after the operation finished
|
|
|
|
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
|
|
|
t.Fatalf("unexpected error locking state after cancelled plan: %s", err.Error())
|
|
|
|
}
|
2019-02-26 19:12:53 +01:00
|
|
|
}
|
|
|
|
|
2019-02-06 11:14:51 +01:00
|
|
|
func TestRemote_planLongLine(t *testing.T) {
|
2019-02-08 16:56:37 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2019-02-06 11:14:51 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-long-line")
|
2019-02-06 11:14:51 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2019-02-06 11:14:51 +01:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithoutPermissions(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendNoDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
// Create a named workspace without permissions.
|
|
|
|
w, err := b.client.Workspaces.Create(
|
|
|
|
context.Background(),
|
|
|
|
b.organization,
|
|
|
|
tfe.WorkspaceCreateOptions{
|
|
|
|
Name: tfe.String(b.prefix + "prod"),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating named workspace: %v", err)
|
|
|
|
}
|
|
|
|
w.Permissions.CanQueueRun = false
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
op.Workspace = "prod"
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "Insufficient rights to generate a plan") {
|
|
|
|
t.Fatalf("expected a permissions error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithParallelism(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
2021-09-14 18:43:00 +02:00
|
|
|
if b.ContextOpts == nil {
|
|
|
|
b.ContextOpts = &terraform.ContextOpts{}
|
|
|
|
}
|
|
|
|
b.ContextOpts.Parallelism = 3
|
2018-10-31 16:45:03 +01:00
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "parallelism values are currently not supported") {
|
|
|
|
t.Fatalf("expected a parallelism error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithPlan(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
op.PlanFile = &planfile.Reader{}
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "saved plan is currently not supported") {
|
|
|
|
t.Fatalf("expected a saved plan error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithPath(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op.PlanOutPath = "./testdata/plan"
|
2018-10-31 16:45:03 +01:00
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "generated plan is currently not supported") {
|
|
|
|
t.Fatalf("expected a generated plan error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithoutRefresh(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-05-14 06:28:16 +02:00
|
|
|
defer done(t)
|
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should find a run inside the mock client that has refresh set
|
|
|
|
// to false.
|
|
|
|
runsAPI := b.client.Runs.(*mockRuns)
|
|
|
|
if got, want := len(runsAPI.runs), 1; got != want {
|
|
|
|
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
|
|
|
|
}
|
|
|
|
for _, run := range runsAPI.runs {
|
|
|
|
if diff := cmp.Diff(false, run.Refresh); diff != "" {
|
|
|
|
t.Errorf("wrong Refresh setting in the created run\n%s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithoutRefreshIncompatibleAPIVersion(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
b.client.SetFakeRemoteAPIVersion("2.3")
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
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()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
2021-05-14 06:28:16 +02:00
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2021-05-14 06:28:16 +02:00
|
|
|
if !strings.Contains(errOutput, "Planning without refresh is not supported") {
|
|
|
|
t.Fatalf("expected not supported error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithRefreshOnly(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
|
|
|
defer configCleanup()
|
|
|
|
defer done(t)
|
|
|
|
|
|
|
|
op.PlanMode = plans.RefreshOnlyMode
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should find a run inside the mock client that has refresh-only set
|
|
|
|
// to true.
|
|
|
|
runsAPI := b.client.Runs.(*mockRuns)
|
|
|
|
if got, want := len(runsAPI.runs), 1; got != want {
|
|
|
|
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
|
|
|
|
}
|
|
|
|
for _, run := range runsAPI.runs {
|
|
|
|
if diff := cmp.Diff(true, run.RefreshOnly); diff != "" {
|
|
|
|
t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithRefreshOnlyIncompatibleAPIVersion(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
b.client.SetFakeRemoteAPIVersion("2.3")
|
|
|
|
|
|
|
|
op.PlanMode = plans.RefreshOnlyMode
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
|
|
|
output := done(t)
|
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
errOutput := output.Stderr()
|
|
|
|
if !strings.Contains(errOutput, "Refresh-only mode is not supported") {
|
|
|
|
t.Fatalf("expected not supported error, got: %v", errOutput)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithTarget(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2020-05-19 00:12:44 +02:00
|
|
|
// When the backend code creates a new run, we'll tweak it so that it
|
|
|
|
// has a cost estimation object with the "skipped_due_to_targeting" status,
|
|
|
|
// emulating how a real server is expected to behave in that case.
|
|
|
|
b.client.Runs.(*mockRuns).modifyNewRun = func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run) {
|
|
|
|
const fakeID = "fake"
|
|
|
|
// This is the cost estimate object embedded in the run itself which
|
|
|
|
// the backend will use to learn the ID to request from the cost
|
|
|
|
// estimates endpoint. It's pending to simulate what a freshly-created
|
|
|
|
// run is likely to look like.
|
|
|
|
run.CostEstimate = &tfe.CostEstimate{
|
|
|
|
ID: fakeID,
|
|
|
|
Status: "pending",
|
|
|
|
}
|
|
|
|
// The backend will then use the main cost estimation API to retrieve
|
|
|
|
// the same ID indicated in the object above, where we'll then return
|
|
|
|
// the status "skipped_due_to_targeting" to trigger the special skip
|
|
|
|
// message in the backend output.
|
|
|
|
client.CostEstimates.estimations[fakeID] = &tfe.CostEstimate{
|
|
|
|
ID: fakeID,
|
|
|
|
Status: "skipped_due_to_targeting",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
|
|
|
|
|
|
|
|
op.Targets = []addrs.Targetable{addr}
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2020-05-02 01:01:36 +02:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to succeed")
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
2020-05-02 01:01:36 +02:00
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be non-empty")
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
2020-05-19 00:12:44 +02:00
|
|
|
// testBackendDefault above attached a "mock UI" to our backend, so we
|
|
|
|
// can retrieve its non-error output via the OutputWriter in-memory buffer.
|
|
|
|
gotOutput := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if wantOutput := "Not available for this plan, because it was created with the -target option."; !strings.Contains(gotOutput, wantOutput) {
|
|
|
|
t.Errorf("missing message about skipped cost estimation\ngot:\n%s\nwant substring: %s", gotOutput, wantOutput)
|
|
|
|
}
|
|
|
|
|
2020-05-02 01:01:36 +02:00
|
|
|
// We should find a run inside the mock client that has the same
|
|
|
|
// target address we requested above.
|
|
|
|
runsAPI := b.client.Runs.(*mockRuns)
|
|
|
|
if got, want := len(runsAPI.runs), 1; got != want {
|
|
|
|
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
|
|
|
|
}
|
|
|
|
for _, run := range runsAPI.runs {
|
|
|
|
if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" {
|
|
|
|
t.Errorf("wrong TargetAddrs in the created run\n%s", diff)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 18:39:15 +02:00
|
|
|
func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2020-05-19 18:39:15 +02:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
// Set the tfe client's RemoteAPIVersion to an empty string, to mimic
|
|
|
|
// API versions prior to 2.3.
|
|
|
|
b.client.SetFakeRemoteAPIVersion("")
|
|
|
|
|
|
|
|
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
|
|
|
|
|
|
|
|
op.Targets = []addrs.Targetable{addr}
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2020-05-19 18:39:15 +02:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2020-05-19 18:39:15 +02:00
|
|
|
if !strings.Contains(errOutput, "Resource targeting is not supported") {
|
|
|
|
t.Fatalf("expected a targeting error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-14 06:28:16 +02:00
|
|
|
func TestRemote_planWithReplace(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
|
|
|
defer configCleanup()
|
|
|
|
defer done(t)
|
|
|
|
|
|
|
|
addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
|
|
|
|
|
|
|
|
op.ForceReplace = []addrs.AbsResourceInstance{addr}
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to succeed")
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be non-empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should find a run inside the mock client that has the same
|
|
|
|
// refresh address we requested above.
|
|
|
|
runsAPI := b.client.Runs.(*mockRuns)
|
|
|
|
if got, want := len(runsAPI.runs), 1; got != want {
|
|
|
|
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
|
|
|
|
}
|
|
|
|
for _, run := range runsAPI.runs {
|
|
|
|
if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" {
|
|
|
|
t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithReplaceIncompatibleAPIVersion(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
b.client.SetFakeRemoteAPIVersion("2.3")
|
|
|
|
|
|
|
|
addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
|
|
|
|
|
|
|
|
op.ForceReplace = []addrs.AbsResourceInstance{addr}
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
|
|
|
output := done(t)
|
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
errOutput := output.Stderr()
|
|
|
|
if !strings.Contains(errOutput, "Planning resource replacements is not supported") {
|
|
|
|
t.Fatalf("expected not supported error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
func TestRemote_planWithVariables(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-variables")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
op.Variables = testVariables(terraform.ValueFromCLIArg, "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()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "variables are currently not supported") {
|
|
|
|
t.Fatalf("expected a variables error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planNoConfig(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
output := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := output.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "configuration files found") {
|
|
|
|
t.Fatalf("expected configuration files error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-19 19:09:37 +01:00
|
|
|
func TestRemote_planNoChanges(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-11-19 19:09:37 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-no-changes")
|
2018-11-19 19:09:37 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-11-19 19:09:37 +01:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected no changes in plan summary: %s", output)
|
2018-11-19 19:09:37 +01:00
|
|
|
}
|
|
|
|
if !strings.Contains(output, "Sentinel Result: true") {
|
|
|
|
t.Fatalf("expected policy check result in output: %s", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
func TestRemote_planForceLocal(t *testing.T) {
|
|
|
|
// Set TF_FORCE_LOCAL_BACKEND so the remote backend will use
|
|
|
|
// the local backend with itself as embedded backend.
|
|
|
|
if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil {
|
|
|
|
t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND")
|
|
|
|
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-11-15 20:26:46 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-11-15 20:26:46 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-11-15 20:26:46 +01:00
|
|
|
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
backend/local: Replace CLI with view instance
This commit extracts the remaining UI logic from the local backend,
and removes access to the direct CLI output. This is replaced with an
instance of a `views.Operation` interface, which codifies the current
requirements for the local backend to interact with the user.
The exception to this at present is interactivity: approving a plan
still depends on the `UIIn` field for the backend. This is out of scope
for this commit and can be revisited separately, at which time the
`UIOut` field can also be removed.
Changes in support of this:
- Some instances of direct error output have been replaced with
diagnostics, most notably in the emergency state backup handler. This
requires reformatting the error messages to allow the diagnostic
renderer to line-wrap them;
- The "in-automation" logic has moved out of the backend and into the
view implementation;
- The plan, apply, refresh, and import commands instantiate a view and
set it on the `backend.Operation` struct, as these are the only code
paths which call the `local.Operation()` method that requires it;
- The show command requires the plan rendering code which is now in the
views package, so there is a stub implementation of a `views.Show`
interface there.
Other refactoring work in support of migrating these commands to the
common views code structure will come in follow-up PRs, at which point
we will be able to remove the UI instances from the unit tests for those
commands.
2021-02-17 19:01:30 +01:00
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
|
|
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
|
|
|
|
op.View = view
|
|
|
|
|
2018-12-05 12:29:08 +01:00
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("unexpected remote backend header in output: %s", output)
|
|
|
|
}
|
backend/local: Replace CLI with view instance
This commit extracts the remaining UI logic from the local backend,
and removes access to the direct CLI output. This is replaced with an
instance of a `views.Operation` interface, which codifies the current
requirements for the local backend to interact with the user.
The exception to this at present is interactivity: approving a plan
still depends on the `UIIn` field for the backend. This is out of scope
for this commit and can be revisited separately, at which time the
`UIOut` field can also be removed.
Changes in support of this:
- Some instances of direct error output have been replaced with
diagnostics, most notably in the emergency state backup handler. This
requires reformatting the error messages to allow the diagnostic
renderer to line-wrap them;
- The "in-automation" logic has moved out of the backend and into the
view implementation;
- The plan, apply, refresh, and import commands instantiate a view and
set it on the `backend.Operation` struct, as these are the only code
paths which call the `local.Operation()` method that requires it;
- The show command requires the plan rendering code which is now in the
views package, so there is a stub implementation of a `views.Show`
interface there.
Other refactoring work in support of migrating these commands to the
common views code structure will come in follow-up PRs, at which point
we will be able to remove the UI instances from the unit tests for those
commands.
2021-02-17 19:01:30 +01:00
|
|
|
if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2018-12-05 12:29:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithoutOperationsEntitlement(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendNoOperations(t)
|
|
|
|
defer bCleanup()
|
2018-12-05 12:29:08 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-12-05 12:29:08 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-12-05 12:29:08 +01:00
|
|
|
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
backend/local: Replace CLI with view instance
This commit extracts the remaining UI logic from the local backend,
and removes access to the direct CLI output. This is replaced with an
instance of a `views.Operation` interface, which codifies the current
requirements for the local backend to interact with the user.
The exception to this at present is interactivity: approving a plan
still depends on the `UIIn` field for the backend. This is out of scope
for this commit and can be revisited separately, at which time the
`UIOut` field can also be removed.
Changes in support of this:
- Some instances of direct error output have been replaced with
diagnostics, most notably in the emergency state backup handler. This
requires reformatting the error messages to allow the diagnostic
renderer to line-wrap them;
- The "in-automation" logic has moved out of the backend and into the
view implementation;
- The plan, apply, refresh, and import commands instantiate a view and
set it on the `backend.Operation` struct, as these are the only code
paths which call the `local.Operation()` method that requires it;
- The show command requires the plan rendering code which is now in the
views package, so there is a stub implementation of a `views.Show`
interface there.
Other refactoring work in support of migrating these commands to the
common views code structure will come in follow-up PRs, at which point
we will be able to remove the UI instances from the unit tests for those
commands.
2021-02-17 19:01:30 +01:00
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
|
|
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
|
|
|
|
op.View = view
|
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("unexpected remote backend header in output: %s", output)
|
|
|
|
}
|
backend/local: Replace CLI with view instance
This commit extracts the remaining UI logic from the local backend,
and removes access to the direct CLI output. This is replaced with an
instance of a `views.Operation` interface, which codifies the current
requirements for the local backend to interact with the user.
The exception to this at present is interactivity: approving a plan
still depends on the `UIIn` field for the backend. This is out of scope
for this commit and can be revisited separately, at which time the
`UIOut` field can also be removed.
Changes in support of this:
- Some instances of direct error output have been replaced with
diagnostics, most notably in the emergency state backup handler. This
requires reformatting the error messages to allow the diagnostic
renderer to line-wrap them;
- The "in-automation" logic has moved out of the backend and into the
view implementation;
- The plan, apply, refresh, and import commands instantiate a view and
set it on the `backend.Operation` struct, as these are the only code
paths which call the `local.Operation()` method that requires it;
- The show command requires the plan rendering code which is now in the
views package, so there is a stub implementation of a `views.Show`
interface there.
Other refactoring work in support of migrating these commands to the
common views code structure will come in follow-up PRs, at which point
we will be able to remove the UI instances from the unit tests for those
commands.
2021-02-17 19:01:30 +01:00
|
|
|
if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2018-11-15 20:26:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendNoDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
// Create a named workspace that doesn't allow operations.
|
|
|
|
_, err := b.client.Workspaces.Create(
|
|
|
|
ctx,
|
|
|
|
b.organization,
|
|
|
|
tfe.WorkspaceCreateOptions{
|
|
|
|
Name: tfe.String(b.prefix + "no-operations"),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating named workspace: %v", err)
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-11-15 20:26:46 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-11-15 20:26:46 +01:00
|
|
|
|
|
|
|
op.Workspace = "no-operations"
|
|
|
|
|
backend/local: Replace CLI with view instance
This commit extracts the remaining UI logic from the local backend,
and removes access to the direct CLI output. This is replaced with an
instance of a `views.Operation` interface, which codifies the current
requirements for the local backend to interact with the user.
The exception to this at present is interactivity: approving a plan
still depends on the `UIIn` field for the backend. This is out of scope
for this commit and can be revisited separately, at which time the
`UIOut` field can also be removed.
Changes in support of this:
- Some instances of direct error output have been replaced with
diagnostics, most notably in the emergency state backup handler. This
requires reformatting the error messages to allow the diagnostic
renderer to line-wrap them;
- The "in-automation" logic has moved out of the backend and into the
view implementation;
- The plan, apply, refresh, and import commands instantiate a view and
set it on the `backend.Operation` struct, as these are the only code
paths which call the `local.Operation()` method that requires it;
- The show command requires the plan rendering code which is now in the
views package, so there is a stub implementation of a `views.Show`
interface there.
Other refactoring work in support of migrating these commands to the
common views code structure will come in follow-up PRs, at which point
we will be able to remove the UI instances from the unit tests for those
commands.
2021-02-17 19:01:30 +01:00
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
|
|
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
|
|
|
|
op.View = view
|
|
|
|
|
2018-11-15 20:26:46 +01:00
|
|
|
run, err := b.Operation(ctx, op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("unexpected remote backend header in output: %s", output)
|
|
|
|
}
|
backend/local: Replace CLI with view instance
This commit extracts the remaining UI logic from the local backend,
and removes access to the direct CLI output. This is replaced with an
instance of a `views.Operation` interface, which codifies the current
requirements for the local backend to interact with the user.
The exception to this at present is interactivity: approving a plan
still depends on the `UIIn` field for the backend. This is out of scope
for this commit and can be revisited separately, at which time the
`UIOut` field can also be removed.
Changes in support of this:
- Some instances of direct error output have been replaced with
diagnostics, most notably in the emergency state backup handler. This
requires reformatting the error messages to allow the diagnostic
renderer to line-wrap them;
- The "in-automation" logic has moved out of the backend and into the
view implementation;
- The plan, apply, refresh, and import commands instantiate a view and
set it on the `backend.Operation` struct, as these are the only code
paths which call the `local.Operation()` method that requires it;
- The show command requires the plan rendering code which is now in the
views package, so there is a stub implementation of a `views.Show`
interface there.
Other refactoring work in support of migrating these commands to the
common views code structure will come in follow-up PRs, at which point
we will be able to remove the UI instances from the unit tests for those
commands.
2021-02-17 19:01:30 +01:00
|
|
|
if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2018-11-15 20:26:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
func TestRemote_planLockTimeout(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
// Retrieve the workspace used to run this operation in.
|
|
|
|
w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error retrieving workspace: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new configuration version.
|
|
|
|
c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating configuration version: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a pending run to block this run.
|
|
|
|
_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
|
|
|
|
ConfigurationVersion: c,
|
|
|
|
Workspace: w,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating pending run: %v", err)
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlanWithTimeout(t, "./testdata/plan", 50)
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
input := testInput(t, map[string]string{
|
|
|
|
"cancel": "yes",
|
|
|
|
"approve": "yes",
|
|
|
|
})
|
|
|
|
|
|
|
|
op.UIIn = input
|
|
|
|
op.UIOut = b.CLI
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
_, err = b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sigint := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigint, syscall.SIGINT)
|
|
|
|
select {
|
|
|
|
case <-sigint:
|
|
|
|
// Stop redirecting SIGINT signals.
|
|
|
|
signal.Stop(sigint)
|
2020-11-18 21:35:48 +01:00
|
|
|
case <-time.After(200 * time.Millisecond):
|
|
|
|
t.Fatalf("expected lock timeout after 50 milliseconds, waited 200 milliseconds")
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(input.answers) != 2 {
|
|
|
|
t.Fatalf("expected unused answers, got: %v", input.answers)
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "Lock timeout exceeded") {
|
2018-11-15 20:26:46 +01:00
|
|
|
t.Fatalf("expected lock timout error in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("unexpected plan summary in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planDestroy(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-04-06 01:28:59 +02:00
|
|
|
op.PlanMode = plans.DestroyMode
|
2018-10-31 16:45:03 +01:00
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planDestroyNoConfig(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-04-06 01:28:59 +02:00
|
|
|
op.PlanMode = plans.DestroyMode
|
2018-10-31 16:45:03 +01:00
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithWorkingDirectory(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
options := tfe.WorkspaceUpdateOptions{
|
|
|
|
WorkingDirectory: tfe.String("terraform"),
|
|
|
|
}
|
|
|
|
|
2019-07-17 14:39:37 +02:00
|
|
|
// Configure the workspace to use a custom working directory.
|
2018-10-31 16:45:03 +01:00
|
|
|
_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error configuring working directory: %v", err)
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-with-working-directory/terraform")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2019-07-18 15:36:53 +02:00
|
|
|
if !strings.Contains(output, "The remote workspace is configured to work with configuration") {
|
|
|
|
t.Fatalf("expected working directory warning: %s", output)
|
|
|
|
}
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-17 14:39:37 +02:00
|
|
|
func TestRemote_planWithWorkingDirectoryFromCurrentPath(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
|
|
|
options := tfe.WorkspaceUpdateOptions{
|
|
|
|
WorkingDirectory: tfe.String("terraform"),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure the workspace to use a custom working directory.
|
|
|
|
_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error configuring working directory: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error getting current working directory: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to change into the configuration directory to make sure
|
|
|
|
// the logic to upload the correct slug is working as expected.
|
|
|
|
if err := os.Chdir("./testdata/plan-with-working-directory/terraform"); err != nil {
|
|
|
|
t.Fatalf("error changing directory: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Chdir(wd) // Make sure we change back again when were done.
|
|
|
|
|
|
|
|
// For this test we need to give our current directory instead of the
|
|
|
|
// full path to the configuration as we already changed directories.
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, ".")
|
2019-07-17 14:39:37 +02:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2019-07-17 14:39:37 +02:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2019-07-17 14:39:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 23:35:33 +02:00
|
|
|
func TestRemote_planCostEstimation(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-cost-estimation")
|
2019-09-26 23:35:33 +02:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2019-09-26 23:35:33 +02:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
|
|
|
if !strings.Contains(output, "Resources: 1 of 1 estimated") {
|
|
|
|
t.Fatalf("expected cost estimate result in output: %s", output)
|
|
|
|
}
|
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:45:03 +01:00
|
|
|
func TestRemote_planPolicyPass(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-passed")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
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.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatalf("expected a non-empty plan")
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "Sentinel Result: true") {
|
2018-11-19 19:09:37 +01:00
|
|
|
t.Fatalf("expected policy check result in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
2019-04-25 09:53:12 +02:00
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2019-04-25 09:53:12 +02:00
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planPolicyHardFail(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-hard-failed")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
viewOutput := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := viewOutput.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "hard failed") {
|
|
|
|
t.Fatalf("expected a policy check error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "Sentinel Result: false") {
|
2018-11-15 20:26:46 +01:00
|
|
|
t.Fatalf("expected policy check result in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
2019-04-25 09:53:12 +02:00
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2019-04-25 09:53:12 +02:00
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planPolicySoftFail(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-soft-failed")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
|
|
|
|
|
|
|
op.Workspace = backend.DefaultStateName
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting operation: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
<-run.Done()
|
2021-02-25 16:02:23 +01:00
|
|
|
viewOutput := done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatalf("expected plan to be empty")
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
errOutput := viewOutput.Stderr()
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(errOutput, "soft failed") {
|
|
|
|
t.Fatalf("expected a policy check error, got: %v", errOutput)
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "Sentinel Result: false") {
|
2018-11-15 20:26:46 +01:00
|
|
|
t.Fatalf("expected policy check result in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
2019-04-25 09:53:12 +02:00
|
|
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
2019-09-27 23:22:34 +02:00
|
|
|
t.Fatalf("expected plan summary in output: %s", output)
|
2019-04-25 09:53:12 +02:00
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRemote_planWithRemoteError(t *testing.T) {
|
2019-02-06 09:36:42 +01:00
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
2018-10-31 16:45:03 +01:00
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-with-error")
|
2018-10-31 16:45:03 +01:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2018-10-31 16:45:03 +01:00
|
|
|
|
|
|
|
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.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("expected plan operation to fail")
|
|
|
|
}
|
|
|
|
if run.Result.ExitStatus() != 1 {
|
|
|
|
t.Fatalf("expected exit code 1, got %d", run.Result.ExitStatus())
|
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
2018-11-15 20:26:46 +01:00
|
|
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
|
|
|
t.Fatalf("expected remote backend header in output: %s", output)
|
|
|
|
}
|
2018-10-31 16:45:03 +01:00
|
|
|
if !strings.Contains(output, "null_resource.foo: 1 error") {
|
2018-11-15 20:26:46 +01:00
|
|
|
t.Fatalf("expected plan error in output: %s", output)
|
2018-10-31 16:45:03 +01:00
|
|
|
}
|
|
|
|
}
|
2019-07-22 15:06:39 +02:00
|
|
|
|
|
|
|
func TestRemote_planOtherError(t *testing.T) {
|
|
|
|
b, bCleanup := testBackendDefault(t)
|
|
|
|
defer bCleanup()
|
|
|
|
|
2021-02-25 16:02:23 +01:00
|
|
|
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
2019-07-22 15:06:39 +02:00
|
|
|
defer configCleanup()
|
2021-02-25 16:02:23 +01:00
|
|
|
defer done(t)
|
2019-07-22 15:06:39 +02:00
|
|
|
|
|
|
|
op.Workspace = "network-error" // custom error response in backend_mock.go
|
|
|
|
|
|
|
|
_, err := b.Operation(context.Background(), op)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("expected error, got success")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(err.Error(),
|
|
|
|
"The configured \"remote\" backend encountered an unexpected error:\n\nI'm a little teacup") {
|
|
|
|
t.Fatalf("expected error message, got: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|