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 }