backend/remote-state
This allows migration of the remote state implementations to a richer experience including input asking.
This commit is contained in:
parent
13c34b16e8
commit
1f5d425428
|
@ -0,0 +1,57 @@
|
||||||
|
// Package remotestate implements a Backend for remote state implementations
|
||||||
|
// from the state/remote package that also implement a backend schema for
|
||||||
|
// configuration.
|
||||||
|
package remotestate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backend implements backend.Backend for remote state backends.
|
||||||
|
//
|
||||||
|
// All exported fields should be set. This struct should only be used
|
||||||
|
// by implementers of backends, not by consumers. If you're consuming, please
|
||||||
|
// use a higher level package such as Consul backends.
|
||||||
|
type Backend struct {
|
||||||
|
// Backend should be set to the configuration schema. ConfigureFunc
|
||||||
|
// should not be set on the schema.
|
||||||
|
*schema.Backend
|
||||||
|
|
||||||
|
// ConfigureFunc takes the ctx from a schema.Backend and returns a
|
||||||
|
// fully configured remote client to use for state operations.
|
||||||
|
ConfigureFunc func(ctx context.Context) (remote.Client, error)
|
||||||
|
|
||||||
|
client remote.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) Configure(rc *terraform.ResourceConfig) error {
|
||||||
|
// Set our configureFunc manually
|
||||||
|
b.Backend.ConfigureFunc = func(ctx context.Context) error {
|
||||||
|
c, err := b.ConfigureFunc(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the client for later
|
||||||
|
b.client = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the normal configuration
|
||||||
|
return b.Backend.Configure(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) State() (state.State, error) {
|
||||||
|
// This shouldn't happen
|
||||||
|
if b.client == nil {
|
||||||
|
panic("nil remote client")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &remote.State{Client: b.client}
|
||||||
|
return s, nil
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package remotestate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackend_impl(t *testing.T) {
|
||||||
|
var _ backend.Backend = new(Backend)
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/backend/remote-state"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new backend for Consul remote state.
|
||||||
|
func New() backend.Backend {
|
||||||
|
return &remotestate.Backend{
|
||||||
|
ConfigureFunc: configure,
|
||||||
|
|
||||||
|
// Set the schema
|
||||||
|
Backend: &schema.Backend{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"path": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "Path to store state in Consul",
|
||||||
|
},
|
||||||
|
|
||||||
|
"access_token": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "Access token for a Consul ACL",
|
||||||
|
Default: "", // To prevent input
|
||||||
|
},
|
||||||
|
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "Address to the Consul Cluster",
|
||||||
|
},
|
||||||
|
|
||||||
|
"scheme": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "Scheme to communicate to Consul with",
|
||||||
|
Default: "", // To prevent input
|
||||||
|
},
|
||||||
|
|
||||||
|
"datacenter": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "Datacenter to communicate with",
|
||||||
|
Default: "", // To prevent input
|
||||||
|
},
|
||||||
|
|
||||||
|
"http_auth": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "HTTP Auth in the format of 'username:password'",
|
||||||
|
Default: "", // To prevent input
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(ctx context.Context) (remote.Client, error) {
|
||||||
|
// Grab the resource data
|
||||||
|
data := schema.FromContextBackendConfig(ctx)
|
||||||
|
|
||||||
|
// Configure the client
|
||||||
|
config := consulapi.DefaultConfig()
|
||||||
|
if v, ok := data.GetOk("access_token"); ok && v.(string) != "" {
|
||||||
|
config.Token = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := data.GetOk("address"); ok && v.(string) != "" {
|
||||||
|
config.Address = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := data.GetOk("scheme"); ok && v.(string) != "" {
|
||||||
|
config.Scheme = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := data.GetOk("datacenter"); ok && v.(string) != "" {
|
||||||
|
config.Datacenter = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := data.GetOk("http_auth"); ok && v.(string) != "" {
|
||||||
|
auth := v.(string)
|
||||||
|
|
||||||
|
var username, password string
|
||||||
|
if strings.Contains(auth, ":") {
|
||||||
|
split := strings.SplitN(auth, ":", 2)
|
||||||
|
username = split[0]
|
||||||
|
password = split[1]
|
||||||
|
} else {
|
||||||
|
username = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
config.HttpAuth = &consulapi.HttpBasicAuth{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := consulapi.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RemoteClient{
|
||||||
|
Client: client,
|
||||||
|
Path: data.Get("path").(string),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoteClient is a remote client that stores data in Consul.
|
||||||
|
type RemoteClient struct {
|
||||||
|
Client *consulapi.Client
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RemoteClient) Get() (*remote.Payload, error) {
|
||||||
|
pair, _, err := c.Client.KV().Get(c.Path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pair == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
md5 := md5.Sum(pair.Value)
|
||||||
|
return &remote.Payload{
|
||||||
|
Data: pair.Value,
|
||||||
|
MD5: md5[:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RemoteClient) Put(data []byte) error {
|
||||||
|
kv := c.Client.KV()
|
||||||
|
_, err := kv.Put(&consulapi.KVPair{
|
||||||
|
Key: c.Path,
|
||||||
|
Value: data,
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RemoteClient) Delete() error {
|
||||||
|
kv := c.Client.KV()
|
||||||
|
_, err := kv.Delete(c.Path, nil)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/backend/remote-state"
|
||||||
|
"github.com/hashicorp/terraform/helper/acctest"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoteClient_impl(t *testing.T) {
|
||||||
|
var _ remote.Client = new(RemoteClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoteClient(t *testing.T) {
|
||||||
|
acctest.RemoteTestPrecheck(t)
|
||||||
|
|
||||||
|
// Get the backend
|
||||||
|
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||||
|
"address": "demo.consul.io:80",
|
||||||
|
"path": fmt.Sprintf("tf-unit/%s", time.Now().String()),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test
|
||||||
|
remotestate.TestClient(t, b)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package remotestate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T, raw backend.Backend) {
|
||||||
|
b, ok := raw.(*Backend)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("not Backend: %T", raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
remote.TestClient(t, b.client)
|
||||||
|
}
|
Loading…
Reference in New Issue