From 2df130edece0393fb0233dd08683bec10a8fa8a5 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 16 Mar 2018 19:30:07 -0400 Subject: [PATCH] move legacy etcd remote state to a backend --- backend/init/init.go | 2 + backend/remote-state/etcdv2/backend.go | 96 +++++++++++++++++++ backend/remote-state/etcdv2/backend_test.go | 11 +++ .../remote-state/etcdv2/client.go | 42 +------- .../remote-state/etcdv2/client_test.go | 15 ++- state/remote/remote.go | 1 - 6 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 backend/remote-state/etcdv2/backend.go create mode 100644 backend/remote-state/etcdv2/backend_test.go rename state/remote/etcd.go => backend/remote-state/etcdv2/client.go (53%) rename state/remote/etcd_test.go => backend/remote-state/etcdv2/client_test.go (62%) diff --git a/backend/init/init.go b/backend/init/init.go index d499968c74..2122454b34 100644 --- a/backend/init/init.go +++ b/backend/init/init.go @@ -13,6 +13,7 @@ import ( backendlocal "github.com/hashicorp/terraform/backend/local" backendAzure "github.com/hashicorp/terraform/backend/remote-state/azure" backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul" + backendetcdv2 "github.com/hashicorp/terraform/backend/remote-state/etcdv2" backendetcdv3 "github.com/hashicorp/terraform/backend/remote-state/etcdv3" backendGCS "github.com/hashicorp/terraform/backend/remote-state/gcs" backendinmem "github.com/hashicorp/terraform/backend/remote-state/inmem" @@ -48,6 +49,7 @@ func init() { "azure": deprecateBackend(backendAzure.New(), `Warning: "azure" name is deprecated, please use "azurerm"`), "azurerm": func() backend.Backend { return backendAzure.New() }, + "etcd": func() backend.Backend { return backendetcdv2.New() }, "etcdv3": func() backend.Backend { return backendetcdv3.New() }, "gcs": func() backend.Backend { return backendGCS.New() }, "manta": func() backend.Backend { return backendManta.New() }, diff --git a/backend/remote-state/etcdv2/backend.go b/backend/remote-state/etcdv2/backend.go new file mode 100644 index 0000000000..231175ea3e --- /dev/null +++ b/backend/remote-state/etcdv2/backend.go @@ -0,0 +1,96 @@ +// legacy etcd2.x backend + +package etcdv2 + +import ( + "context" + "strings" + + etcdapi "github.com/coreos/etcd/client" + "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/state" + "github.com/hashicorp/terraform/state/remote" +) + +func New() backend.Backend { + s := &schema.Backend{ + Schema: map[string]*schema.Schema{ + "path": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The path where to store the state", + }, + "endpoints": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "A space-separated list of the etcd endpoints", + }, + "username": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Username", + }, + "password": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Password", + }, + }, + } + + result := &Backend{Backend: s} + result.Backend.ConfigureFunc = result.configure + return result +} + +type Backend struct { + *schema.Backend + + client etcdapi.Client + path string +} + +func (b *Backend) configure(ctx context.Context) error { + data := schema.FromContextBackendConfig(ctx) + + b.path = data.Get("path").(string) + + endpoints := data.Get("endpoints").(string) + username := data.Get("username").(string) + password := data.Get("password").(string) + + config := etcdapi.Config{ + Endpoints: strings.Split(endpoints, " "), + Username: username, + Password: password, + } + + client, err := etcdapi.New(config) + if err != nil { + return err + } + + b.client = client + return nil +} + +func (b *Backend) States() ([]string, error) { + return nil, backend.ErrNamedStatesNotSupported +} + +func (b *Backend) DeleteState(string) error { + return backend.ErrNamedStatesNotSupported +} + +func (b *Backend) State(name string) (state.State, error) { + if name != backend.DefaultStateName { + return nil, backend.ErrNamedStatesNotSupported + } + return &remote.State{ + Client: &EtcdClient{ + Client: b.client, + Path: b.path, + }, + }, nil +} diff --git a/backend/remote-state/etcdv2/backend_test.go b/backend/remote-state/etcdv2/backend_test.go new file mode 100644 index 0000000000..c992f624e6 --- /dev/null +++ b/backend/remote-state/etcdv2/backend_test.go @@ -0,0 +1,11 @@ +package etcdv2 + +import ( + "testing" + + "github.com/hashicorp/terraform/backend" +) + +func TestBackend_impl(t *testing.T) { + var _ backend.Backend = new(Backend) +} diff --git a/state/remote/etcd.go b/backend/remote-state/etcdv2/client.go similarity index 53% rename from state/remote/etcd.go rename to backend/remote-state/etcdv2/client.go index 7993603ff2..faf891ac97 100644 --- a/state/remote/etcd.go +++ b/backend/remote-state/etcdv2/client.go @@ -1,53 +1,21 @@ -package remote +package etcdv2 import ( + "context" "crypto/md5" "fmt" - "strings" etcdapi "github.com/coreos/etcd/client" - "golang.org/x/net/context" + "github.com/hashicorp/terraform/state/remote" ) -func etcdFactory(conf map[string]string) (Client, error) { - path, ok := conf["path"] - if !ok { - return nil, fmt.Errorf("missing 'path' configuration") - } - - endpoints, ok := conf["endpoints"] - if !ok || endpoints == "" { - return nil, fmt.Errorf("missing 'endpoints' configuration") - } - - config := etcdapi.Config{ - Endpoints: strings.Split(endpoints, " "), - } - if username, ok := conf["username"]; ok && username != "" { - config.Username = username - } - if password, ok := conf["password"]; ok && password != "" { - config.Password = password - } - - client, err := etcdapi.New(config) - if err != nil { - return nil, err - } - - return &EtcdClient{ - Client: client, - Path: path, - }, nil -} - // EtcdClient is a remote client that stores data in etcd. type EtcdClient struct { Client etcdapi.Client Path string } -func (c *EtcdClient) Get() (*Payload, error) { +func (c *EtcdClient) Get() (*remote.Payload, error) { resp, err := etcdapi.NewKeysAPI(c.Client).Get(context.Background(), c.Path, &etcdapi.GetOptions{Quorum: true}) if err != nil { if err, ok := err.(etcdapi.Error); ok && err.Code == etcdapi.ErrorCodeKeyNotFound { @@ -61,7 +29,7 @@ func (c *EtcdClient) Get() (*Payload, error) { data := []byte(resp.Node.Value) md5 := md5.Sum(data) - return &Payload{ + return &remote.Payload{ Data: data, MD5: md5[:], }, nil diff --git a/state/remote/etcd_test.go b/backend/remote-state/etcdv2/client_test.go similarity index 62% rename from state/remote/etcd_test.go rename to backend/remote-state/etcdv2/client_test.go index 6d06d801b2..e37b5753be 100644 --- a/state/remote/etcd_test.go +++ b/backend/remote-state/etcdv2/client_test.go @@ -1,14 +1,17 @@ -package remote +package etcdv2 import ( "fmt" "os" "testing" "time" + + "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/state/remote" ) func TestEtcdClient_impl(t *testing.T) { - var _ Client = new(EtcdClient) + var _ remote.Client = new(EtcdClient) } func TestEtcdClient(t *testing.T) { @@ -17,7 +20,8 @@ func TestEtcdClient(t *testing.T) { t.Skipf("skipping; ETCD_ENDPOINT must be set") } - config := map[string]string{ + // Get the backend + config := map[string]interface{}{ "endpoints": endpoint, "path": fmt.Sprintf("tf-unit/%s", time.Now().String()), } @@ -29,10 +33,11 @@ func TestEtcdClient(t *testing.T) { config["password"] = password } - client, err := etcdFactory(config) + b := backend.TestBackendConfig(t, New(), config) + state, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("Error for valid config: %s", err) } - testClient(t, client) + remote.TestClient(t, state.(*remote.State).Client) } diff --git a/state/remote/remote.go b/state/remote/remote.go index 33bdea559b..3048dcf629 100644 --- a/state/remote/remote.go +++ b/state/remote/remote.go @@ -46,7 +46,6 @@ func NewClient(t string, conf map[string]string) (Client, error) { // NewClient. var BuiltinClients = map[string]Factory{ "artifactory": artifactoryFactory, - "etcd": etcdFactory, "http": httpFactory, "local": fileFactory, }