From f79e04500fbf8eb1c8577c827a1f9a5af122fef5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Feb 2017 11:17:06 -0800 Subject: [PATCH 1/3] backend/init: a package for storing the factories for backends --- backend/init/init.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 backend/init/init.go diff --git a/backend/init/init.go b/backend/init/init.go new file mode 100644 index 000000000..8c6cf0a8f --- /dev/null +++ b/backend/init/init.go @@ -0,0 +1,55 @@ +// Package init contains the list of backends that can be initialized and +// basic helper functions for initializing those backends. +package init + +import ( + "sync" + + "github.com/hashicorp/terraform/backend" + + backendlegacy "github.com/hashicorp/terraform/backend/legacy" + backendlocal "github.com/hashicorp/terraform/backend/local" + backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul" + backendinmem "github.com/hashicorp/terraform/backend/remote-state/inmem" +) + +// backends is the list of available backends. This is a global variable +// because backends are currently hardcoded into Terraform and can't be +// modified without recompilation. +// +// To read an available backend, use the Backend function. This ensures +// safe concurrent read access to the list of built-in backends. +// +// Backends are hardcoded into Terraform because the API for backends uses +// complex structures and supporting that over the plugin system is currently +// prohibitively difficult. For those wanting to implement a custom backend, +// they can do so with recompilation. +var backends map[string]func() backend.Backend +var backendsLock sync.Mutex + +func init() { + // Our hardcoded backends. We don't need to acquire a lock here + // since init() code is serial and can't spawn goroutines. + backends = map[string]func() backend.Backend{ + "local": func() backend.Backend { return &backendlocal.Local{} }, + "consul": func() backend.Backend { return backendconsul.New() }, + "inmem": func() backend.Backend { return backendinmem.New() }, + } + + // Add the legacy remote backends that haven't yet been convertd to + // the new backend API. + backendlegacy.Init(backends) +} + +// Backend returns the initialization factory for the given backend, or +// nil if none exists. +func Backend(name string) func() backend.Backend { + backendsLock.Lock() + defer backendsLock.Unlock() + return backends[name] +} + +// NOTE(@mitchellh): At some point I'm sure we'll want Add() to add a backend +// to the list. Right now we hardcode all backends and there isn't a reasonable +// use case for this so we're leaving it out. It would be trivial to add in +// the future. From 478a7dbfe7bbae25c2f918874d5f7396dfae7fef Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Feb 2017 11:17:27 -0800 Subject: [PATCH 2/3] command: convert to using backend/init --- command/meta_backend.go | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/command/meta_backend.go b/command/meta_backend.go index 4eb79ed5c..894ba8efa 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl" "github.com/hashicorp/terraform/backend" + backendinit "github.com/hashicorp/terraform/backend/init" clistate "github.com/hashicorp/terraform/command/state" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/state" @@ -24,8 +25,6 @@ import ( backendlegacy "github.com/hashicorp/terraform/backend/legacy" backendlocal "github.com/hashicorp/terraform/backend/local" - backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul" - backendinmem "github.com/hashicorp/terraform/backend/remote-state/inmem" ) // BackendOpts are the options used to initialize a backend.Backend. @@ -1149,8 +1148,8 @@ func (m *Meta) backend_C_r_S_unchanged( config := terraform.NewResourceConfig(rawC) // Get the backend - f, ok := Backends[s.Backend.Type] - if !ok { + f := backendinit.Backend(s.Backend.Type) + if f == nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type) } b := f() @@ -1300,8 +1299,8 @@ func (m *Meta) backendInitFromConfig(c *config.Backend) (backend.Backend, error) config := terraform.NewResourceConfig(c.RawConfig) // Get the backend - f, ok := Backends[c.Type] - if !ok { + f := backendinit.Backend(c.Type) + if f == nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type) } b := f() @@ -1374,8 +1373,8 @@ func (m *Meta) backendInitFromSaved(s *terraform.BackendState) (backend.Backend, config := terraform.NewResourceConfig(rawC) // Get the backend - f, ok := Backends[s.Type] - if !ok { + f := backendinit.Backend(s.Type) + if f == nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Type) } b := f() @@ -1397,27 +1396,6 @@ func (m *Meta) backendInitRequired(reason string) { // Output constants and initialization code //------------------------------------------------------------------- -// Backends is the list of available backends. This is currently a hardcoded -// list that can't be modified without recompiling Terraform. This is done -// because the API for backends uses complex structures and supporting that -// over the plugin system is currently prohibitively difficult. For those -// wanting to implement a custom backend, recompilation should not be a -// high barrier. -var Backends map[string]func() backend.Backend - -func init() { - // Our hardcoded backends - Backends = map[string]func() backend.Backend{ - "local": func() backend.Backend { return &backendlocal.Local{} }, - "consul": func() backend.Backend { return backendconsul.New() }, - "inmem": func() backend.Backend { return backendinmem.New() }, - } - - // Add the legacy remote backends that haven't yet been convertd to - // the new backend API. - backendlegacy.Init(Backends) -} - // errBackendInitRequired is the final error message shown when reinit // is required for some reason. The error message includes the reason. var errBackendInitRequired = errors.New( From 52720ce8805beb4138699e4ecf44ac495f342dfd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Feb 2017 11:37:56 -0800 Subject: [PATCH 3/3] providers/terraform: data source uses backends for state loading --- backend/init/init.go | 22 +++++++++--- .../providers/terraform/data_source_state.go | 35 +++++++++++++------ .../terraform/data_source_state_test.go | 29 +++++++++++++++ 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/backend/init/init.go b/backend/init/init.go index 8c6cf0a8f..6c057c32b 100644 --- a/backend/init/init.go +++ b/backend/init/init.go @@ -49,7 +49,21 @@ func Backend(name string) func() backend.Backend { return backends[name] } -// NOTE(@mitchellh): At some point I'm sure we'll want Add() to add a backend -// to the list. Right now we hardcode all backends and there isn't a reasonable -// use case for this so we're leaving it out. It would be trivial to add in -// the future. +// Set sets a new backend in the list of backends. If f is nil then the +// backend will be removed from the map. If this backend already exists +// then it will be overwritten. +// +// This method sets this backend globally and care should be taken to do +// this only before Terraform is executing to prevent odd behavior of backends +// changing mid-execution. +func Set(name string, f func() backend.Backend) { + backendsLock.Lock() + defer backendsLock.Unlock() + + if f == nil { + delete(backends, name) + return + } + + backends[name] = f +} diff --git a/builtin/providers/terraform/data_source_state.go b/builtin/providers/terraform/data_source_state.go index 2c3c5b050..ee9a63630 100644 --- a/builtin/providers/terraform/data_source_state.go +++ b/builtin/providers/terraform/data_source_state.go @@ -1,11 +1,14 @@ package terraform import ( + "fmt" "log" "time" + backendinit "github.com/hashicorp/terraform/backend/init" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/state/remote" + "github.com/hashicorp/terraform/terraform" ) func dataSourceRemoteState() *schema.Resource { @@ -43,9 +46,11 @@ func dataSourceRemoteState() *schema.Resource { func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error { backend := d.Get("backend").(string) - config := make(map[string]string) - for k, v := range d.Get("config").(map[string]interface{}) { - config[k] = v.(string) + + // Get the configuration in a type we want. + rawConfig, err := config.NewRawConfig(d.Get("config").(map[string]interface{})) + if err != nil { + return fmt.Errorf("error initializing backend: %s", err) } // Don't break people using the old _local syntax - but note warning above @@ -55,15 +60,23 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error { } // Create the client to access our remote state - log.Printf("[DEBUG] Initializing remote state client: %s", backend) - client, err := remote.NewClient(backend, config) - if err != nil { - return err + log.Printf("[DEBUG] Initializing remote state backend: %s", backend) + f := backendinit.Backend(backend) + if f == nil { + return fmt.Errorf("Unknown backend type: %s", backend) + } + b := f() + + // Configure the backend + if err := b.Configure(terraform.NewResourceConfig(rawConfig)); err != nil { + return fmt.Errorf("error initializing backend: %s", err) } - // Create the remote state itself and refresh it in order to load the state - log.Printf("[DEBUG] Loading remote state...") - state := &remote.State{Client: client} + // Get the state + state, err := b.State() + if err != nil { + return fmt.Errorf("error loading the remote state: %s", err) + } if err := state.RefreshState(); err != nil { return err } diff --git a/builtin/providers/terraform/data_source_state_test.go b/builtin/providers/terraform/data_source_state_test.go index 1d56823ef..9a58b154f 100644 --- a/builtin/providers/terraform/data_source_state_test.go +++ b/builtin/providers/terraform/data_source_state_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + backendinit "github.com/hashicorp/terraform/backend/init" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -24,6 +25,25 @@ func TestState_basic(t *testing.T) { }) } +func TestState_backends(t *testing.T) { + backendinit.Set("_ds_test", backendinit.Backend("local")) + defer backendinit.Set("_ds_test", nil) + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccState_backend, + Check: resource.ComposeTestCheckFunc( + testAccCheckStateValue( + "data.terraform_remote_state.foo", "foo", "bar"), + ), + }, + }, + }) +} + func TestState_complexOutputs(t *testing.T) { resource.UnitTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -72,6 +92,15 @@ data "terraform_remote_state" "foo" { } }` +const testAccState_backend = ` +data "terraform_remote_state" "foo" { + backend = "_ds_test" + + config { + path = "./test-fixtures/basic.tfstate" + } +}` + const testAccState_complexOutputs = ` resource "terraform_remote_state" "foo" { backend = "local"