Merge pull request #2839 from hashicorp/gce-account-file-contents
providers/google: Change account_file to expect a JSON string
This commit is contained in:
commit
79e5c9d19b
|
@ -3,10 +3,12 @@ package google
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
|
||||||
// TODO(dcunnin): Use version code from version.go
|
// TODO(dcunnin): Use version code from version.go
|
||||||
|
@ -36,7 +38,6 @@ type Config struct {
|
||||||
func (c *Config) loadAndValidate() error {
|
func (c *Config) loadAndValidate() error {
|
||||||
var account accountFile
|
var account accountFile
|
||||||
|
|
||||||
// TODO: validation that it isn't blank
|
|
||||||
if c.AccountFile == "" {
|
if c.AccountFile == "" {
|
||||||
c.AccountFile = os.Getenv("GOOGLE_ACCOUNT_FILE")
|
c.AccountFile = os.Getenv("GOOGLE_ACCOUNT_FILE")
|
||||||
}
|
}
|
||||||
|
@ -50,11 +51,33 @@ func (c *Config) loadAndValidate() error {
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
|
|
||||||
if c.AccountFile != "" {
|
if c.AccountFile != "" {
|
||||||
if err := loadJSON(&account, c.AccountFile); err != nil {
|
contents := c.AccountFile
|
||||||
return fmt.Errorf(
|
|
||||||
"Error loading account file '%s': %s",
|
// Assume account_file is a JSON string
|
||||||
c.AccountFile,
|
if err := parseJSON(&account, contents); err != nil {
|
||||||
err)
|
// 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)
|
||||||
|
|
||||||
|
if err := parseJSON(&account, contents); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error parsing account file '%s': %s",
|
||||||
|
contents,
|
||||||
|
err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientScopes := []string{
|
clientScopes := []string{
|
||||||
|
@ -146,13 +169,9 @@ type accountFile struct {
|
||||||
ClientId string `json:"client_id"`
|
ClientId string `json:"client_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadJSON(result interface{}, path string) error {
|
func parseJSON(result interface{}, contents string) error {
|
||||||
f, err := os.Open(path)
|
r := strings.NewReader(contents)
|
||||||
if err != nil {
|
dec := json.NewDecoder(r)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
dec := json.NewDecoder(f)
|
|
||||||
return dec.Decode(result)
|
return dec.Decode(result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,50 @@
|
||||||
package google
|
package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfigLoadJSON_account(t *testing.T) {
|
const testFakeAccountFilePath = "./test-fixtures/fake_account.json"
|
||||||
var actual accountFile
|
|
||||||
if err := loadJSON(&actual, "./test-fixtures/fake_account.json"); err != nil {
|
func TestConfigLoadAndValidate_accountFilePath(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
config := Config{
|
||||||
|
AccountFile: testFakeAccountFilePath,
|
||||||
|
Project: "my-gce-project",
|
||||||
|
Region: "us-central1",
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := accountFile{
|
err := config.loadAndValidate()
|
||||||
PrivateKeyId: "foo",
|
if err != nil {
|
||||||
PrivateKey: "bar",
|
t.Fatalf("error: %v", err)
|
||||||
ClientEmail: "foo@bar.com",
|
}
|
||||||
ClientId: "id@foo.com",
|
}
|
||||||
}
|
|
||||||
|
func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) {
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
contents, err := ioutil.ReadFile(testFakeAccountFilePath)
|
||||||
t.Fatalf("bad: %#v", actual)
|
if err != nil {
|
||||||
|
t.Fatalf("error: %v", err)
|
||||||
|
}
|
||||||
|
config := Config{
|
||||||
|
AccountFile: string(contents),
|
||||||
|
Project: "my-gce-project",
|
||||||
|
Region: "us-central1",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.loadAndValidate()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) {
|
||||||
|
config := Config{
|
||||||
|
AccountFile: "{this is not json}",
|
||||||
|
Project: "my-gce-project",
|
||||||
|
Region: "us-central1",
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.loadAndValidate() == nil {
|
||||||
|
t.Fatalf("expected error, but got nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package google
|
package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
@ -10,9 +14,10 @@ func Provider() terraform.ResourceProvider {
|
||||||
return &schema.Provider{
|
return &schema.Provider{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"account_file": &schema.Schema{
|
"account_file": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Required: true,
|
||||||
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil),
|
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil),
|
||||||
|
ValidateFunc: validateAccountFile,
|
||||||
},
|
},
|
||||||
|
|
||||||
"project": &schema.Schema{
|
"project": &schema.Schema{
|
||||||
|
@ -64,3 +69,31 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
|
||||||
return &config, nil
|
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.`)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(value); err != nil {
|
||||||
|
errors = append(errors,
|
||||||
|
fmt.Errorf(
|
||||||
|
"account_file path could not be read from '%s': %s",
|
||||||
|
value,
|
||||||
|
err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ Use the navigation to the left to read about the available resources.
|
||||||
```
|
```
|
||||||
# Configure the Google Cloud provider
|
# Configure the Google Cloud provider
|
||||||
provider "google" {
|
provider "google" {
|
||||||
account_file = "account.json"
|
account_file = "${file("account.json")}"
|
||||||
project = "my-gce-project"
|
project = "my-gce-project"
|
||||||
region = "us-central1"
|
region = "us-central1"
|
||||||
}
|
}
|
||||||
|
@ -34,12 +34,12 @@ resource "google_compute_instance" "default" {
|
||||||
|
|
||||||
The following keys can be used to configure the provider.
|
The following keys can be used to configure the provider.
|
||||||
|
|
||||||
* `account_file` - (Required) Path to the JSON file used to describe your
|
* `account_file` - (Required) Contents of the JSON file used to describe your
|
||||||
account credentials, downloaded from Google Cloud Console. More details on
|
account credentials, downloaded from Google Cloud Console. More details on
|
||||||
retrieving this file are below. The _account file_ can be "" if you
|
retrieving this file are below. The `account file` can be "" if you are running
|
||||||
are running terraform from a GCE instance with a properly-configured [Compute
|
terraform from a GCE instance with a properly-configured [Compute Engine
|
||||||
Engine Service Account](https://cloud.google.com/compute/docs/authentication).
|
Service Account](https://cloud.google.com/compute/docs/authentication). This
|
||||||
This can also be specified with the `GOOGLE_ACCOUNT_FILE` shell environment
|
can also be specified with the `GOOGLE_ACCOUNT_FILE` shell environment
|
||||||
variable.
|
variable.
|
||||||
|
|
||||||
* `project` - (Required) The ID of the project to apply any resources to. This
|
* `project` - (Required) The ID of the project to apply any resources to. This
|
||||||
|
|
Loading…
Reference in New Issue