From 5f2d32e4d29fe16f08cb763750dd09972e5e8dd7 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Fri, 3 May 2019 13:56:57 -0400 Subject: [PATCH] backend/remote: fix panic when loading a 0.12 cached backend (#21199) * backend/remote: fix panic when loading a 0.12 cached backend The remote backend schema changed in terraform 0.12, causing a panic when 0.11 tries to load it. While we do not support downgrading configurations, we should prevent panics where possible. Fixes #21190 --- backend/remote/backend.go | 10 +++++++++- backend/remote/backend_test.go | 36 ++++++++++++++++++++++++++++++++++ command/meta_backend.go | 22 ++++++++++----------- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/backend/remote/backend.go b/backend/remote/backend.go index 6026ca5c0f..3040a4498c 100644 --- a/backend/remote/backend.go +++ b/backend/remote/backend.go @@ -149,8 +149,16 @@ func (b *Remote) configure(ctx context.Context) error { b.hostname = d.Get("hostname").(string) b.organization = d.Get("organization").(string) + // The backend schema for the workspaces attribute changed in terraform + // 0.12, and causes a panic when encountered by 0.11. While we don't support + // downgrading, we do want to protect users from that panic. + wsConfig := d.Get("workspaces").(*schema.Set).List() + if len(wsConfig) == 0 { + return fmt.Errorf("the cached backend configuration is not valid for this version of terraform") + } + // Get and assert the workspaces configuration block. - workspace := d.Get("workspaces").(*schema.Set).List()[0].(map[string]interface{}) + workspace := wsConfig[0].(map[string]interface{}) // Get the default workspace name and prefix. b.workspace = workspace["name"].(string) diff --git a/backend/remote/backend_test.go b/backend/remote/backend_test.go index 781cd319d4..f807fb5b48 100644 --- a/backend/remote/backend_test.go +++ b/backend/remote/backend_test.go @@ -126,6 +126,42 @@ func TestRemote_config(t *testing.T) { } } +// TestRemote_CachedConfig is similar to the test above, but we mimic loading a +// cached backend config by skipping the validate call +func TestRemote_CachedConfig(t *testing.T) { + cases := map[string]struct { + config map[string]interface{} + err error + }{ + "0.12 config": { + config: map[string]interface{}{ + "hostname": "nonexisting.local", + "organization": "hashicorp", + "workspaces": map[string]interface{}{ + "name": "prod", + }, + }, + err: errors.New("the cached backend configuration is not valid for this version of terraform"), + }, + } + for name, tc := range cases { + s := testServer(t) + b := New(testDisco(s)) + + // Get the proper config structure + rc, err := config.NewRawConfig(tc.config) + if err != nil { + t.Fatalf("%s: error creating raw config: %v", name, err) + } + conf := terraform.NewResourceConfig(rc) + + // Configure + err = b.Configure(conf) + if err != tc.err && err != nil && tc.err != nil && !strings.Contains(err.Error(), tc.err.Error()) { + t.Fatalf("%s: expected error %q, got: %q", name, tc.err, err) + } + } +} func TestRemote_versionConstraints(t *testing.T) { cases := map[string]struct { config map[string]interface{} diff --git a/command/meta_backend.go b/command/meta_backend.go index 067f5cc8cd..ef2bc20eee 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -12,7 +12,7 @@ import ( "path/filepath" "strings" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl" "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/command/clistate" @@ -390,16 +390,14 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { case c != nil && s.Remote.Empty() && !s.Backend.Empty(): // If our configuration is the same, then we're just initializing // a previously configured remote backend. - if !s.Backend.Empty() { - hash := s.Backend.Hash - // on init we need an updated hash containing any extra options - // that were added after merging. - if opts.Init { - hash = s.Backend.Rehash() - } - if hash == cHash { - return m.backend_C_r_S_unchanged(c, sMgr) - } + hash := s.Backend.Hash + // on init we need an updated hash containing any extra options + // that were added after merging. + if opts.Init { + hash = s.Backend.Rehash() + } + if hash == cHash { + return m.backend_C_r_S_unchanged(c, sMgr) } if !opts.Init { @@ -412,7 +410,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) { log.Printf( "[WARN] command: backend config change! saved: %d, new: %d", - s.Backend.Hash, cHash) + hash, cHash) return m.backend_C_r_S_changed(c, sMgr, true) // Configuring a backend for the first time while having legacy