github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/backend/remote-state/s3/backend.go (about)

     1  package s3
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/aws/awserr"
     9  	"github.com/aws/aws-sdk-go/aws/session"
    10  	"github.com/aws/aws-sdk-go/service/dynamodb"
    11  	"github.com/aws/aws-sdk-go/service/s3"
    12  	cleanhttp "github.com/hashicorp/go-cleanhttp"
    13  	multierror "github.com/hashicorp/go-multierror"
    14  	"github.com/hashicorp/terraform/backend"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  
    17  	terraformAWS "github.com/hashicorp/terraform/builtin/providers/aws"
    18  )
    19  
    20  // New creates a new backend for S3 remote state.
    21  func New() backend.Backend {
    22  	s := &schema.Backend{
    23  		Schema: map[string]*schema.Schema{
    24  			"bucket": &schema.Schema{
    25  				Type:        schema.TypeString,
    26  				Required:    true,
    27  				Description: "The name of the S3 bucket",
    28  			},
    29  
    30  			"key": &schema.Schema{
    31  				Type:        schema.TypeString,
    32  				Required:    true,
    33  				Description: "The path to the state file inside the bucket",
    34  			},
    35  
    36  			"region": &schema.Schema{
    37  				Type:        schema.TypeString,
    38  				Required:    true,
    39  				Description: "The region of the S3 bucket.",
    40  				DefaultFunc: schema.EnvDefaultFunc("AWS_DEFAULT_REGION", nil),
    41  			},
    42  
    43  			"endpoint": &schema.Schema{
    44  				Type:        schema.TypeString,
    45  				Optional:    true,
    46  				Description: "A custom endpoint for the S3 API",
    47  				DefaultFunc: schema.EnvDefaultFunc("AWS_S3_ENDPOINT", ""),
    48  			},
    49  
    50  			"encrypt": &schema.Schema{
    51  				Type:        schema.TypeBool,
    52  				Optional:    true,
    53  				Description: "Whether to enable server side encryption of the state file",
    54  				Default:     false,
    55  			},
    56  
    57  			"acl": &schema.Schema{
    58  				Type:        schema.TypeString,
    59  				Optional:    true,
    60  				Description: "Canned ACL to be applied to the state file",
    61  				Default:     "",
    62  			},
    63  
    64  			"access_key": &schema.Schema{
    65  				Type:        schema.TypeString,
    66  				Optional:    true,
    67  				Description: "AWS access key",
    68  				Default:     "",
    69  			},
    70  
    71  			"secret_key": &schema.Schema{
    72  				Type:        schema.TypeString,
    73  				Optional:    true,
    74  				Description: "AWS secret key",
    75  				Default:     "",
    76  			},
    77  
    78  			"kms_key_id": &schema.Schema{
    79  				Type:        schema.TypeString,
    80  				Optional:    true,
    81  				Description: "The ARN of a KMS Key to use for encrypting the state",
    82  				Default:     "",
    83  			},
    84  
    85  			"lock_table": &schema.Schema{
    86  				Type:        schema.TypeString,
    87  				Optional:    true,
    88  				Description: "DynamoDB table for state locking",
    89  				Default:     "",
    90  			},
    91  
    92  			"profile": &schema.Schema{
    93  				Type:        schema.TypeString,
    94  				Optional:    true,
    95  				Description: "AWS profile name",
    96  				Default:     "",
    97  			},
    98  
    99  			"shared_credentials_file": &schema.Schema{
   100  				Type:        schema.TypeString,
   101  				Optional:    true,
   102  				Description: "Path to a shared credentials file",
   103  				Default:     "",
   104  			},
   105  
   106  			"token": &schema.Schema{
   107  				Type:        schema.TypeString,
   108  				Optional:    true,
   109  				Description: "MFA token",
   110  				Default:     "",
   111  			},
   112  
   113  			"role_arn": &schema.Schema{
   114  				Type:        schema.TypeString,
   115  				Optional:    true,
   116  				Description: "The role to be assumed",
   117  				Default:     "",
   118  			},
   119  		},
   120  	}
   121  
   122  	result := &Backend{Backend: s}
   123  	result.Backend.ConfigureFunc = result.configure
   124  	return result
   125  }
   126  
   127  type Backend struct {
   128  	*schema.Backend
   129  
   130  	// The fields below are set from configure
   131  	s3Client  *s3.S3
   132  	dynClient *dynamodb.DynamoDB
   133  
   134  	bucketName           string
   135  	keyName              string
   136  	serverSideEncryption bool
   137  	acl                  string
   138  	kmsKeyID             string
   139  	lockTable            string
   140  }
   141  
   142  func (b *Backend) configure(ctx context.Context) error {
   143  	if b.s3Client != nil {
   144  		return nil
   145  	}
   146  
   147  	// Grab the resource data
   148  	data := schema.FromContextBackendConfig(ctx)
   149  
   150  	b.bucketName = data.Get("bucket").(string)
   151  	b.keyName = data.Get("key").(string)
   152  	b.serverSideEncryption = data.Get("encrypt").(bool)
   153  	b.acl = data.Get("acl").(string)
   154  	b.kmsKeyID = data.Get("kms_key_id").(string)
   155  	b.lockTable = data.Get("lock_table").(string)
   156  
   157  	var errs []error
   158  	creds, err := terraformAWS.GetCredentials(&terraformAWS.Config{
   159  		AccessKey:     data.Get("access_key").(string),
   160  		SecretKey:     data.Get("secret_key").(string),
   161  		Token:         data.Get("token").(string),
   162  		Profile:       data.Get("profile").(string),
   163  		CredsFilename: data.Get("shared_credentials_file").(string),
   164  		AssumeRoleARN: data.Get("role_arn").(string),
   165  	})
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	// Call Get to check for credential provider. If nothing found, we'll get an
   171  	// error, and we can present it nicely to the user
   172  	_, err = creds.Get()
   173  	if err != nil {
   174  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
   175  			errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote.
   176  Please see https://www.terraform.io/docs/state/remote/s3.html for more information on
   177  providing credentials for the AWS S3 remote`))
   178  		} else {
   179  			errs = append(errs, fmt.Errorf("Error loading credentials for AWS S3 remote: %s", err))
   180  		}
   181  		return &multierror.Error{Errors: errs}
   182  	}
   183  
   184  	endpoint := data.Get("endpoint").(string)
   185  	region := data.Get("region").(string)
   186  
   187  	awsConfig := &aws.Config{
   188  		Credentials: creds,
   189  		Endpoint:    aws.String(endpoint),
   190  		Region:      aws.String(region),
   191  		HTTPClient:  cleanhttp.DefaultClient(),
   192  	}
   193  	sess := session.New(awsConfig)
   194  	b.s3Client = s3.New(sess)
   195  	b.dynClient = dynamodb.New(sess)
   196  
   197  	return nil
   198  }