diff --git a/command/meta_backend.go b/command/meta_backend.go index ea96e7455..31dc9b566 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -138,6 +138,20 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, error) { return local, nil } +// IsLocalBackend returns true if the backend is a local backend. We use this +// for some checks that require a remote backend. +func (m *Meta) IsLocalBackend(b backend.Backend) bool { + // Is it a local backend? + bLocal, ok := b.(*backendlocal.Local) + + // If it is, does it not have an alternate state backend? + if ok { + ok = bLocal.Backend == nil + } + + return ok +} + // Operation initializes a new backend.Operation struct. // // This prepares the operation. After calling this, the caller is expected diff --git a/command/push.go b/command/push.go index f3e173ec4..7dc1567f9 100644 --- a/command/push.go +++ b/command/push.go @@ -71,24 +71,6 @@ func (c *PushCommand) Run(args []string) int { return 1 } - /* - // Verify the state is remote, we can't push without a remote state - s, err := c.State() - if err != nil { - c.Ui.Error(fmt.Sprintf("Failed to read state: %s", err)) - return 1 - } - if !s.State().IsRemote() { - c.Ui.Error( - "Remote state is not enabled. For Atlas to run Terraform\n" + - "for you, remote state must be used and configured. Remote\n" + - "state via any backend is accepted, not just Atlas. To\n" + - "configure remote state, use the `terraform remote config`\n" + - "command.") - return 1 - } - */ - // Check if the path is a plan plan, err := c.Plan(configPath) if err != nil { @@ -125,6 +107,17 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // We require a non-local backend + if c.IsLocalBackend(b) { + c.Ui.Error( + "Remote state is not enabled. For Atlas to run Terraform\n" + + "for you, remote state must be used and configured. Remote\n" + + "state via any backend is accepted, not just Atlas. To\n" + + "configure remote state, use the `terraform remote config`\n" + + "command.") + return 1 + } + // We require a local backend local, ok := b.(backend.Local) if !ok { diff --git a/command/push_test.go b/command/push_test.go index 94573765f..0d1f25413 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -9,9 +9,11 @@ import ( "path/filepath" "reflect" "sort" + "strings" "testing" atlas "github.com/hashicorp/atlas-go/v1" + "github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" ) @@ -662,6 +664,12 @@ func TestPush_noState(t *testing.T) { } func TestPush_noRemoteState(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("push-no-remote"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + state := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ @@ -679,19 +687,32 @@ func TestPush_noRemoteState(t *testing.T) { } statePath := testStateFile(t, state) + // Path where the archive will be "uploaded" to + archivePath := testTempFile(t) + defer os.Remove(archivePath) + + client := &mockPushClient{File: archivePath} ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ Ui: ui, }, + client: client, } args := []string{ + "-vcs=false", "-state", statePath, + td, } if code := c.Run(args); code != 1 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } + + errStr := ui.ErrorWriter.String() + if !strings.Contains(errStr, "Remote state") { + t.Fatalf("bad: %s", errStr) + } } func TestPush_plan(t *testing.T) { diff --git a/command/test-fixtures/push-no-remote/main.tf b/command/test-fixtures/push-no-remote/main.tf new file mode 100644 index 000000000..265162636 --- /dev/null +++ b/command/test-fixtures/push-no-remote/main.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "foo" {} + +atlas { + name = "foo" +}