Merge pull request #12173 from hashicorp/b-remote-state-ds
providers/terraform: remote state data source supports backends
This commit is contained in:
commit
8f11068ab2
|
@ -0,0 +1,69 @@
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dataSourceRemoteState() *schema.Resource {
|
func dataSourceRemoteState() *schema.Resource {
|
||||||
|
@ -43,9 +46,11 @@ func dataSourceRemoteState() *schema.Resource {
|
||||||
|
|
||||||
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
backend := d.Get("backend").(string)
|
backend := d.Get("backend").(string)
|
||||||
config := make(map[string]string)
|
|
||||||
for k, v := range d.Get("config").(map[string]interface{}) {
|
// Get the configuration in a type we want.
|
||||||
config[k] = v.(string)
|
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
|
// 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
|
// Create the client to access our remote state
|
||||||
log.Printf("[DEBUG] Initializing remote state client: %s", backend)
|
log.Printf("[DEBUG] Initializing remote state backend: %s", backend)
|
||||||
client, err := remote.NewClient(backend, config)
|
f := backendinit.Backend(backend)
|
||||||
if err != nil {
|
if f == nil {
|
||||||
return err
|
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
|
// Get the state
|
||||||
log.Printf("[DEBUG] Loading remote state...")
|
state, err := b.State()
|
||||||
state := &remote.State{Client: client}
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading the remote state: %s", err)
|
||||||
|
}
|
||||||
if err := state.RefreshState(); err != nil {
|
if err := state.RefreshState(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"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) {
|
func TestState_complexOutputs(t *testing.T) {
|
||||||
resource.UnitTest(t, resource.TestCase{
|
resource.UnitTest(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
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 = `
|
const testAccState_complexOutputs = `
|
||||||
resource "terraform_remote_state" "foo" {
|
resource "terraform_remote_state" "foo" {
|
||||||
backend = "local"
|
backend = "local"
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||||
clistate "github.com/hashicorp/terraform/command/state"
|
clistate "github.com/hashicorp/terraform/command/state"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
|
@ -24,8 +25,6 @@ import (
|
||||||
|
|
||||||
backendlegacy "github.com/hashicorp/terraform/backend/legacy"
|
backendlegacy "github.com/hashicorp/terraform/backend/legacy"
|
||||||
backendlocal "github.com/hashicorp/terraform/backend/local"
|
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.
|
// 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)
|
config := terraform.NewResourceConfig(rawC)
|
||||||
|
|
||||||
// Get the backend
|
// Get the backend
|
||||||
f, ok := Backends[s.Backend.Type]
|
f := backendinit.Backend(s.Backend.Type)
|
||||||
if !ok {
|
if f == nil {
|
||||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)
|
return nil, fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)
|
||||||
}
|
}
|
||||||
b := f()
|
b := f()
|
||||||
|
@ -1300,8 +1299,8 @@ func (m *Meta) backendInitFromConfig(c *config.Backend) (backend.Backend, error)
|
||||||
config := terraform.NewResourceConfig(c.RawConfig)
|
config := terraform.NewResourceConfig(c.RawConfig)
|
||||||
|
|
||||||
// Get the backend
|
// Get the backend
|
||||||
f, ok := Backends[c.Type]
|
f := backendinit.Backend(c.Type)
|
||||||
if !ok {
|
if f == nil {
|
||||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type)
|
return nil, fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type)
|
||||||
}
|
}
|
||||||
b := f()
|
b := f()
|
||||||
|
@ -1374,8 +1373,8 @@ func (m *Meta) backendInitFromSaved(s *terraform.BackendState) (backend.Backend,
|
||||||
config := terraform.NewResourceConfig(rawC)
|
config := terraform.NewResourceConfig(rawC)
|
||||||
|
|
||||||
// Get the backend
|
// Get the backend
|
||||||
f, ok := Backends[s.Type]
|
f := backendinit.Backend(s.Type)
|
||||||
if !ok {
|
if f == nil {
|
||||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Type)
|
return nil, fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Type)
|
||||||
}
|
}
|
||||||
b := f()
|
b := f()
|
||||||
|
@ -1397,27 +1396,6 @@ func (m *Meta) backendInitRequired(reason string) {
|
||||||
// Output constants and initialization code
|
// 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
|
// errBackendInitRequired is the final error message shown when reinit
|
||||||
// is required for some reason. The error message includes the reason.
|
// is required for some reason. The error message includes the reason.
|
||||||
var errBackendInitRequired = errors.New(
|
var errBackendInitRequired = errors.New(
|
||||||
|
|
Loading…
Reference in New Issue