Merge pull request #3901 from hashicorp/phinze/google-credentials

provider/google: read credentials as contents instead of path
This commit is contained in:
Paul Hinze 2015-11-16 17:57:54 -06:00
commit 7e59d7f67c
5 changed files with 75 additions and 68 deletions

View File

@ -3,13 +3,12 @@ package google
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os"
"runtime" "runtime"
"strings" "strings"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
@ -24,7 +23,7 @@ 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 Credentials string
Project string Project string
Region string Region string
@ -44,46 +43,17 @@ func (c *Config) loadAndValidate() error {
"https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/devstorage.full_control",
} }
if c.AccountFile == "" {
c.AccountFile = os.Getenv("GOOGLE_ACCOUNT_FILE")
}
if c.Project == "" {
c.Project = os.Getenv("GOOGLE_PROJECT")
}
if c.Region == "" {
c.Region = os.Getenv("GOOGLE_REGION")
}
var client *http.Client var client *http.Client
if c.AccountFile != "" { if c.Credentials != "" {
contents := c.AccountFile contents, _, err := pathorcontents.Read(c.Credentials)
if err != nil {
return fmt.Errorf("Error loading credentials: %s", err)
}
// Assume account_file is a JSON string // Assume account_file is a JSON string
if err := parseJSON(&account, contents); err != nil { if err := parseJSON(&account, contents); err != nil {
// If account_file was not JSON, assume it is a file path instead return fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
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)
}
} }
// Get the token for use in our requests // Get the token for use in our requests

View File

@ -5,11 +5,11 @@ import (
"testing" "testing"
) )
const testFakeAccountFilePath = "./test-fixtures/fake_account.json" const testFakeCredentialsPath = "./test-fixtures/fake_account.json"
func TestConfigLoadAndValidate_accountFilePath(t *testing.T) { func TestConfigLoadAndValidate_accountFilePath(t *testing.T) {
config := Config{ config := Config{
AccountFile: testFakeAccountFilePath, Credentials: testFakeCredentialsPath,
Project: "my-gce-project", Project: "my-gce-project",
Region: "us-central1", Region: "us-central1",
} }
@ -21,12 +21,12 @@ func TestConfigLoadAndValidate_accountFilePath(t *testing.T) {
} }
func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) { func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) {
contents, err := ioutil.ReadFile(testFakeAccountFilePath) contents, err := ioutil.ReadFile(testFakeCredentialsPath)
if err != nil { if err != nil {
t.Fatalf("error: %v", err) t.Fatalf("error: %v", err)
} }
config := Config{ config := Config{
AccountFile: string(contents), Credentials: string(contents),
Project: "my-gce-project", Project: "my-gce-project",
Region: "us-central1", Region: "us-central1",
} }
@ -39,7 +39,7 @@ func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) {
func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) { func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) {
config := Config{ config := Config{
AccountFile: "{this is not json}", Credentials: "{this is not json}",
Project: "my-gce-project", Project: "my-gce-project",
Region: "us-central1", Region: "us-central1",
} }

View File

@ -3,8 +3,8 @@ package google
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -18,6 +18,14 @@ func Provider() terraform.ResourceProvider {
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil), DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil),
ValidateFunc: validateAccountFile, ValidateFunc: validateAccountFile,
Deprecated: "Use the credentials field instead",
},
"credentials": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_CREDENTIALS", nil),
ValidateFunc: validateCredentials,
}, },
"project": &schema.Schema{ "project": &schema.Schema{
@ -73,8 +81,12 @@ func Provider() terraform.ResourceProvider {
} }
func providerConfigure(d *schema.ResourceData) (interface{}, error) { func providerConfigure(d *schema.ResourceData) (interface{}, error) {
credentials := d.Get("credentials").(string)
if credentials == "" {
credentials = d.Get("account_file").(string)
}
config := Config{ config := Config{
AccountFile: d.Get("account_file").(string), Credentials: credentials,
Project: d.Get("project").(string), Project: d.Get("project").(string),
Region: d.Get("region").(string), Region: d.Get("region").(string),
} }
@ -97,22 +109,34 @@ func validateAccountFile(v interface{}, k string) (warnings []string, errors []e
return return
} }
var account accountFile contents, wasPath, err := pathorcontents.Read(value)
if err := json.Unmarshal([]byte(value), &account); err != nil { if err != nil {
warnings = append(warnings, ` errors = append(errors, fmt.Errorf("Error loading Account File: %s", err))
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 if wasPath {
${file("filename.json")} instead.`) warnings = append(warnings, `account_file was provided as a path instead of
} else { as file contents. This support will be removed in the future. Please update
return your configuration to use ${file("filename.json")} instead.`)
} }
if _, err := os.Stat(value); err != nil { var account accountFile
if err := json.Unmarshal([]byte(contents), &account); err != nil {
errors = append(errors, errors = append(errors,
fmt.Errorf( fmt.Errorf("account_file not valid JSON '%s': %s", contents, err))
"account_file path could not be read from '%s': %s", }
value,
err)) return
}
func validateCredentials(v interface{}, k string) (warnings []string, errors []error) {
if v == nil || v.(string) == "" {
return
}
creds := v.(string)
var account accountFile
if err := json.Unmarshal([]byte(creds), &account); err != nil {
errors = append(errors,
fmt.Errorf("credentials are not valid JSON '%s': %s", creds, err))
} }
return return

View File

@ -29,8 +29,8 @@ func TestProvider_impl(t *testing.T) {
} }
func testAccPreCheck(t *testing.T) { func testAccPreCheck(t *testing.T) {
if v := os.Getenv("GOOGLE_ACCOUNT_FILE"); v == "" { if v := os.Getenv("GOOGLE_CREDENTIALS"); v == "" {
t.Fatal("GOOGLE_ACCOUNT_FILE must be set for acceptance tests") t.Fatal("GOOGLE_CREDENTIALS must be set for acceptance tests")
} }
if v := os.Getenv("GOOGLE_PROJECT"); v == "" { if v := os.Getenv("GOOGLE_PROJECT"); v == "" {

View File

@ -19,14 +19,14 @@ 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 = "${file("account.json")}" credentials = "${file("account.json")}"
project = "my-gce-project" project = "my-gce-project"
region = "us-central1" region = "us-central1"
} }
# Create a new instance # Create a new instance
resource "google_compute_instance" "default" { resource "google_compute_instance" "default" {
... ...
} }
``` ```
@ -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) Contents of the JSON file used to describe your * `credentials` - (Optional) 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 are running retrieving this file are below. Credentials may be blank if you are running
terraform from a GCE instance with a properly-configured [Compute Engine Terraform from a GCE instance with a properly-configured [Compute Engine
Service Account](https://cloud.google.com/compute/docs/authentication). This Service Account](https://cloud.google.com/compute/docs/authentication). This
can also be specified with the `GOOGLE_ACCOUNT_FILE` shell environment can also be specified with the `GOOGLE_CREDENTIALS` 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
@ -48,6 +48,19 @@ The following keys can be used to configure the provider.
* `region` - (Required) The region to operate under. This can also be specified * `region` - (Required) The region to operate under. This can also be specified
with the `GOOGLE_REGION` shell environment variable. with the `GOOGLE_REGION` shell environment variable.
The following keys are supported for backwards compatibility, and may be
removed in a future version:
* `account_file` - __Deprecated: please use `credentials` instead.__
Path to or 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.
## Authentication JSON File ## Authentication JSON File
Authenticating with Google Cloud services requires a JSON Authenticating with Google Cloud services requires a JSON