From e3748901b4fa652d958ff8afc259354fbe4339a7 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 8 Aug 2017 18:41:00 -0400 Subject: [PATCH 1/3] Don't ForceLocal for the import backend While the `local.Local` backend is the only implementation of `backend.Local`, creating the backend with `ForceLocal` bypasses the `backend.Backend` in the `local.Local` causing a local state to be implicitly created rather than using the configured state backend. Add a test that imports into a configured backend (using the "local" backend as a remote state proxy). This further confirms the confusing nature of ForceLocal, as the backend _is_ local, but not from the viewpoint of meta.Backend. --- command/import.go | 9 +- command/import_test.go | 82 +++++++++++++++++++ .../import-provider-remote-state/main.tf | 12 +++ 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 command/test-fixtures/import-provider-remote-state/main.tf diff --git a/command/import.go b/command/import.go index b1cc623e44..b2c0549ffa 100644 --- a/command/import.go +++ b/command/import.go @@ -123,15 +123,18 @@ func (c *ImportCommand) Run(args []string) int { // Load the backend b, err := c.Backend(&BackendOpts{ - Config: mod.Config(), - ForceLocal: true, + Config: mod.Config(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 } - // We require a local backend + // We require a backend.Local to build a context. + // This isn't necessarily a "local.Local" backend, which provides local + // operations, however that is the only current implementation. A + // "local.Local" backend also doesn't necessarily provide local state, as + // that may be delegated to a "remotestate.Backend". local, ok := b.(backend.Local) if !ok { c.Ui.Error(ErrUnsupportedLocalOp) diff --git a/command/import_test.go b/command/import_test.go index 057ab560bb..417e6143aa 100644 --- a/command/import_test.go +++ b/command/import_test.go @@ -110,6 +110,88 @@ func TestImport_providerConfig(t *testing.T) { testStateOutput(t, statePath, testImportStr) } +// "remote" state provided by the "local" backend +func TestImport_remoteState(t *testing.T) { + td := tempDir(t) + copy.CopyDir(testFixturePath("import-provider-remote-state"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + statePath := "imported.tfstate" + + // init our backend + ui := new(cli.MockUi) + m := Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + } + + ic := &InitCommand{ + Meta: m, + providerInstaller: &mockProviderInstaller{ + Providers: map[string][]string{ + "test": []string{"1.2.3"}, + }, + + Dir: m.pluginDir(), + }, + } + + if code := ic.Run([]string{}); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter) + } + + p := testProvider() + ui = new(cli.MockUi) + c := &ImportCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + p.ImportStateFn = nil + p.ImportStateReturn = []*terraform.InstanceState{ + &terraform.InstanceState{ + ID: "yay", + Ephemeral: terraform.EphemeralState{ + Type: "test_instance", + }, + }, + } + + configured := false + p.ConfigureFn = func(c *terraform.ResourceConfig) error { + configured = true + + if v, ok := c.Get("foo"); !ok || v.(string) != "bar" { + return fmt.Errorf("bad value: %#v", v) + } + + return nil + } + + args := []string{ + "test_instance.foo", + "bar", + } + if code := c.Run(args); code != 0 { + fmt.Println(ui.OutputWriter) + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Verify that we were called + if !configured { + t.Fatal("Configure should be called") + } + + if !p.ImportStateCalled { + t.Fatal("ImportState should be called") + } + + testStateOutput(t, statePath, testImportStr) +} + func TestImport_providerConfigWithVar(t *testing.T) { defer testChdir(t, testFixturePath("import-provider-var"))() diff --git a/command/test-fixtures/import-provider-remote-state/main.tf b/command/test-fixtures/import-provider-remote-state/main.tf new file mode 100644 index 0000000000..23ebfb4c62 --- /dev/null +++ b/command/test-fixtures/import-provider-remote-state/main.tf @@ -0,0 +1,12 @@ +terraform { + backend "local" { + path = "imported.tfstate" + } +} + +provider "test" { + foo = "bar" +} + +resource "test_instance" "foo" { +} From 4034f988dfcd3648d839581e3e6ef754216785d8 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 9 Aug 2017 14:01:45 -0400 Subject: [PATCH 2/3] update import command docs Fix the -state and -state-out wording to be consistent with other commands. Remove the erroneous reference to remote state in the website version of the flag description. --- command/import.go | 9 +++++---- website/docs/commands/import.html.md | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/command/import.go b/command/import.go index b2c0549ffa..e455bb53c2 100644 --- a/command/import.go +++ b/command/import.go @@ -235,11 +235,12 @@ Options: specifying aliases, such as "aws.eu". Defaults to the normal provider prefix of the resource being imported. - -state=path Path to read and save state (unless state-out - is specified). Defaults to "terraform.tfstate". + -state=PATH Path to the source state file. Defaults to the configured + backend, or "terraform.tfstate" - -state-out=path Path to write updated state file. By default, the - "-state" path will be used. + -state-out=PATH Path to the destination state file to write to. If this + isn't specified, the source state file will be used. This + can be a new or existing path. -var 'foo=bar' Set a variable in the Terraform configuration. This flag can be set multiple times. This is only useful diff --git a/website/docs/commands/import.html.md b/website/docs/commands/import.html.md index 4862f87ec3..2662b72eda 100644 --- a/website/docs/commands/import.html.md +++ b/website/docs/commands/import.html.md @@ -53,12 +53,12 @@ The command-line flags are all optional. The list of available flags are: provider based on the prefix of the resource being imported. You usually don't need to specify this. -* `-state=path` - The path to read and save state files (unless state-out is - specified). Ignored when [remote state](/docs/state/remote.html) is used. +* `-state=path` - Path to the source state file to read from. Defaults to the + configured backend, or "terraform.tfstate". -* `-state-out=path` - Path to write the final state file. By default, this is - the state path. Ignored when [remote state](/docs/state/remote.html) is - used. +* `-state-out=path` - Path to the destination state file to write to. If this + isn't specified the source state file will be used. This can be a new or + existing path. * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. Variable values are interpreted as From 37932c3cd61f5356ad00311b9a195b9f87ef7e8e Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 9 Aug 2017 14:03:06 -0400 Subject: [PATCH 3/3] make state_rm flag description match state_mv --- command/state_rm.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/command/state_rm.go b/command/state_rm.go index a2ffdf1b8e..e106afb810 100644 --- a/command/state_rm.go +++ b/command/state_rm.go @@ -87,9 +87,8 @@ Options: will write it to the same path as the statefile with a backup extension. - -state=statefile Path to a Terraform state file to use to look - up Terraform-managed resources. By default it will - use the state "terraform.tfstate" if it exists. + -state=PATH Path to the source state file. Defaults to the configured + backend, or "terraform.tfstate" ` return strings.TrimSpace(helpText)