Skip to content

Commit bc1f9ae

Browse files
emilymyemodular-magician
authored andcommitted
Add OAuth2 access_token and client config as credential options.
Signed-off-by: Modular Magician <[email protected]>
1 parent 98d2972 commit bc1f9ae

File tree

4 files changed

+76
-73
lines changed

4 files changed

+76
-73
lines changed

google/config.go

+31-64
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,17 @@ package google
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"log"
87
"net/http"
9-
"strings"
108

119
"github.com/hashicorp/terraform/helper/logging"
1210
"github.com/hashicorp/terraform/helper/pathorcontents"
1311
"github.com/hashicorp/terraform/httpclient"
1412
"github.com/terraform-providers/terraform-provider-google/version"
1513

1614
"golang.org/x/oauth2"
17-
"golang.org/x/oauth2/google"
18-
"golang.org/x/oauth2/jwt"
15+
googleoauth "golang.org/x/oauth2/google"
1916
appengine "google.golang.org/api/appengine/v1"
2017
"google.golang.org/api/bigquery/v2"
2118
"google.golang.org/api/cloudbilling/v1"
@@ -53,6 +50,7 @@ import (
5350
// provider.
5451
type Config struct {
5552
Credentials string
53+
AccessToken string
5654
Project string
5755
Region string
5856
Zone string
@@ -98,63 +96,20 @@ type Config struct {
9896
}
9997

10098
func (c *Config) loadAndValidate() error {
101-
var account accountFile
10299
clientScopes := []string{
103100
"https://www.googleapis.com/auth/compute",
104101
"https://www.googleapis.com/auth/cloud-platform",
105102
"https://www.googleapis.com/auth/ndev.clouddns.readwrite",
106103
"https://www.googleapis.com/auth/devstorage.full_control",
107104
}
108105

109-
var client *http.Client
110-
var tokenSource oauth2.TokenSource
111-
112-
if c.Credentials != "" {
113-
contents, _, err := pathorcontents.Read(c.Credentials)
114-
if err != nil {
115-
return fmt.Errorf("Error loading credentials: %s", err)
116-
}
117-
118-
// Assume account_file is a JSON string
119-
if err := parseJSON(&account, contents); err != nil {
120-
return fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
121-
}
122-
123-
// Get the token for use in our requests
124-
log.Printf("[INFO] Requesting Google token...")
125-
log.Printf("[INFO] -- Email: %s", account.ClientEmail)
126-
log.Printf("[INFO] -- Scopes: %s", clientScopes)
127-
log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey))
128-
129-
conf := jwt.Config{
130-
Email: account.ClientEmail,
131-
PrivateKey: []byte(account.PrivateKey),
132-
Scopes: clientScopes,
133-
TokenURL: "https://accounts.google.com/o/oauth2/token",
134-
}
135-
136-
// Initiate an http.Client. The following GET request will be
137-
// authorized and authenticated on the behalf of
138-
// your service account.
139-
client = conf.Client(context.Background())
140-
141-
tokenSource = conf.TokenSource(context.Background())
142-
} else {
143-
log.Printf("[INFO] Authenticating using DefaultClient")
144-
err := error(nil)
145-
client, err = google.DefaultClient(context.Background(), clientScopes...)
146-
if err != nil {
147-
return err
148-
}
149-
150-
tokenSource, err = google.DefaultTokenSource(context.Background(), clientScopes...)
151-
if err != nil {
152-
return err
153-
}
106+
tokenSource, err := c.getTokenSource(clientScopes)
107+
if err != nil {
108+
return err
154109
}
155-
156110
c.tokenSource = tokenSource
157111

112+
client := oauth2.NewClient(context.Background(), tokenSource)
158113
client.Transport = logging.NewTransport("Google", client.Transport)
159114

160115
terraformVersion := httpclient.UserAgentString()
@@ -165,8 +120,6 @@ func (c *Config) loadAndValidate() error {
165120
c.client = client
166121
c.userAgent = userAgent
167122

168-
var err error
169-
170123
log.Printf("[INFO] Instantiating GCE client...")
171124
c.clientCompute, err = compute.New(client)
172125
if err != nil {
@@ -391,17 +344,31 @@ func (c *Config) loadAndValidate() error {
391344
return nil
392345
}
393346

394-
// accountFile represents the structure of the account file JSON file.
395-
type accountFile struct {
396-
PrivateKeyId string `json:"private_key_id"`
397-
PrivateKey string `json:"private_key"`
398-
ClientEmail string `json:"client_email"`
399-
ClientId string `json:"client_id"`
400-
}
347+
func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, error) {
348+
if c.AccessToken != "" {
349+
log.Printf("[INFO] Using configured Google access token (length %d)", len(c.AccessToken))
350+
log.Printf("[INFO] -- Scopes: %s", clientScopes)
351+
token := &oauth2.Token{AccessToken: c.AccessToken}
352+
return oauth2.StaticTokenSource(token), nil
353+
}
401354

402-
func parseJSON(result interface{}, contents string) error {
403-
r := strings.NewReader(contents)
404-
dec := json.NewDecoder(r)
355+
if c.Credentials != "" {
356+
contents, _, err := pathorcontents.Read(c.Credentials)
357+
if err != nil {
358+
return nil, fmt.Errorf("Error loading credentials: %s", err)
359+
}
360+
361+
creds, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(contents), clientScopes...)
362+
if err != nil {
363+
return nil, fmt.Errorf("Unable to parse credentials from '%s': %s", contents, err)
364+
}
365+
366+
log.Printf("[INFO] Requesting Google token using Credential File %q...", c.Credentials)
367+
log.Printf("[INFO] -- Scopes: %s", clientScopes)
368+
return creds.TokenSource, nil
369+
}
405370

406-
return dec.Decode(result)
371+
log.Printf("[INFO] Authenticating using DefaultClient")
372+
log.Printf("[INFO] -- Scopes: %s", clientScopes)
373+
return googleoauth.DefaultTokenSource(context.Background(), clientScopes...)
407374
}

google/config_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,18 @@ func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) {
4848
t.Fatalf("expected error, but got nil")
4949
}
5050
}
51+
52+
func TestConfigLoadValidate_accessToken(t *testing.T) {
53+
accessToken := getTestAccessTokenFromEnv(t)
54+
55+
config := Config{
56+
AccessToken: accessToken,
57+
Project: "my-gce-project",
58+
Region: "us-central1",
59+
}
60+
61+
err := config.loadAndValidate()
62+
if err != nil {
63+
t.Fatalf("error: %v", err)
64+
}
65+
}

google/provider.go

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package google
22

33
import (
4-
"encoding/json"
4+
"context"
55
"fmt"
66
"os"
77

88
"github.com/hashicorp/terraform/helper/mutexkv"
99
"github.com/hashicorp/terraform/helper/schema"
1010
"github.com/hashicorp/terraform/terraform"
11+
12+
googleoauth "golang.org/x/oauth2/google"
1113
)
1214

1315
// Global MutexKV
@@ -28,6 +30,12 @@ func Provider() terraform.ResourceProvider {
2830
ValidateFunc: validateCredentials,
2931
},
3032

33+
"access_token": {
34+
Type: schema.TypeString,
35+
Optional: true,
36+
ConflictsWith: []string{"credentials"},
37+
},
38+
3139
"project": {
3240
Type: schema.TypeString,
3341
Optional: true,
@@ -244,12 +252,17 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
244252
}
245253

246254
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
247-
credentials := d.Get("credentials").(string)
248255
config := Config{
249-
Credentials: credentials,
250-
Project: d.Get("project").(string),
251-
Region: d.Get("region").(string),
252-
Zone: d.Get("zone").(string),
256+
Project: d.Get("project").(string),
257+
Region: d.Get("region").(string),
258+
Zone: d.Get("zone").(string),
259+
}
260+
261+
// Add credential source
262+
if v, ok := d.GetOk("access_token"); ok {
263+
config.AccessToken = v.(string)
264+
} else if v, ok := d.GetOk("credentials"); ok {
265+
config.Credentials = v.(string)
253266
}
254267

255268
if err := config.loadAndValidate(); err != nil {
@@ -268,10 +281,9 @@ func validateCredentials(v interface{}, k string) (warnings []string, errors []e
268281
if _, err := os.Stat(creds); err == nil {
269282
return
270283
}
271-
var account accountFile
272-
if err := json.Unmarshal([]byte(creds), &account); err != nil {
284+
if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(creds)); err != nil {
273285
errors = append(errors,
274-
fmt.Errorf("credentials are not valid JSON '%s': %s", creds, err))
286+
fmt.Errorf("JSON credentials in %q are not valid: %s", creds, err))
275287
}
276288

277289
return

google/provider_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ var billingAccountEnvVars = []string{
5757
"GOOGLE_BILLING_ACCOUNT",
5858
}
5959

60+
var accessTokenEnvVars = []string{
61+
"GOOGLE_OAUTH2_ACCESS_TOKEN",
62+
}
63+
6064
func init() {
6165
testAccProvider = Provider().(*schema.Provider)
6266
testAccRandomProvider = random.Provider().(*schema.Provider)
@@ -202,6 +206,11 @@ func getTestServiceAccountFromEnv(t *testing.T) string {
202206
return multiEnvSearch(serviceAccountEnvVars)
203207
}
204208

209+
func getTestAccessTokenFromEnv(t *testing.T) string {
210+
skipIfEnvNotSet(t, accessTokenEnvVars...)
211+
return multiEnvSearch(accessTokenEnvVars)
212+
}
213+
205214
func multiEnvSearch(ks []string) string {
206215
for _, k := range ks {
207216
if v := os.Getenv(k); v != "" {

0 commit comments

Comments
 (0)