providers/google: Change account_file to JSON
If JSON fails to parse, treat it as a file path
This commit is contained in:
parent
2a04708d66
commit
773852e2d5
|
@ -25,10 +25,9 @@ import (
|
||||||
// Config is the configuration structure used to instantiate the Google
|
// Config is the configuration structure used to instantiate the Google
|
||||||
// provider.
|
// provider.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AccountFile string
|
AccountFile string
|
||||||
AccountFileContents string
|
Project string
|
||||||
Project string
|
Region string
|
||||||
Region string
|
|
||||||
|
|
||||||
clientCompute *compute.Service
|
clientCompute *compute.Service
|
||||||
clientContainer *container.Service
|
clientContainer *container.Service
|
||||||
|
@ -39,13 +38,9 @@ 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")
|
||||||
}
|
}
|
||||||
if c.AccountFileContents == "" {
|
|
||||||
c.AccountFileContents = os.Getenv("GOOGLE_ACCOUNT_FILE_CONTENTS")
|
|
||||||
}
|
|
||||||
if c.Project == "" {
|
if c.Project == "" {
|
||||||
c.Project = os.Getenv("GOOGLE_PROJECT")
|
c.Project = os.Getenv("GOOGLE_PROJECT")
|
||||||
}
|
}
|
||||||
|
@ -56,25 +51,32 @@ func (c *Config) loadAndValidate() error {
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
|
|
||||||
if c.AccountFile != "" {
|
if c.AccountFile != "" {
|
||||||
if c.AccountFileContents != "" {
|
contents := c.AccountFile
|
||||||
return fmt.Errorf(
|
|
||||||
"Cannot provide both account_file and account_file_contents",
|
// 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 := parseJSON(&account, contents); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.AccountFileContents = string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AccountFileContents != "" {
|
|
||||||
if err := parseJSON(&account, c.AccountFileContents); err != nil {
|
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Error parsing account file contents '%s': %s",
|
"Error parsing account file '%s': %s",
|
||||||
c.AccountFileContents,
|
contents,
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
const testFakeAccountFilePath = "./test-fixtures/fake_account.json"
|
const testFakeAccountFilePath = "./test-fixtures/fake_account.json"
|
||||||
|
|
||||||
func TestConfigLoadAndValidate_accountFile(t *testing.T) {
|
func TestConfigLoadAndValidate_accountFilePath(t *testing.T) {
|
||||||
config := Config{
|
config := Config{
|
||||||
AccountFile: testFakeAccountFilePath,
|
AccountFile: testFakeAccountFilePath,
|
||||||
Project: "my-gce-project",
|
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)
|
contents, err := ioutil.ReadFile(testFakeAccountFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %v", err)
|
t.Fatalf("error: %v", err)
|
||||||
}
|
}
|
||||||
config := Config{
|
config := Config{
|
||||||
AccountFileContents: string(contents),
|
AccountFile: string(contents),
|
||||||
Project: "my-gce-project",
|
Project: "my-gce-project",
|
||||||
Region: "us-central1",
|
Region: "us-central1",
|
||||||
}
|
}
|
||||||
|
|
||||||
err = config.loadAndValidate()
|
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{
|
config := Config{
|
||||||
Project: "my-gce-project",
|
AccountFile: "{this is not json}",
|
||||||
Region: "us-central1",
|
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",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.loadAndValidate() == nil {
|
if config.loadAndValidate() == 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,15 +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", ""),
|
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil),
|
||||||
},
|
ValidateFunc: validateAccountFile,
|
||||||
|
|
||||||
"account_file_contents": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE_CONTENTS", ""),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"project": &schema.Schema{
|
"project": &schema.Schema{
|
||||||
|
@ -59,10 +58,9 @@ func Provider() terraform.ResourceProvider {
|
||||||
|
|
||||||
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||||
config := Config{
|
config := Config{
|
||||||
AccountFile: d.Get("account_file").(string),
|
AccountFile: d.Get("account_file").(string),
|
||||||
AccountFileContents: d.Get("account_file_contents").(string),
|
Project: d.Get("project").(string),
|
||||||
Project: d.Get("project").(string),
|
Region: d.Get("region").(string),
|
||||||
Region: d.Get("region").(string),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.loadAndValidate(); err != nil {
|
if err := config.loadAndValidate(); err != nil {
|
||||||
|
@ -71,3 +69,28 @@ 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.`)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(value); os.IsNotExist(err) {
|
||||||
|
errors = append(errors, err)
|
||||||
|
fmt.Errorf("account_file path does not exist: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,19 +34,13 @@ 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, unless `account_file_contents` is present) Path
|
* `account_file` - (Required) Contents of the JSON file used to describe your
|
||||||
to the JSON file used to describe your account credentials, downloaded from
|
account credentials, downloaded from Google Cloud Console. More details on
|
||||||
Google Cloud Console. More details on retrieving this file are below. The
|
retrieving this file are below. The `account file` can be "" if you are running
|
||||||
_account file_ can be "" if you are running terraform from a GCE instance with
|
terraform from a GCE instance with a properly-configured [Compute Engine
|
||||||
a properly-configured [Compute Engine Service
|
Service Account](https://cloud.google.com/compute/docs/authentication). This
|
||||||
Account](https://cloud.google.com/compute/docs/authentication). This can also
|
can also be specified with the `GOOGLE_ACCOUNT_FILE` shell environment
|
||||||
be specified with the `GOOGLE_ACCOUNT_FILE` shell environment variable.
|
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.
|
|
||||||
|
|
||||||
* `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
|
||||||
can also be specified with the `GOOGLE_PROJECT` shell environment variable.
|
can also be specified with the `GOOGLE_PROJECT` shell environment variable.
|
||||||
|
|
Loading…
Reference in New Issue