package filestate import ( "context" "encoding/json" "os" "github.com/pulumi/pulumi/pkg/diag" "github.com/pulumi/pulumi/pkg/util/cmdutil" "golang.org/x/oauth2/google" "gocloud.dev/blob/gcsblob" "cloud.google.com/go/storage" "github.com/pkg/errors" "gocloud.dev/blob" "gocloud.dev/gcp" ) type GoogleCredentials struct { PrivateKeyID string `json:"private_key_id"` PrivateKey string `json:"private_key"` ClientEmail string `json:"client_email"` ClientID string `json:"client_id"` } func googleCredentials(ctx context.Context) (*google.Credentials, error) { // GOOGLE_CREDENTIALS aren't part of the gcloud standard authorization variables // but the GCP terraform provider uses this variable to allow users to authenticate // with the contents of a credentials.json file instead of just a file path. // https://www.terraform.io/docs/backends/types/gcs.html if creds := os.Getenv("GOOGLE_CREDENTIALS"); creds != "" { // We try $GOOGLE_CREDENTIALS before gcp.DefaultCredentials // so that users can override the default creds credentials, err := google.CredentialsFromJSON(ctx, []byte(creds), storage.ScopeReadWrite) if err != nil { return nil, errors.Wrap(err, "unable to parse credentials from $GOOGLE_CREDENTIALS") } return credentials, nil } // DefaultCredentials will attempt to load creds in the following order: // 1. a file located at $GOOGLE_APPLICATION_CREDENTIALS // 2. application_default_credentials.json file in ~/.config/gcloud or $APPDATA\gcloud credentials, err := gcp.DefaultCredentials(ctx) if err != nil { return nil, errors.Wrap(err, "unable to find gcp credentials") } return credentials, nil } func GoogleCredentialsMux(ctx context.Context) (*blob.URLMux, error) { credentials, err := googleCredentials(ctx) if err != nil { return nil, errors.New("missing google credentials") } client, err := gcp.NewHTTPClient(gcp.DefaultTransport(), credentials.TokenSource) if err != nil { return nil, err } options := gcsblob.Options{} account := GoogleCredentials{} err = json.Unmarshal(credentials.JSON, &account) if err == nil && account.ClientEmail != "" && account.PrivateKey != "" { options.GoogleAccessID = account.ClientEmail options.PrivateKey = []byte(account.PrivateKey) } else { cmdutil.Diag().Warningf(diag.Message("", "Pulumi will not be able to print a statefile permalink using these credentials. "+ "Neither a GoogleAccessID or PrivateKey are available. "+ "Try using a GCP Service Account.")) } blobmux := &blob.URLMux{} blobmux.RegisterBucket(gcsblob.Scheme, &gcsblob.URLOpener{ Client: client, Options: options, }) return blobmux, nil }