Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/google: read credentials as contents instead of path #3901

Merged
merged 1 commit into from
Nov 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 8 additions & 38 deletions builtin/providers/google/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package google
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"runtime"
"strings"

"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
Expand All @@ -24,7 +23,7 @@ import (
// Config is the configuration structure used to instantiate the Google
// provider.
type Config struct {
AccountFile string
Credentials string
Project string
Region string

Expand All @@ -44,46 +43,17 @@ func (c *Config) loadAndValidate() error {
"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

if c.AccountFile != "" {
contents := c.AccountFile
if c.Credentials != "" {
contents, _, err := pathorcontents.Read(c.Credentials)
if err != nil {
return fmt.Errorf("Error loading credentials: %s", err)
}

// 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)

if err := parseJSON(&account, contents); err != nil {
return fmt.Errorf(
"Error parsing account file '%s': %s",
contents,
err)
}
return fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
}

// Get the token for use in our requests
Expand Down
10 changes: 5 additions & 5 deletions builtin/providers/google/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"testing"
)

const testFakeAccountFilePath = "./test-fixtures/fake_account.json"
const testFakeCredentialsPath = "./test-fixtures/fake_account.json"

func TestConfigLoadAndValidate_accountFilePath(t *testing.T) {
config := Config{
AccountFile: testFakeAccountFilePath,
Credentials: testFakeCredentialsPath,
Project: "my-gce-project",
Region: "us-central1",
}
Expand All @@ -21,12 +21,12 @@ func TestConfigLoadAndValidate_accountFilePath(t *testing.T) {
}

func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) {
contents, err := ioutil.ReadFile(testFakeAccountFilePath)
contents, err := ioutil.ReadFile(testFakeCredentialsPath)
if err != nil {
t.Fatalf("error: %v", err)
}
config := Config{
AccountFile: string(contents),
Credentials: string(contents),
Project: "my-gce-project",
Region: "us-central1",
}
Expand All @@ -39,7 +39,7 @@ func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) {

func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) {
config := Config{
AccountFile: "{this is not json}",
Credentials: "{this is not json}",
Project: "my-gce-project",
Region: "us-central1",
}
Expand Down
52 changes: 38 additions & 14 deletions builtin/providers/google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package google
import (
"encoding/json"
"fmt"
"os"

"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
Expand All @@ -18,6 +18,14 @@ func Provider() terraform.ResourceProvider {
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil),
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{
Expand Down Expand Up @@ -73,8 +81,12 @@ func Provider() terraform.ResourceProvider {
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
credentials := d.Get("credentials").(string)
if credentials == "" {
credentials = d.Get("account_file").(string)
}
config := Config{
AccountFile: d.Get("account_file").(string),
Credentials: credentials,
Project: d.Get("project").(string),
Region: d.Get("region").(string),
}
Expand All @@ -97,22 +109,34 @@ func validateAccountFile(v interface{}, k string) (warnings []string, errors []e
return
}

contents, wasPath, err := pathorcontents.Read(value)
if err != nil {
errors = append(errors, fmt.Errorf("Error loading Account File: %s", err))
}
if wasPath {
warnings = append(warnings, `account_file was provided as a path instead of
as file contents. This support will be removed in the future. Please update
your configuration to use ${file("filename.json")} instead.`)
}

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 := json.Unmarshal([]byte(contents), &account); err != nil {
errors = append(errors,
fmt.Errorf("account_file not valid JSON '%s': %s", contents, err))
}

if _, err := os.Stat(value); err != nil {
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(
"account_file path could not be read from '%s': %s",
value,
err))
fmt.Errorf("credentials are not valid JSON '%s': %s", creds, err))
}

return
Expand Down
4 changes: 2 additions & 2 deletions builtin/providers/google/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func TestProvider_impl(t *testing.T) {
}

func testAccPreCheck(t *testing.T) {
if v := os.Getenv("GOOGLE_ACCOUNT_FILE"); v == "" {
t.Fatal("GOOGLE_ACCOUNT_FILE must be set for acceptance tests")
if v := os.Getenv("GOOGLE_CREDENTIALS"); v == "" {
t.Fatal("GOOGLE_CREDENTIALS must be set for acceptance tests")
}

if v := os.Getenv("GOOGLE_PROJECT"); v == "" {
Expand Down
29 changes: 21 additions & 8 deletions website/source/docs/providers/google/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,27 @@ Use the navigation to the left to read about the available resources.
```
# Configure the Google Cloud provider
provider "google" {
account_file = "${file("account.json")}"
project = "my-gce-project"
region = "us-central1"
credentials = "${file("account.json")}"
project = "my-gce-project"
region = "us-central1"
}

# Create a new instance
resource "google_compute_instance" "default" {
...
...
}
```

## Configuration Reference

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
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
retrieving this file are below. Credentials may be blank 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
can also be specified with the `GOOGLE_CREDENTIALS` shell environment
variable.

* `project` - (Required) The ID of the project to apply any resources to. This
Expand All @@ -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
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

Authenticating with Google Cloud services requires a JSON
Expand Down