Add OAuth2 access_token and client config as credential options. (#2838)

Signed-off-by: Modular Magician <magic-modules@google.com>
This commit is contained in:
The Magician 2019-01-09 16:20:07 -08:00 committed by emily
parent cbfef5e68c
commit b9cedc68ef
4 changed files with 76 additions and 73 deletions

View File

@ -2,11 +2,9 @@ package google
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strings"
"github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/helper/pathorcontents"
@ -14,8 +12,7 @@ import (
"github.com/terraform-providers/terraform-provider-google/version" "github.com/terraform-providers/terraform-provider-google/version"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" googleoauth "golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
appengine "google.golang.org/api/appengine/v1" appengine "google.golang.org/api/appengine/v1"
"google.golang.org/api/bigquery/v2" "google.golang.org/api/bigquery/v2"
"google.golang.org/api/cloudbilling/v1" "google.golang.org/api/cloudbilling/v1"
@ -53,6 +50,7 @@ import (
// provider. // provider.
type Config struct { type Config struct {
Credentials string Credentials string
AccessToken string
Project string Project string
Region string Region string
Zone string Zone string
@ -98,7 +96,6 @@ type Config struct {
} }
func (c *Config) loadAndValidate() error { func (c *Config) loadAndValidate() error {
var account accountFile
clientScopes := []string{ clientScopes := []string{
"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-platform",
@ -106,55 +103,13 @@ func (c *Config) loadAndValidate() error {
"https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/devstorage.full_control",
} }
var client *http.Client tokenSource, err := c.getTokenSource(clientScopes)
var tokenSource oauth2.TokenSource if err != nil {
return err
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 {
return fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
}
// Get the token for use in our requests
log.Printf("[INFO] Requesting Google token...")
log.Printf("[INFO] -- Email: %s", account.ClientEmail)
log.Printf("[INFO] -- Scopes: %s", clientScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey))
conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: clientScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
// Initiate an http.Client. The following GET request will be
// authorized and authenticated on the behalf of
// your service account.
client = conf.Client(context.Background())
tokenSource = conf.TokenSource(context.Background())
} else {
log.Printf("[INFO] Authenticating using DefaultClient")
err := error(nil)
client, err = google.DefaultClient(context.Background(), clientScopes...)
if err != nil {
return err
}
tokenSource, err = google.DefaultTokenSource(context.Background(), clientScopes...)
if err != nil {
return err
}
} }
c.tokenSource = tokenSource c.tokenSource = tokenSource
client := oauth2.NewClient(context.Background(), tokenSource)
client.Transport = logging.NewTransport("Google", client.Transport) client.Transport = logging.NewTransport("Google", client.Transport)
terraformVersion := httpclient.UserAgentString() terraformVersion := httpclient.UserAgentString()
@ -165,8 +120,6 @@ func (c *Config) loadAndValidate() error {
c.client = client c.client = client
c.userAgent = userAgent c.userAgent = userAgent
var err error
log.Printf("[INFO] Instantiating GCE client...") log.Printf("[INFO] Instantiating GCE client...")
c.clientCompute, err = compute.New(client) c.clientCompute, err = compute.New(client)
if err != nil { if err != nil {
@ -391,17 +344,31 @@ func (c *Config) loadAndValidate() error {
return nil return nil
} }
// accountFile represents the structure of the account file JSON file. func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, error) {
type accountFile struct { if c.AccessToken != "" {
PrivateKeyId string `json:"private_key_id"` log.Printf("[INFO] Using configured Google access token (length %d)", len(c.AccessToken))
PrivateKey string `json:"private_key"` log.Printf("[INFO] -- Scopes: %s", clientScopes)
ClientEmail string `json:"client_email"` token := &oauth2.Token{AccessToken: c.AccessToken}
ClientId string `json:"client_id"` return oauth2.StaticTokenSource(token), nil
} }
func parseJSON(result interface{}, contents string) error { if c.Credentials != "" {
r := strings.NewReader(contents) contents, _, err := pathorcontents.Read(c.Credentials)
dec := json.NewDecoder(r) if err != nil {
return nil, fmt.Errorf("Error loading credentials: %s", err)
}
return dec.Decode(result) creds, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(contents), clientScopes...)
if err != nil {
return nil, fmt.Errorf("Unable to parse credentials from '%s': %s", contents, err)
}
log.Printf("[INFO] Requesting Google token using Credential File %q...", c.Credentials)
log.Printf("[INFO] -- Scopes: %s", clientScopes)
return creds.TokenSource, nil
}
log.Printf("[INFO] Authenticating using DefaultClient")
log.Printf("[INFO] -- Scopes: %s", clientScopes)
return googleoauth.DefaultTokenSource(context.Background(), clientScopes...)
} }

View File

@ -48,3 +48,18 @@ func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) {
t.Fatalf("expected error, but got nil") t.Fatalf("expected error, but got nil")
} }
} }
func TestConfigLoadValidate_accessToken(t *testing.T) {
accessToken := getTestAccessTokenFromEnv(t)
config := Config{
AccessToken: accessToken,
Project: "my-gce-project",
Region: "us-central1",
}
err := config.loadAndValidate()
if err != nil {
t.Fatalf("error: %v", err)
}
}

View File

@ -1,13 +1,15 @@
package google package google
import ( import (
"encoding/json" "context"
"fmt" "fmt"
"os" "os"
"github.com/hashicorp/terraform/helper/mutexkv" "github.com/hashicorp/terraform/helper/mutexkv"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
googleoauth "golang.org/x/oauth2/google"
) )
// Global MutexKV // Global MutexKV
@ -28,6 +30,12 @@ func Provider() terraform.ResourceProvider {
ValidateFunc: validateCredentials, ValidateFunc: validateCredentials,
}, },
"access_token": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"credentials"},
},
"project": { "project": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
@ -244,12 +252,17 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
} }
func providerConfigure(d *schema.ResourceData) (interface{}, error) { func providerConfigure(d *schema.ResourceData) (interface{}, error) {
credentials := d.Get("credentials").(string)
config := Config{ config := Config{
Credentials: credentials, Project: d.Get("project").(string),
Project: d.Get("project").(string), Region: d.Get("region").(string),
Region: d.Get("region").(string), Zone: d.Get("zone").(string),
Zone: d.Get("zone").(string), }
// Add credential source
if v, ok := d.GetOk("access_token"); ok {
config.AccessToken = v.(string)
} else if v, ok := d.GetOk("credentials"); ok {
config.Credentials = v.(string)
} }
if err := config.loadAndValidate(); err != nil { if err := config.loadAndValidate(); err != nil {
@ -268,10 +281,9 @@ func validateCredentials(v interface{}, k string) (warnings []string, errors []e
if _, err := os.Stat(creds); err == nil { if _, err := os.Stat(creds); err == nil {
return return
} }
var account accountFile if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(creds)); err != nil {
if err := json.Unmarshal([]byte(creds), &account); err != nil {
errors = append(errors, errors = append(errors,
fmt.Errorf("credentials are not valid JSON '%s': %s", creds, err)) fmt.Errorf("JSON credentials in %q are not valid: %s", creds, err))
} }
return return

View File

@ -57,6 +57,10 @@ var billingAccountEnvVars = []string{
"GOOGLE_BILLING_ACCOUNT", "GOOGLE_BILLING_ACCOUNT",
} }
var accessTokenEnvVars = []string{
"GOOGLE_OAUTH2_ACCESS_TOKEN",
}
func init() { func init() {
testAccProvider = Provider().(*schema.Provider) testAccProvider = Provider().(*schema.Provider)
testAccRandomProvider = random.Provider().(*schema.Provider) testAccRandomProvider = random.Provider().(*schema.Provider)
@ -202,6 +206,11 @@ func getTestServiceAccountFromEnv(t *testing.T) string {
return multiEnvSearch(serviceAccountEnvVars) return multiEnvSearch(serviceAccountEnvVars)
} }
func getTestAccessTokenFromEnv(t *testing.T) string {
skipIfEnvNotSet(t, accessTokenEnvVars...)
return multiEnvSearch(accessTokenEnvVars)
}
func multiEnvSearch(ks []string) string { func multiEnvSearch(ks []string) string {
for _, k := range ks { for _, k := range ks {
if v := os.Getenv(k); v != "" { if v := os.Getenv(k); v != "" {