Skip to content

Commit e7cd1b5

Browse files
chrisstmodular-magician
authored andcommitted
Add google_impersonated_credential datasource
Signed-off-by: Modular Magician <[email protected]>
1 parent 4d2ade9 commit e7cd1b5

10 files changed

+1933
-5
lines changed

google/config.go

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
dnsBeta "google.golang.org/api/dns/v1beta2"
3535
file "google.golang.org/api/file/v1beta1"
3636
"google.golang.org/api/iam/v1"
37+
iamcredentials "google.golang.org/api/iamcredentials/v1"
3738
cloudlogging "google.golang.org/api/logging/v2"
3839
"google.golang.org/api/pubsub/v1"
3940
runtimeconfig "google.golang.org/api/runtimeconfig/v1beta1"
@@ -73,6 +74,7 @@ type Config struct {
7374
clientDns *dns.Service
7475
clientDnsBeta *dnsBeta.Service
7576
clientFilestore *file.Service
77+
clientIamCredentials *iamcredentials.Service
7678
clientKms *cloudkms.Service
7779
clientLogging *cloudlogging.Service
7880
clientPubsub *pubsub.Service
@@ -240,6 +242,13 @@ func (c *Config) LoadAndValidate() error {
240242
}
241243
c.clientIAM.UserAgent = userAgent
242244

245+
log.Printf("[INFO] Instantiating Google Cloud IAMCredentials Client...")
246+
c.clientIamCredentials, err = iamcredentials.New(client)
247+
if err != nil {
248+
return err
249+
}
250+
c.clientIamCredentials.UserAgent = userAgent
251+
243252
log.Printf("[INFO] Instantiating Google Cloud Service Management Client...")
244253
c.clientServiceMan, err = servicemanagement.New(client)
245254
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"strings"
8+
"time"
9+
10+
"github.com/hashicorp/terraform/helper/schema"
11+
iamcredentials "google.golang.org/api/iamcredentials/v1"
12+
)
13+
14+
func dataSourceGoogleServiceAccountAccessToken() *schema.Resource {
15+
16+
return &schema.Resource{
17+
Read: dataSourceGoogleServiceAccountAccessTokenRead,
18+
Schema: map[string]*schema.Schema{
19+
"target_service_account": {
20+
Type: schema.TypeString,
21+
Required: true,
22+
ValidateFunc: validateRegexp("(" + strings.Join(PossibleServiceAccountNames, "|") + ")"),
23+
},
24+
"access_token": {
25+
Type: schema.TypeString,
26+
Sensitive: true,
27+
Computed: true,
28+
},
29+
"scopes": {
30+
Type: schema.TypeSet,
31+
Required: true,
32+
Elem: &schema.Schema{
33+
Type: schema.TypeString,
34+
StateFunc: func(v interface{}) string {
35+
return canonicalizeServiceScope(v.(string))
36+
},
37+
},
38+
// ValidateFunc is not yet supported on lists or sets.
39+
},
40+
"delegates": {
41+
Type: schema.TypeSet,
42+
Optional: true,
43+
Elem: &schema.Schema{
44+
Type: schema.TypeString,
45+
ValidateFunc: validateRegexp(ServiceAccountLinkRegex),
46+
},
47+
},
48+
"lifetime": {
49+
Type: schema.TypeString,
50+
Optional: true,
51+
ValidateFunc: validateDuration(), // duration <=3600s; TODO: support validteDuration(min,max)
52+
Default: "3600s",
53+
},
54+
},
55+
}
56+
}
57+
58+
func dataSourceGoogleServiceAccountAccessTokenRead(d *schema.ResourceData, meta interface{}) error {
59+
config := meta.(*Config)
60+
log.Printf("[INFO] Acquire Service Account AccessToken for %s", d.Get("target_service_account").(string))
61+
62+
service := config.clientIamCredentials
63+
64+
name := fmt.Sprintf("projects/-/serviceAccounts/%s", d.Get("target_service_account").(string))
65+
tokenRequest := &iamcredentials.GenerateAccessTokenRequest{
66+
Lifetime: d.Get("lifetime").(string),
67+
Delegates: convertStringSet(d.Get("delegates").(*schema.Set)),
68+
Scope: canonicalizeServiceScopes(convertStringSet(d.Get("scopes").(*schema.Set))),
69+
}
70+
at, err := service.Projects.ServiceAccounts.GenerateAccessToken(name, tokenRequest).Do()
71+
if err != nil {
72+
return err
73+
}
74+
75+
d.SetId(time.Now().UTC().String())
76+
d.Set("access_token", at.AccessToken)
77+
78+
return nil
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package google
2+
3+
import (
4+
"testing"
5+
6+
"fmt"
7+
8+
"github.com/hashicorp/terraform/helper/resource"
9+
"github.com/hashicorp/terraform/terraform"
10+
)
11+
12+
func testAccCheckServiceAccountAccessTokenValue(name, value string) resource.TestCheckFunc {
13+
return func(s *terraform.State) error {
14+
ms := s.RootModule()
15+
rs, ok := ms.Outputs[name]
16+
if !ok {
17+
return fmt.Errorf("Not found: %s", name)
18+
}
19+
20+
// TODO: validate the token belongs to the service account
21+
if rs.Value == "" {
22+
return fmt.Errorf("%s Cannot be empty", name)
23+
}
24+
25+
return nil
26+
}
27+
}
28+
29+
func TestAccDataSourceGoogleServiceAccountAccessToken_basic(t *testing.T) {
30+
t.Parallel()
31+
32+
resourceName := "data.google_service_account_access_token.default"
33+
34+
targetServiceAccountEmail := getTestServiceAccountFromEnv(t)
35+
36+
resource.Test(t, resource.TestCase{
37+
PreCheck: func() { testAccPreCheck(t) },
38+
Providers: testAccProviders,
39+
Steps: []resource.TestStep{
40+
{
41+
Config: testAccCheckGoogleServiceAccountAccessToken_datasource(targetServiceAccountEmail),
42+
Destroy: true,
43+
Check: resource.ComposeTestCheckFunc(
44+
resource.TestCheckResourceAttr(resourceName, "target_service_account", targetServiceAccountEmail),
45+
testAccCheckServiceAccountAccessTokenValue("access_token", targetServiceAccountEmail),
46+
),
47+
},
48+
},
49+
})
50+
}
51+
52+
func testAccCheckGoogleServiceAccountAccessToken_datasource(targetServiceAccountID string) string {
53+
54+
return fmt.Sprintf(`
55+
56+
data "google_service_account_access_token" "default" {
57+
target_service_account = "%s"
58+
scopes = ["userinfo-email", "https://www.googleapis.com/auth/cloud-platform"]
59+
lifetime = "30s"
60+
}
61+
62+
output "access_token" {
63+
value = "${data.google_service_account_access_token.default.access_token}"
64+
}
65+
`, targetServiceAccountID)
66+
}

google/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ func Provider() terraform.ResourceProvider {
117117
"google_project_organization_policy": dataSourceGoogleProjectOrganizationPolicy(),
118118
"google_project_services": dataSourceGoogleProjectServices(),
119119
"google_service_account": dataSourceGoogleServiceAccount(),
120+
"google_service_account_access_token": dataSourceGoogleServiceAccountAccessToken(),
120121
"google_service_account_key": dataSourceGoogleServiceAccountKey(),
121122
"google_storage_bucket_object": dataSourceGoogleStorageBucketObject(),
122123
"google_storage_object_signed_url": dataSourceGoogleSignedUrl(),

google/validation.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import (
1414

1515
const (
1616
// Copied from the official Google Cloud auto-generated client.
17-
ProjectRegex = "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))"
18-
RegionRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
19-
SubnetworkRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
17+
ProjectRegex = "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))"
18+
ProjectRegexWildCard = "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?)|-)"
19+
RegionRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
20+
SubnetworkRegex = "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?"
2021

2122
SubnetworkLinkRegex = "projects/(" + ProjectRegex + ")/regions/(" + RegionRegex + ")/subnetworks/(" + SubnetworkRegex + ")$"
2223

@@ -35,7 +36,7 @@ var (
3536
// 4 and 28 since the first and last character are excluded.
3637
ServiceAccountNameRegex = fmt.Sprintf(RFC1035NameTemplate, 4, 28)
3738

38-
ServiceAccountLinkRegexPrefix = "projects/" + ProjectRegex + "/serviceAccounts/"
39+
ServiceAccountLinkRegexPrefix = "projects/" + ProjectRegexWildCard + "/serviceAccounts/"
3940
PossibleServiceAccountNames = []string{
4041
AppEngineServiceAccountNameRegex,
4142
ComputeServiceAccountNameRegex,

0 commit comments

Comments
 (0)