move s3 config from client to backend

The RemoteClient needs to be configured for the named state, so move the
general config to the backend.

Rename some fields for consistency.
This commit is contained in:
James Bardin 2017-03-22 15:52:55 -04:00
parent ef94acbf1f
commit 4980fa20e7
6 changed files with 115 additions and 72 deletions

View File

@ -56,7 +56,7 @@ func (b *Backend) States() ([]string, error) {
} }
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteState(name string) error {
if name == backend.DefaultStateName { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }

View File

@ -128,25 +128,31 @@ type Backend struct {
*schema.Backend *schema.Backend
// The fields below are set from configure // The fields below are set from configure
client *S3Client s3Client *s3.S3
dynClient *dynamodb.DynamoDB
bucketName string
keyName string
serverSideEncryption bool
acl string
kmsKeyID string
lockTable string
} }
func (b *Backend) configure(ctx context.Context) error { func (b *Backend) configure(ctx context.Context) error {
if b.client != nil { if b.s3Client != nil {
return nil return nil
} }
// Grab the resource data // Grab the resource data
data := schema.FromContextBackendConfig(ctx) data := schema.FromContextBackendConfig(ctx)
bucketName := data.Get("bucket").(string) b.bucketName = data.Get("bucket").(string)
keyName := data.Get("key").(string) b.keyName = data.Get("key").(string)
endpoint := data.Get("endpoint").(string) b.serverSideEncryption = data.Get("encrypt").(bool)
region := data.Get("region").(string) b.acl = data.Get("acl").(string)
serverSideEncryption := data.Get("encrypt").(bool) b.kmsKeyID = data.Get("kms_key_id").(string)
acl := data.Get("acl").(string) b.lockTable = data.Get("lock_table").(string)
kmsKeyID := data.Get("kms_key_id").(string)
lockTable := data.Get("lock_table").(string)
var errs []error var errs []error
creds, err := terraformAWS.GetCredentials(&terraformAWS.Config{ creds, err := terraformAWS.GetCredentials(&terraformAWS.Config{
@ -175,6 +181,9 @@ providing credentials for the AWS S3 remote`))
return &multierror.Error{Errors: errs} return &multierror.Error{Errors: errs}
} }
endpoint := data.Get("endpoint").(string)
region := data.Get("region").(string)
awsConfig := &aws.Config{ awsConfig := &aws.Config{
Credentials: creds, Credentials: creds,
Endpoint: aws.String(endpoint), Endpoint: aws.String(endpoint),
@ -182,18 +191,8 @@ providing credentials for the AWS S3 remote`))
HTTPClient: cleanhttp.DefaultClient(), HTTPClient: cleanhttp.DefaultClient(),
} }
sess := session.New(awsConfig) sess := session.New(awsConfig)
nativeClient := s3.New(sess) b.s3Client = s3.New(sess)
dynClient := dynamodb.New(sess) b.dynClient = dynamodb.New(sess)
b.client = &S3Client{
nativeClient: nativeClient,
bucketName: bucketName,
keyName: keyName,
serverSideEncryption: serverSideEncryption,
acl: acl,
kmsKeyID: kmsKeyID,
dynClient: dynClient,
lockTable: lockTable,
}
return nil return nil
} }

View File

