Merge pull request #884 from hashicorp/452-google-secrets-file

[REPACK] #452 providers/google: remove deprecated client secrets file
This commit is contained in:
Paul Hinze 2015-01-28 17:21:11 -06:00
commit 24c3718ac6
6 changed files with 44 additions and 90 deletions

View File

@ -17,17 +17,26 @@ const clientScopes string = "https://www.googleapis.com/auth/compute"
// 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
ClientSecretsFile string Project string
Project string Region string
Region string
clientCompute *compute.Service clientCompute *compute.Service
} }
func (c *Config) loadAndValidate() error { func (c *Config) loadAndValidate() error {
var account accountFile var account accountFile
var secrets clientSecretsFile
// TODO: validation that it isn't blank
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")
}
if err := loadJSON(&account, c.AccountFile); err != nil { if err := loadJSON(&account, c.AccountFile); err != nil {
return fmt.Errorf( return fmt.Errorf(
@ -36,24 +45,15 @@ func (c *Config) loadAndValidate() error {
err) err)
} }
if err := loadJSON(&secrets, c.ClientSecretsFile); err != nil {
return fmt.Errorf(
"Error loading client secrets file '%s': %s",
c.ClientSecretsFile,
err)
}
// Get the token for use in our requests // Get the token for use in our requests
log.Printf("[INFO] Requesting Google token...") log.Printf("[INFO] Requesting Google token...")
log.Printf("[INFO] -- Email: %s", account.ClientEmail) log.Printf("[INFO] -- Email: %s", account.ClientEmail)
log.Printf("[INFO] -- Scopes: %s", clientScopes) log.Printf("[INFO] -- Scopes: %s", clientScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey)) log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey))
log.Printf("[INFO] -- Token URL: %s", secrets.Web.TokenURI)
jwtTok := jwt.NewToken( jwtTok := jwt.NewToken(
account.ClientEmail, account.ClientEmail,
clientScopes, clientScopes,
[]byte(account.PrivateKey)) []byte(account.PrivateKey))
jwtTok.ClaimSet.Aud = secrets.Web.TokenURI
token, err := jwtTok.Assert(new(http.Client)) token, err := jwtTok.Assert(new(http.Client))
if err != nil { if err != nil {
return fmt.Errorf("Error retrieving auth token: %s", err) return fmt.Errorf("Error retrieving auth token: %s", err)
@ -64,8 +64,6 @@ func (c *Config) loadAndValidate() error {
Config: &oauth.Config{ Config: &oauth.Config{
ClientId: account.ClientId, ClientId: account.ClientId,
Scope: clientScopes, Scope: clientScopes,
TokenURL: secrets.Web.TokenURI,
AuthURL: secrets.Web.AuthURI,
}, },
Token: token, Token: token,
} }
@ -87,16 +85,6 @@ type accountFile struct {
ClientId string `json:"client_id"` ClientId string `json:"client_id"`
} }
// clientSecretsFile represents the structure of the client secrets JSON file.
type clientSecretsFile struct {
Web struct {
AuthURI string `json:"auth_uri"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
TokenURI string `json:"token_uri"`
}
}
func loadJSON(result interface{}, path string) error { func loadJSON(result interface{}, path string) error {
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {

View File

@ -22,20 +22,3 @@ func TestConfigLoadJSON_account(t *testing.T) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v", actual)
} }
} }
func TestConfigLoadJSON_client(t *testing.T) {
var actual clientSecretsFile
if err := loadJSON(&actual, "./test-fixtures/fake_client.json"); err != nil {
t.Fatalf("err: %s", err)
}
var expected clientSecretsFile
expected.Web.AuthURI = "https://accounts.google.com/o/oauth2/auth"
expected.Web.ClientEmail = "foo@developer.gserviceaccount.com"
expected.Web.ClientId = "foo.apps.googleusercontent.com"
expected.Web.TokenURI = "https://accounts.google.com/o/oauth2/token"
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}

View File

@ -15,12 +15,6 @@ func Provider() terraform.ResourceProvider {
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil), DefaultFunc: schema.EnvDefaultFunc("GOOGLE_ACCOUNT_FILE", nil),
}, },
"client_secrets_file": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("GOOGLE_CLIENT_FILE", nil),
},
"project": &schema.Schema{ "project": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
@ -49,10 +43,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),
ClientSecretsFile: d.Get("client_secrets_file").(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 {

View File

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

View File

@ -137,11 +137,11 @@ func resourceComputeInstance() *schema.Resource {
}, },
"scopes": &schema.Schema{ "scopes": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
Elem: &schema.Schema{ Elem: &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
StateFunc: func(v interface{}) string { StateFunc: func(v interface{}) string {
return canonicalizeServiceScope(v.(string)) return canonicalizeServiceScope(v.(string))
}, },
@ -294,11 +294,11 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
scopesCount := d.Get(prefix + ".scopes.#").(int) scopesCount := d.Get(prefix + ".scopes.#").(int)
scopes := make([]string, 0, scopesCount) scopes := make([]string, 0, scopesCount)
for j := 0; j < scopesCount; j++ { for j := 0; j < scopesCount; j++ {
scope := d.Get(fmt.Sprintf(prefix + ".scopes.%d", j)).(string) scope := d.Get(fmt.Sprintf(prefix+".scopes.%d", j)).(string)
scopes = append(scopes, canonicalizeServiceScope(scope)) scopes = append(scopes, canonicalizeServiceScope(scope))
} }
serviceAccount := &compute.ServiceAccount { serviceAccount := &compute.ServiceAccount{
Email: "default", Email: "default",
Scopes: scopes, Scopes: scopes,
} }
@ -378,8 +378,8 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
// Set the service accounts // Set the service accounts
for i, serviceAccount := range instance.ServiceAccounts { for i, serviceAccount := range instance.ServiceAccounts {
prefix := fmt.Sprintf("service_account.%d", i) prefix := fmt.Sprintf("service_account.%d", i)
d.Set(prefix + ".email", serviceAccount.Email) d.Set(prefix+".email", serviceAccount.Email)
d.Set(prefix + ".scopes.#", len(serviceAccount.Scopes)) d.Set(prefix+".scopes.#", len(serviceAccount.Scopes))
for j, scope := range serviceAccount.Scopes { for j, scope := range serviceAccount.Scopes {
d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope) d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope)
} }
@ -534,14 +534,19 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err
func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata { func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata {
var metadata *compute.Metadata var metadata *compute.Metadata
if v := d.Get("metadata").([]interface{}); len(v) > 0 { if metadataList := d.Get("metadata").([]interface{}); len(metadataList) > 0 {
m := new(compute.Metadata) m := new(compute.Metadata)
m.Items = make([]*compute.MetadataItems, 0, len(v)) m.Items = make([]*compute.MetadataItems, 0, len(metadataList))
for _, v := range v { for _, metadataMap := range metadataList {
for k, v := range v.(map[string]interface{}) { for key, val := range metadataMap.(map[string]interface{}) {
// TODO: fix https://github.com/hashicorp/terraform/issues/883
// and remove this workaround <3 phinze
if key == "#" {
continue
}
m.Items = append(m.Items, &compute.MetadataItems{ m.Items = append(m.Items, &compute.MetadataItems{
Key: k, Key: key,
Value: v.(string), Value: val.(string),
}) })
} }
} }

View File

@ -20,7 +20,6 @@ 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 = "account.json"
client_secrets_file = "client_secrets.json"
project = "my-gce-project" project = "my-gce-project"
region = "us-central1" region = "us-central1"
} }
@ -39,33 +38,23 @@ The following keys can be used to configure the provider.
your account credentials, downloaded from Google Cloud Console. More your account credentials, downloaded from Google Cloud Console. More
details on retrieving this file are below. details on retrieving this file are below.
* `client_secrets_file` - (Required) Path to the JSON file containing
the secrets for your account, downloaded from Google Cloud Console.
More details on retrieving this file are below.
* `project` - (Required) The name of the project to apply any resources to. * `project` - (Required) The name of the project to apply any resources to.
* `region` - (Required) The region to operate under. * `region` - (Required) The region to operate under.
## Authentication JSON Files ## Authentication JSON File
Authenticating with Google Cloud services requires two separate JSON Authenticating with Google Cloud services requires a JSON
files: one which we call the _account file_ and the _client secrets file_. file which we call the _account file_.
Both of these files are downloaded directly from the This file is downloaded directly from the
[Google Developers Console](https://console.developers.google.com). To make [Google Developers Console](https://console.developers.google.com). To make
the process more straightforwarded, it is documented here. the process more straightforwarded, it is documented here:
1. Log into the [Google Developers Console](https://console.developers.google.com) 1. Log into the [Google Developers Console](https://console.developers.google.com)
and select a project. and select a project.
2. Under the "APIs & Auth" section, click "Credentials." 2. Under the "APIs & Auth" section, click "Credentials."
3. Create a new OAuth client ID and select "Installed application" as the 3. Create a new OAuth client ID and select "Service account" as the type
type of account. Once created, click the "Download JSON" button underneath of account. Once created, and after a P12 key is downloaded, a JSON file should be downloaded. This is your _account file_.
the account. The file should start with "client\_secret". This is your _client
secrets file_.
4. Create a new OAuth client ID and select "Service account" as the type
of account. Once created, a JSON file should be downloaded. This is your
_account file_.