diff --git a/backend/remote-state/gcs/backend.go b/backend/remote-state/gcs/backend.go index 1491a9b6d..12e8d43ed 100644 --- a/backend/remote-state/gcs/backend.go +++ b/backend/remote-state/gcs/backend.go @@ -3,14 +3,17 @@ package gcs import ( "context" + "encoding/json" "fmt" "os" "strings" "cloud.google.com/go/storage" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" + "golang.org/x/oauth2/jwt" "google.golang.org/api/option" ) @@ -111,16 +114,39 @@ func (b *gcsBackend) configure(ctx context.Context) error { b.region = r } - opts := []option.ClientOption{ - option.WithScopes(storage.ScopeReadWrite), - option.WithUserAgent(terraform.UserAgentString()), - } - if credentialsFile := data.Get("credentials").(string); credentialsFile != "" { - opts = append(opts, option.WithCredentialsFile(credentialsFile)) - } else if credentialsFile := os.Getenv("GOOGLE_CREDENTIALS"); credentialsFile != "" { - opts = append(opts, option.WithCredentialsFile(credentialsFile)) + var opts []option.ClientOption + + creds := data.Get("credentials").(string) + if creds == "" { + creds = os.Getenv("GOOGLE_CREDENTIALS") } + if creds != "" { + var account accountFile + + // to mirror how the provider works, we accept the file path or the contents + contents, _, err := pathorcontents.Read(creds) + if err != nil { + return fmt.Errorf("Error loading credentials: %s", err) + } + + if err := json.Unmarshal([]byte(contents), &account); err != nil { + return fmt.Errorf("Error parsing credentials '%s': %s", contents, err) + } + + conf := jwt.Config{ + Email: account.ClientEmail, + PrivateKey: []byte(account.PrivateKey), + Scopes: []string{storage.ScopeReadWrite}, + TokenURL: "https://accounts.google.com/o/oauth2/token", + } + + opts = append(opts, option.WithHTTPClient(conf.Client(ctx))) + } else { + opts = append(opts, option.WithScopes(storage.ScopeReadWrite)) + } + + opts = append(opts, option.WithUserAgent(terraform.UserAgentString())) client, err := storage.NewClient(b.storageContext, opts...) if err != nil { return fmt.Errorf("storage.NewClient() failed: %v", err) @@ -130,3 +156,11 @@ func (b *gcsBackend) configure(ctx context.Context) error { return nil } + +// accountFile represents the structure of the account file JSON file. +type accountFile struct { + PrivateKeyId string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + ClientEmail string `json:"client_email"` + ClientId string `json:"client_id"` +} diff --git a/backend/remote-state/gcs/backend_test.go b/backend/remote-state/gcs/backend_test.go index 2eff3474a..dc8649c95 100644 --- a/backend/remote-state/gcs/backend_test.go +++ b/backend/remote-state/gcs/backend_test.go @@ -144,13 +144,6 @@ func setupBackend(t *testing.T, bucket, prefix string) backend.Backend { "prefix": prefix, } - if creds := os.Getenv("GOOGLE_CREDENTIALS"); creds != "" { - config["credentials"] = creds - t.Logf("using credentials from %q", creds) - } else { - t.Log("using default credentials; set GOOGLE_CREDENTIALS for custom credentials") - } - b := backend.TestBackendConfig(t, New(), config) be := b.(*gcsBackend)