@ -1,13 +1,18 @@
package s3 package s3
import ( import (
"fmt"
"strings"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/state/remote"
) )
const ( const (
keyEnvPrefix = "-env:" // This will be used a directory name, the odd looking colon is to reduce
// the chance of name conflicts with existing deployments.
keyEnvPrefix = "env:"
) )
func (b *Backend) States() ([]string, error) { func (b *Backend) States() ([]string, error) {
@ -16,6 +21,20 @@ func (b *Backend) States() ([]string, error) {
func (b *Backend) DeleteState(name string) error { func (b *Backend) DeleteState(name string) error {
return backend.ErrNamedStatesNotSupported return backend.ErrNamedStatesNotSupported
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
//params := &s3.ListObjectsInput{
// Bucket: &b.client.bucketName,
// Delimiter: aws.String("Delimiter"),
// EncodingType: aws.String("EncodingType"),
// Marker: aws.String("Marker"),
// MaxKeys: aws.Int64(1),
// Prefix: aws.String("env"),
// RequestPayer: aws.String("RequestPayer"),
//}
return nil
} }
func (b *Backend) State(name string) (state.State, error) { func (b *Backend) State(name string) (state.State, error) {
@ -23,5 +42,30 @@ func (b *Backend) State(name string) (state.State, error) {
return nil, backend.ErrNamedStatesNotSupported return nil, backend.ErrNamedStatesNotSupported
} }
return &remote.State{Client: b.client}, nil client := &RemoteClient{
s3Client: b.s3Client,
dynClient: b.dynClient,
bucketName: b.bucketName,
path: b.path(name),
serverSideEncryption: b.serverSideEncryption,
acl: b.acl,
kmsKeyID: b.kmsKeyID,
lockTable: b.lockTable,
}
// TODO: create new state if it doesn't exist
return &remote.State{Client: client}, nil
}
func (b *Backend) client() *RemoteClient {
return &RemoteClient{}
}
func (b *Backend) path(name string) string {
if name == backend.DefaultStateName {
return b.keyName
}
return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/")
} }

View File

@ -44,17 +44,17 @@ func TestBackendConfig(t *testing.T) {
b := backend.TestBackendConfig(t, New(), config).(*Backend) b := backend.TestBackendConfig(t, New(), config).(*Backend)
if *b.client.nativeClient.Config.Region != "us-west-1" { if *b.s3Client.Config.Region != "us-west-1" {
t.Fatalf("Incorrect region was populated") t.Fatalf("Incorrect region was populated")
} }
if b.client.bucketName != "tf-test" { if b.bucketName != "tf-test" {
t.Fatalf("Incorrect bucketName was populated") t.Fatalf("Incorrect bucketName was populated")
} }
if b.client.keyName != "state" { if b.keyName != "state" {
t.Fatalf("Incorrect keyName was populated") t.Fatalf("Incorrect keyName was populated")
} }
credentials, err := b.client.nativeClient.Config.Credentials.Get() credentials, err := b.s3Client.Config.Credentials.Get()
if err != nil { if err != nil {
t.Fatalf("Error when requesting credentials") t.Fatalf("Error when requesting credentials")
} }
@ -78,8 +78,8 @@ func TestBackend(t *testing.T) {
"encrypt": true, "encrypt": true,
}).(*Backend) }).(*Backend)
createS3Bucket(t, b.client, bucketName) createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.client, bucketName) defer deleteS3Bucket(t, b.s3Client, bucketName)
backend.TestBackend(t, b, nil) backend.TestBackend(t, b, nil)
} }
@ -104,41 +104,41 @@ func TestBackendLocked(t *testing.T) {
"lock_table": bucketName, "lock_table": bucketName,
}).(*Backend) }).(*Backend)
createS3Bucket(t, b1.client, bucketName) createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.client, bucketName) defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.client, bucketName) createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.client, bucketName) defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
backend.TestBackend(t, b1, b2) backend.TestBackend(t, b1, b2)
} }
func createS3Bucket(t *testing.T, c *S3Client, bucketName string) { func createS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
createBucketReq := &s3.CreateBucketInput{ createBucketReq := &s3.CreateBucketInput{
Bucket: &bucketName, Bucket: &bucketName,
} }
// Be clear about what we're doing in case the user needs to clean // Be clear about what we're doing in case the user needs to clean
// this up later. // this up later.
t.Logf("creating S3 bucket %s in %s", bucketName, *c.nativeClient.Config.Region) t.Logf("creating S3 bucket %s in %s", bucketName, *s3Client.Config.Region)
_, err := c.nativeClient.CreateBucket(createBucketReq) _, err := s3Client.CreateBucket(createBucketReq)
if err != nil { if err != nil {
t.Fatal("failed to create test S3 bucket:", err) t.Fatal("failed to create test S3 bucket:", err)
} }
} }
func deleteS3Bucket(t *testing.T, c *S3Client, bucketName string) { func deleteS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
deleteBucketReq := &s3.DeleteBucketInput{ deleteBucketReq := &s3.DeleteBucketInput{
Bucket: &bucketName, Bucket: &bucketName,
} }
_, err := c.nativeClient.DeleteBucket(deleteBucketReq) _, err := s3Client.DeleteBucket(deleteBucketReq)
if err != nil { if err != nil {
t.Logf("WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)", err) t.Logf("WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)", err)
} }
} }
// create the dynamoDB table, and wait until we can query it. // create the dynamoDB table, and wait until we can query it.
func createDynamoDBTable(t *testing.T, c *S3Client, tableName string) { func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
createInput := &dynamodb.CreateTableInput{ createInput := &dynamodb.CreateTableInput{
AttributeDefinitions: []*dynamodb.AttributeDefinition{ AttributeDefinitions: []*dynamodb.AttributeDefinition{
{ {
@ -159,7 +159,7 @@ func createDynamoDBTable(t *testing.T, c *S3Client, tableName string) {
TableName: aws.String(tableName), TableName: aws.String(tableName),
} }
_, err := c.dynClient.CreateTable(createInput) _, err := dynClient.CreateTable(createInput)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -173,7 +173,7 @@ func createDynamoDBTable(t *testing.T, c *S3Client, tableName string) {
} }
for { for {
resp, err := c.dynClient.DescribeTable(describeInput) resp, err := dynClient.DescribeTable(describeInput)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -191,11 +191,11 @@ func createDynamoDBTable(t *testing.T, c *S3Client, tableName string) {
} }
func deleteDynamoDBTable(t *testing.T, c *S3Client, tableName string) { func deleteDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
params := &dynamodb.DeleteTableInput{ params := &dynamodb.DeleteTableInput{
TableName: aws.String(tableName), TableName: aws.String(tableName),
} }
_, err := c.dynClient.DeleteTable(params) _, err := dynClient.DeleteTable(params)
if err != nil { if err != nil {
t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err) t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err)
} }

View File

@ -17,21 +17,21 @@ import (
"github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/state/remote"
) )
type S3Client struct { type RemoteClient struct {
nativeClient *s3.S3 s3Client *s3.S3
dynClient *dynamodb.DynamoDB
bucketName string bucketName string
keyName string path string
serverSideEncryption bool serverSideEncryption bool
acl string acl string
kmsKeyID string kmsKeyID string
dynClient *dynamodb.DynamoDB
lockTable string lockTable string
} }
func (c *S3Client) Get() (*remote.Payload, error) { func (c *RemoteClient) Get() (*remote.Payload, error) {
output, err := c.nativeClient.GetObject(&s3.GetObjectInput{ output, err := c.s3Client.GetObject(&s3.GetObjectInput{
Bucket: &c.bucketName, Bucket: &c.bucketName,
Key: &c.keyName, Key: &c.path,
}) })
if err != nil { if err != nil {
@ -65,7 +65,7 @@ func (c *S3Client) Get() (*remote.Payload, error) {
return payload, nil return payload, nil
} }
func (c *S3Client) Put(data []byte) error { func (c *RemoteClient) Put(data []byte) error {
contentType := "application/json" contentType := "application/json"
contentLength := int64(len(data)) contentLength := int64(len(data))
@ -74,7 +74,7 @@ func (c *S3Client) Put(data []byte) error {
ContentLength: &contentLength, ContentLength: &contentLength,
Body: bytes.NewReader(data), Body: bytes.NewReader(data),
Bucket: &c.bucketName, Bucket: &c.bucketName,
Key: &c.keyName, Key: &c.path,
} }
if c.serverSideEncryption { if c.serverSideEncryption {
@ -92,28 +92,28 @@ func (c *S3Client) Put(data []byte) error {
log.Printf("[DEBUG] Uploading remote state to S3: %#v", i) log.Printf("[DEBUG] Uploading remote state to S3: %#v", i)
if _, err := c.nativeClient.PutObject(i); err == nil { if _, err := c.s3Client.PutObject(i); err == nil {
return nil return nil
} else { } else {
return fmt.Errorf("Failed to upload state: %v", err) return fmt.Errorf("Failed to upload state: %v", err)
} }
} }
func (c *S3Client) Delete() error { func (c *RemoteClient) Delete() error {
_, err := c.nativeClient.DeleteObject(&s3.DeleteObjectInput{ _, err := c.s3Client.DeleteObject(&s3.DeleteObjectInput{
Bucket: &c.bucketName, Bucket: &c.bucketName,
Key: &c.keyName, Key: &c.path,
}) })
return err return err
} }
func (c *S3Client) Lock(info *state.LockInfo) (string, error) { func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
if c.lockTable == "" { if c.lockTable == "" {
return "", nil return "", nil
} }
stateName := fmt.Sprintf("%s/%s", c.bucketName, c.keyName) stateName := fmt.Sprintf("%s/%s", c.bucketName, c.path)
info.Path = stateName info.Path = stateName
if info.ID == "" { if info.ID == "" {
@ -150,10 +150,10 @@ func (c *S3Client) Lock(info *state.LockInfo) (string, error) {
return info.ID, nil return info.ID, nil
} }
func (c *S3Client) getLockInfo() (*state.LockInfo, error) { func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) {
getParams := &dynamodb.GetItemInput{ getParams := &dynamodb.GetItemInput{
Key: map[string]*dynamodb.AttributeValue{ Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.keyName))}, "LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.path))},
}, },
ProjectionExpression: aws.String("LockID, Info"), ProjectionExpression: aws.String("LockID, Info"),
TableName: aws.String(c.lockTable), TableName: aws.String(c.lockTable),
@ -178,7 +178,7 @@ func (c *S3Client) getLockInfo() (*state.LockInfo, error) {
return lockInfo, nil return lockInfo, nil
} }
func (c *S3Client) Unlock(id string) error { func (c *RemoteClient) Unlock(id string) error {
if c.lockTable == "" { if c.lockTable == "" {
return nil return nil
} }
@ -202,7 +202,7 @@ func (c *S3Client) Unlock(id string) error {
params := &dynamodb.DeleteItemInput{ params := &dynamodb.DeleteItemInput{
Key: map[string]*dynamodb.AttributeValue{ Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.keyName))}, "LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.path))},
}, },
TableName: aws.String(c.lockTable), TableName: aws.String(c.lockTable),
} }

View File

@ -10,8 +10,8 @@ import (
) )
func TestRemoteClient_impl(t *testing.T) { func TestRemoteClient_impl(t *testing.T) {
var _ remote.Client = new(S3Client) var _ remote.Client = new(RemoteClient)
var _ remote.ClientLocker = new(S3Client) var _ remote.ClientLocker = new(RemoteClient)
} }
func TestRemoteClient(t *testing.T) { func TestRemoteClient(t *testing.T) {
@ -31,8 +31,8 @@ func TestRemoteClient(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
createS3Bucket(t, b.client, bucketName) createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.client, bucketName) defer deleteS3Bucket(t, b.s3Client, bucketName)
remote.TestClient(t, state.(*remote.State).Client) remote.TestClient(t, state.(*remote.State).Client)
} }
@ -67,10 +67,10 @@ func TestRemoteClientLocks(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
createS3Bucket(t, b1.client, bucketName) createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.client, bucketName) defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.client, bucketName) createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.client, bucketName) defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client) remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
} }