Skip to content

Commit

Permalink
Merge pull request #12173 from hashicorp/b-remote-state-ds
Browse files Browse the repository at this point in the history
providers/terraform: remote state data source supports backends
  • Loading branch information
mitchellh authored Feb 23, 2017
2 parents 8621189 + 52720ce commit 8f11068
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 40 deletions.
69 changes: 69 additions & 0 deletions backend/init/init.go
Original file line number Diff line number Diff line change
@@ -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
}
35 changes: 24 additions & 11 deletions builtin/providers/terraform/data_source_state.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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()

// 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}
// Configure the backend
if err := b.Configure(terraform.NewResourceConfig(rawConfig)); err != nil {
return fmt.Errorf("error initializing backend: %s", err)
}

// 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
}
Expand Down
29 changes: 29 additions & 0 deletions builtin/providers/terraform/data_source_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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) },
Expand Down Expand Up @@ -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"
Expand Down
36 changes: 7 additions & 29 deletions command/meta_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand Down

0 comments on commit 8f11068

Please sign in to comment.