github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/backend/remote-state/gcs/backend.go (about) 1 // Package gcs implements remote storage of state on Google Cloud Storage (GCS). 2 package gcs 3 4 import ( 5 "context" 6 "fmt" 7 "os" 8 "strings" 9 10 "cloud.google.com/go/storage" 11 "github.com/hashicorp/terraform/backend" 12 "github.com/hashicorp/terraform/helper/schema" 13 "github.com/hashicorp/terraform/terraform" 14 "google.golang.org/api/option" 15 ) 16 17 // gcsBackend implements "backend".Backend for GCS. 18 // Input(), Validate() and Configure() are implemented by embedding *schema.Backend. 19 // State(), DeleteState() and States() are implemented explicitly. 20 type gcsBackend struct { 21 *schema.Backend 22 23 storageClient *storage.Client 24 storageContext context.Context 25 26 bucketName string 27 prefix string 28 defaultStateFile string 29 30 projectID string 31 region string 32 } 33 34 func New() backend.Backend { 35 be := &gcsBackend{} 36 be.Backend = &schema.Backend{ 37 ConfigureFunc: be.configure, 38 Schema: map[string]*schema.Schema{ 39 "bucket": { 40 Type: schema.TypeString, 41 Required: true, 42 Description: "The name of the Google Cloud Storage bucket", 43 }, 44 45 "path": { 46 Type: schema.TypeString, 47 Optional: true, 48 Description: "Path of the default state file", 49 Deprecated: "Use the \"prefix\" option instead", 50 }, 51 52 "prefix": { 53 Type: schema.TypeString, 54 Optional: true, 55 Description: "The directory where state files will be saved inside the bucket", 56 }, 57 58 "credentials": { 59 Type: schema.TypeString, 60 Optional: true, 61 Description: "Google Cloud JSON Account Key", 62 Default: "", 63 }, 64 65 "project": { 66 Type: schema.TypeString, 67 Optional: true, 68 Description: "Google Cloud Project ID", 69 Default: "", 70 }, 71 72 "region": { 73 Type: schema.TypeString, 74 Optional: true, 75 Description: "Region / location in which to create the bucket", 76 Default: "", 77 }, 78 }, 79 } 80 81 return be 82 } 83 84 func (b *gcsBackend) configure(ctx context.Context) error { 85 if b.storageClient != nil { 86 return nil 87 } 88 89 // ctx is a background context with the backend config added. 90 // Since no context is passed to remoteClient.Get(), .Lock(), etc. but 91 // one is required for calling the GCP API, we're holding on to this 92 // context here and re-use it later. 93 b.storageContext = ctx 94 95 data := schema.FromContextBackendConfig(b.storageContext) 96 97 b.bucketName = data.Get("bucket").(string) 98 b.prefix = strings.TrimLeft(data.Get("prefix").(string), "/") 99 100 b.defaultStateFile = strings.TrimLeft(data.Get("path").(string), "/") 101 102 b.projectID = data.Get("project").(string) 103 if id := os.Getenv("GOOGLE_PROJECT"); b.projectID == "" && id != "" { 104 b.projectID = id 105 } 106 b.region = data.Get("region").(string) 107 if r := os.Getenv("GOOGLE_REGION"); b.projectID == "" && r != "" { 108 b.region = r 109 } 110 111 opts := []option.ClientOption{ 112 option.WithScopes(storage.ScopeReadWrite), 113 option.WithUserAgent(terraform.UserAgentString()), 114 } 115 if credentialsFile := data.Get("credentials").(string); credentialsFile != "" { 116 opts = append(opts, option.WithCredentialsFile(credentialsFile)) 117 } else if credentialsFile := os.Getenv("GOOGLE_CREDENTIALS"); credentialsFile != "" { 118 opts = append(opts, option.WithCredentialsFile(credentialsFile)) 119 } 120 121 client, err := storage.NewClient(b.storageContext, opts...) 122 if err != nil { 123 return fmt.Errorf("storage.NewClient() failed: %v", err) 124 } 125 126 b.storageClient = client 127 128 return b.ensureBucketExists() 129 } 130 131 func (b *gcsBackend) ensureBucketExists() error { 132 _, err := b.storageClient.Bucket(b.bucketName).Attrs(b.storageContext) 133 if err != storage.ErrBucketNotExist { 134 return err 135 } 136 137 if b.projectID == "" { 138 return fmt.Errorf("bucket %q does not exist; specify the \"project\" option or create the bucket manually using `gsutil mb gs://%s`", b.bucketName, b.bucketName) 139 } 140 141 attrs := &storage.BucketAttrs{ 142 Location: b.region, 143 } 144 145 return b.storageClient.Bucket(b.bucketName).Create(b.storageContext, b.projectID, attrs) 146 }