diff --git a/builtin/providers/google/config.go b/builtin/providers/google/config.go index 853b5228a..409443fdb 100644 --- a/builtin/providers/google/config.go +++ b/builtin/providers/google/config.go @@ -25,10 +25,9 @@ import ( // Config is the configuration structure used to instantiate the Google // provider. type Config struct { - AccountFile string - AccountFileContents string - Project string - Region string + AccountFile string + Project string + Region string clientCompute *compute.Service clientContainer *container.Service @@ -39,13 +38,9 @@ type Config struct { func (c *Config) loadAndValidate() error { var account accountFile - // TODO: validation that it isn't blank if c.AccountFile == "" { c.AccountFile = os.Getenv("GOOGLE_ACCOUNT_FILE") } - if c.AccountFileContents == "" { - c.AccountFileContents = os.Getenv("GOOGLE_ACCOUNT_FILE_CONTENTS") - } if c.Project == "" { c.Project = os.Getenv("GOOGLE_PROJECT") } @@ -56,25 +51,32 @@ func (c *Config) loadAndValidate() error { var client *http.Client if c.AccountFile != "" { - if c.AccountFileContents != "" { - return fmt.Errorf( - "Cannot provide both account_file and account_file_contents", - ) + contents := c.AccountFile + + // Assume account_file is a JSON string + if err := parseJSON(&account, contents); err != nil { + // If account_file was not JSON, assume it is a file path instead + if _, err := os.Stat(c.AccountFile); os.IsNotExist(err) { + return fmt.Errorf( + "account_file path does not exist: %s", + c.AccountFile) + } + + b, err := ioutil.ReadFile(c.AccountFile) + if err != nil { + return fmt.Errorf( + "Error reading account_file from path '%s': %s", + c.AccountFile, + err) + } + + contents = string(b) } - b, err := ioutil.ReadFile(c.AccountFile) - if err != nil { - return err - } - - c.AccountFileContents = string(b) - } - - if c.AccountFileContents != "" { - if err := parseJSON(&account, c.AccountFileContents); err != nil { + if err := parseJSON(&account, contents); err != nil { return fmt.Errorf( - "Error parsing account file contents '%s': %s", - c.AccountFileContents, + "Error parsing account file '%s': %s", + contents, err) } diff --git a/builtin/providers/google/config_test.go b/builtin/providers/google/config_test.go index 3ec20376a..cc1b6213f 100644 --- a/builtin/providers/google/config_test.go +++ b/builtin/providers/google/config_test.go @@ -7,7 +7,7 @@ import ( const testFakeAccountFilePath = "./test-fixtures/fake_account.json" -func TestConfigLoadAndValidate_accountFile(t *testing.T) { +func TestConfigLoadAndValidate_accountFilePath(t *testing.T) { config := Config{ AccountFile: testFakeAccountFilePath, Project: "my-gce-project", @@ -20,15 +20,15 @@ func TestConfigLoadAndValidate_accountFile(t *testing.T) { } } -func TestConfigLoadAndValidate_accountFileContents(t *testing.T) { +func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) { contents, err := ioutil.ReadFile(testFakeAccountFilePath) if err != nil { t.Fatalf("error: %v", err) } config := Config{ - AccountFileContents: string(contents), - Project: "my-gce-project", - Region: "us-central1", + AccountFile: string(contents), + Project: "my-gce-project", + Region: "us-central1", } err = config.loadAndValidate() @@ -37,24 +37,11 @@ func TestConfigLoadAndValidate_accountFileContents(t *testing.T) { } } -func TestConfigLoadAndValidate_none(t *testing.T) { +func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) { config := Config{ - Project: "my-gce-project", - Region: "us-central1", - } - - err := config.loadAndValidate() - if err != nil { - t.Fatalf("error: %v", err) - } -} - -func TestConfigLoadAndValidate_both(t *testing.T) { - config := Config{ - AccountFile: testFakeAccountFilePath, - AccountFileContents: "{}", - Project: "my-gce-project", - Region: "us-central1", + AccountFile: "{this is not json}", + Project: "my-gce-project", + Region: "us-central1", } if config.loadAndValidate() == nil { diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index c1b2316fd..b99ac44b3 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -1,6 +1,10 @@ package google import ( + "encoding/json" + "fmt" + "os" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -10,15 +14,10 @@ func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "account_file": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", ""), - }, - - "account_file_contents": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE_CONTENTS", ""), + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil), + ValidateFunc: validateAccountFile, }, "project": &schema.Schema{ @@ -59,10 +58,9 @@ func Provider() terraform.ResourceProvider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { config := Config{ - AccountFile: d.Get("account_file").(string), - AccountFileContents: d.Get("account_file_contents").(string), - Project: d.Get("project").(string), - Region: d.Get("region").(string), + AccountFile: d.Get("account_file").(string), + Project: d.Get("project").(string), + Region: d.Get("region").(string), } if err := config.loadAndValidate(); err != nil { @@ -71,3 +69,28 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return &config, nil } + +func validateAccountFile(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if value == "" { + return + } + + var account accountFile + if err := json.Unmarshal([]byte(value), &account); err != nil { + warnings = append(warnings, ` +account_file is not valid JSON, so we are assuming it is a file path. This +support will be removed in the future. Please update your configuration to use +${file("filename.json")} instead.`) + + return + } + + if _, err := os.Stat(value); os.IsNotExist(err) { + errors = append(errors, err) + fmt.Errorf("account_file path does not exist: %s", value) + } + + return +} diff --git a/website/source/docs/providers/google/index.html.markdown b/website/source/docs/providers/google/index.html.markdown index 86745b8af..3bbef84c6 100644 --- a/website/source/docs/providers/google/index.html.markdown +++ b/website/source/docs/providers/google/index.html.markdown @@ -19,7 +19,7 @@ Use the navigation to the left to read about the available resources. ``` # Configure the Google Cloud provider provider "google" { - account_file = "account.json" + account_file = "${file("account.json")}" project = "my-gce-project" region = "us-central1" } @@ -34,19 +34,13 @@ resource "google_compute_instance" "default" { The following keys can be used to configure the provider. -* `account_file` - (Required, unless `account_file_contents` is present) Path - to the JSON file used to describe your account credentials, downloaded from - Google Cloud Console. More details on retrieving this file are below. The - _account file_ can be "" if you are running terraform from a GCE instance with - a properly-configured [Compute Engine Service - Account](https://cloud.google.com/compute/docs/authentication). This can also - be specified with the `GOOGLE_ACCOUNT_FILE` shell environment variable. - -* `account_file_contents` - (Required, unless `account_file` is present) The - contents of `account_file`. This can be used to pass the account credentials - with a Terraform var or environment variable if the account file is not - accessible. This can also be specified with the `GOOGLE_ACCOUNT_FILE_CONTENTS` - shell environment variable. +* `account_file` - (Required) Contents of the JSON file used to describe your + account credentials, downloaded from Google Cloud Console. More details on + retrieving this file are below. The `account file` can be "" if you are running + terraform from a GCE instance with a properly-configured [Compute Engine + Service Account](https://cloud.google.com/compute/docs/authentication). This + can also be specified with the `GOOGLE_ACCOUNT_FILE` shell environment + variable. * `project` - (Required) The ID of the project to apply any resources to. This can also be specified with the `GOOGLE_PROJECT` shell environment variable.