package s3 import ( "context" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/s3" cleanhttp "github.com/hashicorp/go-cleanhttp" multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/helper/schema" terraformAWS "github.com/hashicorp/terraform/builtin/providers/aws" ) // New creates a new backend for S3 remote state. func New() backend.Backend { s := &schema.Backend{ Schema: map[string]*schema.Schema{ "bucket": &schema.Schema{ Type: schema.TypeString, Required: true, Description: "The name of the S3 bucket", }, "key": &schema.Schema{ Type: schema.TypeString, Required: true, Description: "The path to the state file inside the bucket", }, "region": &schema.Schema{ Type: schema.TypeString, Required: true, Description: "The region of the S3 bucket.", DefaultFunc: schema.EnvDefaultFunc("AWS_DEFAULT_REGION", nil), }, "endpoint": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "A custom endpoint for the S3 API", DefaultFunc: schema.EnvDefaultFunc("AWS_S3_ENDPOINT", ""), }, "encrypt": &schema.Schema{ Type: schema.TypeBool, Optional: true, Description: "Whether to enable server side encryption of the state file", Default: false, }, "acl": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "Canned ACL to be applied to the state file", Default: "", }, "access_key": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "AWS access key", Default: "", }, "secret_key": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "AWS secret key", Default: "", }, "kms_key_id": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "The ARN of a KMS Key to use for encrypting the state", Default: "", }, "lock_table": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "DynamoDB table for state locking", Default: "", }, "profile": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "AWS profile name", Default: "", }, "shared_credentials_file": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "Path to a shared credentials file", Default: "", }, "token": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "MFA token", Default: "", }, "role_arn": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "The role to be assumed", Default: "", }, }, } result := &Backend{Backend: s} result.Backend.ConfigureFunc = result.configure return result } type Backend struct { *schema.Backend // The fields below are set from configure client *S3Client } func (b *Backend) configure(ctx context.Context) error { if b.client != nil { return nil } // Grab the resource data data := schema.FromContextBackendConfig(ctx) bucketName := data.Get("bucket").(string) keyName := data.Get("key").(string) endpoint := data.Get("endpoint").(string) region := data.Get("region").(string) serverSideEncryption := data.Get("encrypt").(bool) acl := data.Get("acl").(string) kmsKeyID := data.Get("kms_key_id").(string) lockTable := data.Get("lock_table").(string) var errs []error creds, err := terraformAWS.GetCredentials(&terraformAWS.Config{ AccessKey: data.Get("access_key").(string), SecretKey: data.Get("secret_key").(string), Token: data.Get("token").(string), Profile: data.Get("profile").(string), CredsFilename: data.Get("shared_credentials_file").(string), AssumeRoleARN: data.Get("role_arn").(string), }) if err != nil { return err } // Call Get to check for credential provider. If nothing found, we'll get an // error, and we can present it nicely to the user _, err = creds.Get() if err != nil { if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" { errs = append(errs, fmt.Errorf(`No valid credential sources found for AWS S3 remote. Please see https://www.terraform.io/docs/state/remote/s3.html for more information on providing credentials for the AWS S3 remote`)) } else { errs = append(errs, fmt.Errorf("Error loading credentials for AWS S3 remote: %s", err)) } return &multierror.Error{Errors: errs} } awsConfig := &aws.Config{ Credentials: creds, Endpoint: aws.String(endpoint), Region: aws.String(region), HTTPClient: cleanhttp.DefaultClient(), } sess := session.New(awsConfig) nativeClient := s3.New(sess) 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 }