diff --git a/backend/legacy/backend.go b/backend/legacy/backend.go new file mode 100644 index 000000000..21ed7b1fb --- /dev/null +++ b/backend/legacy/backend.go @@ -0,0 +1,62 @@ +package legacy + +import ( + "fmt" + + "github.com/hashicorp/terraform/state" + "github.com/hashicorp/terraform/state/remote" + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/mapstructure" +) + +// Backend is an implementation of backend.Backend for legacy remote state +// clients. +type Backend struct { + // Type is the type of remote state client to support + Type string + + // client is set after Configure is called and client is initialized. + client remote.Client +} + +func (b *Backend) Input( + ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { + // Return the config as-is, legacy doesn't support input + return c, nil +} + +func (b *Backend) Validate(*terraform.ResourceConfig) ([]string, []error) { + // No validation was supported for old clients + return nil, nil +} + +func (b *Backend) Configure(c *terraform.ResourceConfig) error { + // Legacy remote state was only map[string]string config + var conf map[string]string + if err := mapstructure.Decode(c.Raw, &conf); err != nil { + return fmt.Errorf( + "Failed to decode %q configuration: %s\n\n"+ + "This backend expects all configuration keys and values to be\n"+ + "strings. Please verify your configuration and try again.", + b.Type, err) + } + + client, err := remote.NewClient(b.Type, conf) + if err != nil { + return fmt.Errorf( + "Failed to configure remote backend %q: %s", + b.Type, err) + } + + // Set our client + b.client = client + return nil +} + +func (b *Backend) State() (state.State, error) { + if b.client == nil { + panic("State called with nil remote state client") + } + + return &remote.State{Client: b.client}, nil +} diff --git a/backend/legacy/backend_test.go b/backend/legacy/backend_test.go new file mode 100644 index 000000000..eceb48510 --- /dev/null +++ b/backend/legacy/backend_test.go @@ -0,0 +1,49 @@ +package legacy + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/state" + "github.com/hashicorp/terraform/terraform" +) + +func TestBackend_impl(t *testing.T) { + var _ backend.Backend = new(Backend) +} + +func TestBackend(t *testing.T) { + td, err := ioutil.TempDir("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + b := &Backend{Type: "local"} + conf := terraform.NewResourceConfig(config.TestRawConfig(t, map[string]interface{}{ + "path": filepath.Join(td, "data"), + })) + + // Config + if err := b.Configure(conf); err != nil { + t.Fatalf("err: %s", err) + } + + // Grab state + s, err := b.State() + if err != nil { + t.Fatalf("err: %s", err) + } + if s == nil { + t.Fatalf("state is nil") + } + + // Test it + s.WriteState(state.TestStateInitial()) + s.PersistState() + state.TestState(t, s) +} diff --git a/backend/legacy/legacy.go b/backend/legacy/legacy.go new file mode 100644 index 000000000..be3163bd5 --- /dev/null +++ b/backend/legacy/legacy.go @@ -0,0 +1,28 @@ +// Package legacy contains a backend implementation that can be used +// with the legacy remote state clients. +package legacy + +import ( + "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/state/remote" +) + +// Init updates the backend/init package map of initializers to support +// all the remote state types. +// +// If a type is already in the map, it will not be added. This will allow +// us to slowly convert the legacy types to first-class backends. +func Init(m map[string]func() backend.Backend) { + for k, _ := range remote.BuiltinClients { + if _, ok := m[k]; !ok { + // Copy the "k" value since the variable "k" is reused for + // each key (address doesn't change). + typ := k + + // Build the factory function to return a backend of typ + m[k] = func() backend.Backend { + return &Backend{Type: typ} + } + } + } +} diff --git a/backend/legacy/legacy_test.go b/backend/legacy/legacy_test.go new file mode 100644 index 000000000..77d81bf1a --- /dev/null +++ b/backend/legacy/legacy_test.go @@ -0,0 +1,34 @@ +package legacy + +import ( + "testing" + + "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/state/remote" +) + +func TestInit(t *testing.T) { + m := make(map[string]func() backend.Backend) + Init(m) + + for k, _ := range remote.BuiltinClients { + b, ok := m[k] + if !ok { + t.Fatalf("missing: %s", k) + } + + if typ := b().(*Backend).Type; typ != k { + t.Fatalf("bad type: %s", typ) + } + } +} + +func TestInit_ignoreExisting(t *testing.T) { + m := make(map[string]func() backend.Backend) + m["local"] = nil + Init(m) + + if v, ok := m["local"]; !ok || v != nil { + t.Fatalf("bad: %#v", m) + } +}