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 (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/helper/pathorcontents"
@ -14,8 +12,7 @@ import (
"github.com/terraform-providers/terraform-provider-google/version"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
googleoauth "golang.org/x/oauth2/google"
appengine "google.golang.org/api/appengine/v1"
"google.golang.org/api/bigquery/v2"
"google.golang.org/api/cloudbilling/v1"
@ -53,6 +50,7 @@ import (
// provider.
type Config struct {
Credentials string
AccessToken string
Project string
Region string
Zone string
@ -98,7 +96,6 @@ type Config struct {
}
func (c *Config) loadAndValidate() error {
var account accountFile
clientScopes := []string{
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/cloud-platform",
@ -106,55 +103,13 @@ func (c *Config) loadAndValidate() error {
"https://www.googleapis.com/auth/devstorage.full_control",
}
var client *http.Client
var tokenSource oauth2.TokenSource
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
}
tokenSource, err := c.getTokenSource(clientScopes)
if err != nil {
return err
}
c.tokenSource = tokenSource
client := oauth2.NewClient(context.Background(), tokenSource)
client.Transport = logging.NewTransport("Google", client.Transport)
terraformVersion := httpclient.UserAgentString()
@ -165,8 +120,6 @@ func (c *Config) loadAndValidate() error {
c.client = client
c.userAgent = userAgent
var err error
log.Printf("[INFO] Instantiating GCE client...")
c.clientCompute, err = compute.New(client)
if err != nil {
@ -391,17 +344,31 @@ func (c *Config) loadAndValidate() error {
return nil
}
// accountFile represents the structure of the account file JSON file.
type accountFile struct {
PrivateKeyId string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
}
func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, error) {
if c.AccessToken != "" {
log.Printf("[INFO] Using configured Google access token (length %d)", len(c.AccessToken))
log.Printf("[INFO] -- Scopes: %s", clientScopes)
token := &oauth2.Token{AccessToken: c.AccessToken}
return oauth2.StaticTokenSource(token), nil
}
func parseJSON(result interface{}, contents string) error {
r := strings.NewReader(contents)
dec := json.NewDecoder(r)
if c.Credentials != "" {
contents, _, err := pathorcontents.Read(c.Credentials)
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")
}
}
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
import (
"encoding/json"
"context"
"fmt"
"os"
"github.com/hashicorp/terraform/helper/mutexkv"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
googleoauth "golang.org/x/oauth2/google"
)
// Global MutexKV
@ -28,6 +30,12 @@ func Provider() terraform.ResourceProvider {
ValidateFunc: validateCredentials,
},
"access_token": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"credentials"},
},
"project": {
Type: schema.TypeString,
Optional: true,
@ -244,12 +252,17 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
credentials := d.Get("credentials").(string)
config := Config{
Credentials: credentials,
Project: d.Get("project").(string),
Region: d.Get("region").(string),
Zone: d.Get("zone").(string),
Project: d.Get("project").(string),
Region: d.Get("region").(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 {
@ -268,10 +281,9 @@ func validateCredentials(v interface{}, k string) (warnings []string, errors []e
if _, err := os.Stat(creds); err == nil {
return
}
var account accountFile
if err := json.Unmarshal([]byte(creds), &account); err != nil {
if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(creds)); err != nil {
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

View File

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