Skip to content

Commit 10ffc87

Browse files
reechar-googmodular-magician
authored andcommitted
IAM Policy on subnets is now GA, moving into non-beta provider
Signed-off-by: Modular Magician <[email protected]>
1 parent 74f706b commit 10ffc87

5 files changed

+412
-98
lines changed

google/bootstrap_utils_test.go

-94
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"testing"
88

99
"google.golang.org/api/cloudkms/v1"
10-
"google.golang.org/api/iam/v1"
1110
)
1211

1312
var SharedKeyRing = "tftest-shared-keyring-1"
@@ -104,96 +103,3 @@ func BootstrapKMSKey(t *testing.T) bootstrappedKMS {
104103
cryptoKey,
105104
}
106105
}
107-
108-
var serviceAccountEmail = "tf-bootstrap-service-account"
109-
var serviceAccountDisplay = "Bootstrapped Service Account for Terraform tests"
110-
111-
// Some tests need a second service account, other than the test runner, to assert functionality on.
112-
// This provides a well-known service account that can be used when dynamically creating a service
113-
// account isn't an option.
114-
func getOrCreateServiceAccount(config Config, project string) (*iam.ServiceAccount, error) {
115-
name := fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, serviceAccountEmail, project)
116-
log.Printf("[DEBUG] Verifying %s as bootstrapped service account.\n", name)
117-
118-
sa, err := config.clientIAM.Projects.ServiceAccounts.Get(name).Do()
119-
if err != nil && !isGoogleApiErrorWithCode(err, 404) {
120-
return nil, err
121-
}
122-
123-
if sa == nil {
124-
log.Printf("[DEBUG] Account missing. Creating %s as bootstrapped service account.\n", name)
125-
sa = &iam.ServiceAccount{
126-
DisplayName: serviceAccountDisplay,
127-
}
128-
129-
r := &iam.CreateServiceAccountRequest{
130-
AccountId: serviceAccountEmail,
131-
ServiceAccount: sa,
132-
}
133-
sa, err = config.clientIAM.Projects.ServiceAccounts.Create("projects/"+project, r).Do()
134-
if err != nil {
135-
return nil, err
136-
}
137-
}
138-
139-
return sa, nil
140-
}
141-
142-
// In order to test impersonation we need to grant the testRunner's account the ability to grant tokens
143-
// on a different service account. Granting permissions takes time and there is no operation to wait on
144-
// so instead this creates a single service account once per test-suite with the correct permissions.
145-
// The first time this test is run it will fail, but subsequent runs will succeed.
146-
func impersonationServiceAccountPermissions(config Config, sa *iam.ServiceAccount, testRunner string) error {
147-
log.Printf("[DEBUG] Setting service account permissions.\n")
148-
policy := iam.Policy{
149-
Bindings: []*iam.Binding{},
150-
}
151-
152-
binding := &iam.Binding{
153-
Role: "roles/iam.serviceAccountTokenCreator",
154-
Members: []string{"serviceAccount:" + sa.Email, "serviceAccount:" + testRunner},
155-
}
156-
policy.Bindings = append(policy.Bindings, binding)
157-
158-
// Overwrite the roles each time on this service account. This is because this account is
159-
// only created for the test suite and will stop snowflaking of permissions to get tests
160-
// to run. Overwriting permissions on 1 service account shouldn't affect others.
161-
_, err := config.clientIAM.Projects.ServiceAccounts.SetIamPolicy(sa.Name, &iam.SetIamPolicyRequest{
162-
Policy: &policy,
163-
}).Do()
164-
if err != nil {
165-
return err
166-
}
167-
168-
return nil
169-
}
170-
171-
func BootstrapServiceAccount(t *testing.T, project, testRunner string) string {
172-
if v := os.Getenv("TF_ACC"); v == "" {
173-
log.Println("Acceptance tests and bootstrapping skipped unless env 'TF_ACC' set")
174-
return ""
175-
}
176-
177-
config := Config{
178-
Credentials: getTestCredsFromEnv(),
179-
Project: getTestProjectFromEnv(),
180-
Region: getTestRegionFromEnv(),
181-
Zone: getTestZoneFromEnv(),
182-
}
183-
184-
if err := config.LoadAndValidate(); err != nil {
185-
t.Fatalf("Bootstrapping failed. Unable to load test config: %s", err)
186-
}
187-
188-
sa, err := getOrCreateServiceAccount(config, project)
189-
if err != nil {
190-
t.Fatalf("Bootstrapping failed. Cannot retrieve service account, %s", err)
191-
}
192-
193-
err = impersonationServiceAccountPermissions(config, sa, testRunner)
194-
if err != nil {
195-
t.Fatalf("Bootstrapping failed. Cannot set service account permissions, %s", err)
196-
}
197-
198-
return sa.Email
199-
}

google/data_source_google_service_account_access_token_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ func TestAccDataSourceGoogleServiceAccountAccessToken_basic(t *testing.T) {
3030
t.Parallel()
3131

3232
resourceName := "data.google_service_account_access_token.default"
33-
serviceAccount := getTestServiceAccountFromEnv(t)
34-
targetServiceAccountEmail := BootstrapServiceAccount(t, getTestProjectFromEnv(), serviceAccount)
33+
34+
targetServiceAccountEmail := getTestServiceAccountFromEnv(t)
3535

3636
resource.Test(t, resource.TestCase{
3737
PreCheck: func() { testAccPreCheck(t) },

google/iam_compute_subnetwork.go

+162-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,164 @@
11
package google
22

3-
// Magic Modules doesn't let us remove files - blank out beta-only common-compile files for now.
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/hashicorp/errwrap"
8+
"github.com/hashicorp/terraform/helper/schema"
9+
"google.golang.org/api/cloudresourcemanager/v1"
10+
"google.golang.org/api/compute/v1"
11+
)
12+
13+
var IamComputeSubnetworkSchema = map[string]*schema.Schema{
14+
"subnetwork": {
15+
Type: schema.TypeString,
16+
Required: true,
17+
ForceNew: true,
18+
},
19+
20+
"project": {
21+
Type: schema.TypeString,
22+
Optional: true,
23+
Computed: true,
24+
ForceNew: true,
25+
},
26+
27+
"region": {
28+
Type: schema.TypeString,
29+
Optional: true,
30+
Computed: true,
31+
ForceNew: true,
32+
},
33+
}
34+
35+
type ComputeSubnetworkIamUpdater struct {
36+
project string
37+
region string
38+
resourceId string
39+
Config *Config
40+
}
41+
42+
func NewComputeSubnetworkIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) {
43+
project, err := getProject(d, config)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
region, err := getRegion(d, config)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
return &ComputeSubnetworkIamUpdater{
54+
project: project,
55+
region: region,
56+
resourceId: d.Get("subnetwork").(string),
57+
Config: config,
58+
}, nil
59+
}
60+
61+
func ComputeSubnetworkIdParseFunc(d *schema.ResourceData, config *Config) error {
62+
parts := strings.Split(d.Id(), "/")
63+
var fv *RegionalFieldValue
64+
if len(parts) == 3 {
65+
// {project}/{region}/{name} syntax
66+
fv = &RegionalFieldValue{
67+
Project: parts[0],
68+
Region: parts[1],
69+
Name: parts[2],
70+
resourceType: "subnetworks",
71+
}
72+
} else if len(parts) == 2 {
73+
// /{region}/{name} syntax
74+
project, err := getProject(d, config)
75+
if err != nil {
76+
return err
77+
}
78+
fv = &RegionalFieldValue{
79+
Project: project,
80+
Region: parts[0],
81+
Name: parts[1],
82+
resourceType: "subnetworks",
83+
}
84+
} else {
85+
// We either have a name or a full self link, so use the field helper
86+
var err error
87+
fv, err = ParseSubnetworkFieldValue(d.Id(), d, config)
88+
if err != nil {
89+
return err
90+
}
91+
}
92+
d.Set("subnetwork", fv.Name)
93+
d.Set("project", fv.Project)
94+
d.Set("region", fv.Region)
95+
96+
// Explicitly set the id so imported resources have the same ID format as non-imported ones.
97+
d.SetId(fv.RelativeLink())
98+
return nil
99+
}
100+
101+
func (u *ComputeSubnetworkIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
102+
p, err := u.Config.clientCompute.Subnetworks.GetIamPolicy(u.project, u.region, u.resourceId).Do()
103+
104+
if err != nil {
105+
return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
106+
}
107+
108+
cloudResourcePolicy, err := computeToResourceManagerPolicy(p)
109+
110+
if err != nil {
111+
return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err)
112+
}
113+
114+
return cloudResourcePolicy, nil
115+
}
116+
117+
func (u *ComputeSubnetworkIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
118+
computePolicy, err := resourceManagerToComputePolicy(policy)
119+
120+
if err != nil {
121+
return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err)
122+
}
123+
124+
req := &compute.RegionSetPolicyRequest{
125+
Policy: computePolicy,
126+
}
127+
_, err = u.Config.clientCompute.Subnetworks.SetIamPolicy(u.project, u.region, u.resourceId, req).Do()
128+
129+
if err != nil {
130+
return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err)
131+
}
132+
133+
return nil
134+
}
135+
136+
func (u *ComputeSubnetworkIamUpdater) GetResourceId() string {
137+
return fmt.Sprintf("projects/%s/regions/%s/subnetworks/%s", u.project, u.region, u.resourceId)
138+
}
139+
140+
func (u *ComputeSubnetworkIamUpdater) GetMutexKey() string {
141+
return fmt.Sprintf("iam-compute-subnetwork-%s-%s-%s", u.project, u.region, u.resourceId)
142+
}
143+
144+
func (u *ComputeSubnetworkIamUpdater) DescribeResource() string {
145+
return fmt.Sprintf("Compute Subnetwork %s/%s/%s", u.project, u.region, u.resourceId)
146+
}
147+
148+
func resourceManagerToComputePolicy(p *cloudresourcemanager.Policy) (*compute.Policy, error) {
149+
out := &compute.Policy{}
150+
err := Convert(p, out)
151+
if err != nil {
152+
return nil, errwrap.Wrapf("Cannot convert a resourcemanager policy to a compute policy: {{err}}", err)
153+
}
154+
return out, nil
155+
}
156+
157+
func computeToResourceManagerPolicy(p *compute.Policy) (*cloudresourcemanager.Policy, error) {
158+
out := &cloudresourcemanager.Policy{}
159+
err := Convert(p, out)
160+
if err != nil {
161+
return nil, errwrap.Wrapf("Cannot convert a compute policy to a resourcemanager policy: {{err}}", err)
162+
}
163+
return out, nil
164+
}

google/provider.go

+3
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
183183
"google_compute_security_policy": resourceComputeSecurityPolicy(),
184184
"google_compute_shared_vpc_host_project": resourceComputeSharedVpcHostProject(),
185185
"google_compute_shared_vpc_service_project": resourceComputeSharedVpcServiceProject(),
186+
"google_compute_subnetwork_iam_binding": ResourceIamBindingWithImport(IamComputeSubnetworkSchema, NewComputeSubnetworkIamUpdater, ComputeSubnetworkIdParseFunc),
187+
"google_compute_subnetwork_iam_member": ResourceIamMemberWithImport(IamComputeSubnetworkSchema, NewComputeSubnetworkIamUpdater, ComputeSubnetworkIdParseFunc),
188+
"google_compute_subnetwork_iam_policy": ResourceIamPolicyWithImport(IamComputeSubnetworkSchema, NewComputeSubnetworkIamUpdater, ComputeSubnetworkIdParseFunc),
186189
"google_compute_target_pool": resourceComputeTargetPool(),
187190
"google_container_cluster": resourceContainerCluster(),
188191
"google_container_node_pool": resourceContainerNodePool(),

0 commit comments

Comments
 (0)