forked from GoogleCloudPlatform/magic-modules
/
resource_google_organization_policy.go
483 lines (425 loc) · 15.6 KB
/
resource_google_organization_policy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
package resourcemanager
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
"google.golang.org/api/cloudresourcemanager/v1"
)
var schemaOrganizationPolicy = map[string]*schema.Schema{
// Although the API suggests that boolean_policy, list_policy, or restore_policy must be set,
// Organization policies can be "inherited from parent" in the UI, and this is the default
// state of the resource without any policy set.
// See https://github.com/hashicorp/terraform-provider-google/issues/3607
"constraint": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName,
Description: `The name of the Constraint the Policy is configuring, for example, serviceuser.services.`,
},
"boolean_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: `A boolean policy is a constraint that is either enforced or not.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enforced": {
Type: schema.TypeBool,
Required: true,
Description: `If true, then the Policy is enforced. If false, then any configuration is acceptable.`,
},
},
},
},
"list_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: `A policy that can define specific values that are allowed or denied for the given constraint. It can also be used to allow or deny all values. `,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"allow": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: `One or the other must be set.`,
ExactlyOneOf: []string{"list_policy.0.allow", "list_policy.0.deny"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"all": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: `The policy allows or denies all values.`,
ExactlyOneOf: []string{"list_policy.0.allow.0.all", "list_policy.0.allow.0.values"},
},
"values": {
Type: schema.TypeSet,
Optional: true,
Description: `The policy can define specific values that are allowed or denied.`,
ExactlyOneOf: []string{"list_policy.0.allow.0.all", "list_policy.0.allow.0.values"},
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
"deny": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: `One or the other must be set.`,
ExactlyOneOf: []string{"list_policy.0.allow", "list_policy.0.deny"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"all": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: `The policy allows or denies all values.`,
ExactlyOneOf: []string{"list_policy.0.deny.0.all", "list_policy.0.deny.0.values"},
},
"values": {
Type: schema.TypeSet,
Optional: true,
Description: `The policy can define specific values that are allowed or denied.`,
ExactlyOneOf: []string{"list_policy.0.deny.0.all", "list_policy.0.deny.0.values"},
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
"suggested_value": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: `The Google Cloud Console will try to default to a configuration that matches the value specified in this field.`,
},
"inherit_from_parent": {
Type: schema.TypeBool,
Optional: true,
Description: `If set to true, the values from the effective Policy of the parent resource are inherited, meaning the values set in this Policy are added to the values inherited up the hierarchy.`,
},
},
},
},
"version": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: `Version of the Policy. Default version is 0.`,
},
"etag": {
Type: schema.TypeString,
Computed: true,
Description: `The etag of the organization policy. etag is used for optimistic concurrency control as a way to help prevent simultaneous updates of a policy from overwriting each other.`,
},
"update_time": {
Type: schema.TypeString,
Computed: true,
Description: `The timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds, representing when the variable was last updated. Example: "2016-10-09T12:33:37.578138407Z".`,
},
"restore_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: `A restore policy is a constraint to restore the default policy.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default": {
Type: schema.TypeBool,
Required: true,
Description: `May only be set to true. If set, then the default Policy is restored.`,
},
},
},
},
}
func ResourceGoogleOrganizationPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleOrganizationPolicyCreate,
Read: resourceGoogleOrganizationPolicyRead,
Update: resourceGoogleOrganizationPolicyUpdate,
Delete: resourceGoogleOrganizationPolicyDelete,
Importer: &schema.ResourceImporter{
State: resourceGoogleOrganizationPolicyImportState,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(4 * time.Minute),
Update: schema.DefaultTimeout(4 * time.Minute),
Read: schema.DefaultTimeout(4 * time.Minute),
Delete: schema.DefaultTimeout(4 * time.Minute),
},
Schema: tpgresource.MergeSchemas(
schemaOrganizationPolicy,
map[string]*schema.Schema{
"org_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
}),
UseJSONNumber: true,
}
}
func resourceGoogleOrganizationPolicyCreate(d *schema.ResourceData, meta interface{}) error {
if isOrganizationPolicyUnset(d) {
return resourceGoogleOrganizationPolicyDelete(d, meta)
}
if err := setOrganizationPolicy(d, meta); err != nil {
return err
}
d.SetId(fmt.Sprintf("%s/%s", d.Get("org_id"), d.Get("constraint").(string)))
return resourceGoogleOrganizationPolicyRead(d, meta)
}
func resourceGoogleOrganizationPolicyRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*transport_tpg.Config)
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}
org := "organizations/" + d.Get("org_id").(string)
var policy *cloudresourcemanager.OrgPolicy
err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() (readErr error) {
policy, readErr = config.NewResourceManagerClient(userAgent).Organizations.GetOrgPolicy(org, &cloudresourcemanager.GetOrgPolicyRequest{
Constraint: CanonicalOrgPolicyConstraint(d.Get("constraint").(string)),
}).Do()
return readErr
},
Timeout: d.Timeout(schema.TimeoutRead),
})
if err != nil {
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Organization policy for %s", org))
}
if err := d.Set("constraint", policy.Constraint); err != nil {
return fmt.Errorf("Error setting constraint: %s", err)
}
if err := d.Set("boolean_policy", flattenBooleanOrganizationPolicy(policy.BooleanPolicy)); err != nil {
return fmt.Errorf("Error setting boolean_policy: %s", err)
}
if err := d.Set("list_policy", flattenListOrganizationPolicy(policy.ListPolicy)); err != nil {
return fmt.Errorf("Error setting list_policy: %s", err)
}
if err := d.Set("version", policy.Version); err != nil {
return fmt.Errorf("Error setting version: %s", err)
}
if err := d.Set("etag", policy.Etag); err != nil {
return fmt.Errorf("Error setting etag: %s", err)
}
if err := d.Set("update_time", policy.UpdateTime); err != nil {
return fmt.Errorf("Error setting update_time: %s", err)
}
if err := d.Set("restore_policy", flattenRestoreOrganizationPolicy(policy.RestoreDefault)); err != nil {
return fmt.Errorf("Error setting restore_policy: %s", err)
}
return nil
}
func resourceGoogleOrganizationPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
if isOrganizationPolicyUnset(d) {
return resourceGoogleOrganizationPolicyDelete(d, meta)
}
if err := setOrganizationPolicy(d, meta); err != nil {
return err
}
return resourceGoogleOrganizationPolicyRead(d, meta)
}
func resourceGoogleOrganizationPolicyDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*transport_tpg.Config)
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}
org := "organizations/" + d.Get("org_id").(string)
err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() error {
_, dErr := config.NewResourceManagerClient(userAgent).Organizations.ClearOrgPolicy(org, &cloudresourcemanager.ClearOrgPolicyRequest{
Constraint: CanonicalOrgPolicyConstraint(d.Get("constraint").(string)),
}).Do()
return dErr
},
Timeout: d.Timeout(schema.TimeoutDelete),
})
if err != nil {
return err
}
return nil
}
func resourceGoogleOrganizationPolicyImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.SplitN(d.Id(), "/", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid id format. Expecting {org_id}/{constraint}, got '%s' instead.", d.Id())
}
if err := d.Set("org_id", parts[0]); err != nil {
return nil, fmt.Errorf("Error setting org_id: %s", err)
}
if err := d.Set("constraint", parts[1]); err != nil {
return nil, fmt.Errorf("Error setting constraint: %s", err)
}
return []*schema.ResourceData{d}, nil
}
// Organization policies can be "inherited from parent" the UI, and this is the default
// state of the resource without any policy set. In order to revert to this state the current
// resource cannot be updated it must instead be Deleted. This allows Terraform to assert that
// no policy has been set even if previously one had.
// See https://github.com/hashicorp/terraform-provider-google/issues/3607
func isOrganizationPolicyUnset(d *schema.ResourceData) bool {
listPolicy := d.Get("list_policy").([]interface{})
booleanPolicy := d.Get("boolean_policy").([]interface{})
restorePolicy := d.Get("restore_policy").([]interface{})
return len(listPolicy)+len(booleanPolicy)+len(restorePolicy) == 0
}
func setOrganizationPolicy(d *schema.ResourceData, meta interface{}) error {
config := meta.(*transport_tpg.Config)
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}
org := "organizations/" + d.Get("org_id").(string)
listPolicy, err := expandListOrganizationPolicy(d.Get("list_policy").([]interface{}))
if err != nil {
return err
}
restoreDefault, err := expandRestoreOrganizationPolicy(d.Get("restore_policy").([]interface{}))
if err != nil {
return err
}
err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() (setErr error) {
_, setErr = config.NewResourceManagerClient(userAgent).Organizations.SetOrgPolicy(org, &cloudresourcemanager.SetOrgPolicyRequest{
Policy: &cloudresourcemanager.OrgPolicy{
Constraint: CanonicalOrgPolicyConstraint(d.Get("constraint").(string)),
BooleanPolicy: expandBooleanOrganizationPolicy(d.Get("boolean_policy").([]interface{})),
ListPolicy: listPolicy,
RestoreDefault: restoreDefault,
Version: int64(d.Get("version").(int)),
Etag: d.Get("etag").(string),
},
}).Do()
return setErr
},
Timeout: d.Timeout(schema.TimeoutCreate),
})
return err
}
func flattenBooleanOrganizationPolicy(policy *cloudresourcemanager.BooleanPolicy) []map[string]interface{} {
bPolicies := make([]map[string]interface{}, 0, 1)
if policy == nil {
return bPolicies
}
bPolicies = append(bPolicies, map[string]interface{}{
"enforced": policy.Enforced,
})
return bPolicies
}
func flattenRestoreOrganizationPolicy(restore_policy *cloudresourcemanager.RestoreDefault) []map[string]interface{} {
rp := make([]map[string]interface{}, 0, 1)
if restore_policy == nil {
return rp
}
rp = append(rp, map[string]interface{}{
"default": true,
})
return rp
}
func expandBooleanOrganizationPolicy(configured []interface{}) *cloudresourcemanager.BooleanPolicy {
if len(configured) == 0 || configured[0] == nil {
return nil
}
booleanPolicy := configured[0].(map[string]interface{})
return &cloudresourcemanager.BooleanPolicy{
Enforced: booleanPolicy["enforced"].(bool),
}
}
func expandRestoreOrganizationPolicy(configured []interface{}) (*cloudresourcemanager.RestoreDefault, error) {
if len(configured) == 0 || configured[0] == nil {
return nil, nil
}
restoreDefaultMap := configured[0].(map[string]interface{})
default_value := restoreDefaultMap["default"].(bool)
if default_value {
return &cloudresourcemanager.RestoreDefault{}, nil
}
return nil, fmt.Errorf("Invalid value for restore_policy. Expecting default = true")
}
func flattenListOrganizationPolicy(policy *cloudresourcemanager.ListPolicy) []map[string]interface{} {
lPolicies := make([]map[string]interface{}, 0, 1)
if policy == nil {
return lPolicies
}
listPolicy := map[string]interface{}{
"suggested_value": policy.SuggestedValue,
"inherit_from_parent": policy.InheritFromParent,
}
switch {
case policy.AllValues == "ALLOW":
listPolicy["allow"] = []interface{}{map[string]interface{}{
"all": true,
}}
case policy.AllValues == "DENY":
listPolicy["deny"] = []interface{}{map[string]interface{}{
"all": true,
}}
case len(policy.AllowedValues) > 0:
listPolicy["allow"] = []interface{}{map[string]interface{}{
"values": schema.NewSet(schema.HashString, tpgresource.ConvertStringArrToInterface(policy.AllowedValues)),
}}
case len(policy.DeniedValues) > 0:
listPolicy["deny"] = []interface{}{map[string]interface{}{
"values": schema.NewSet(schema.HashString, tpgresource.ConvertStringArrToInterface(policy.DeniedValues)),
}}
}
lPolicies = append(lPolicies, listPolicy)
return lPolicies
}
func expandListOrganizationPolicy(configured []interface{}) (*cloudresourcemanager.ListPolicy, error) {
if len(configured) == 0 || configured[0] == nil {
return nil, nil
}
listPolicyMap := configured[0].(map[string]interface{})
allow := listPolicyMap["allow"].([]interface{})
deny := listPolicyMap["deny"].([]interface{})
var allValues string
var allowedValues []string
var deniedValues []string
if len(allow) > 0 {
allowMap := allow[0].(map[string]interface{})
all := allowMap["all"].(bool)
values := allowMap["values"].(*schema.Set)
if all {
allValues = "ALLOW"
} else {
allowedValues = tpgresource.ConvertStringArr(values.List())
}
}
if len(deny) > 0 {
denyMap := deny[0].(map[string]interface{})
all := denyMap["all"].(bool)
values := denyMap["values"].(*schema.Set)
if all {
allValues = "DENY"
} else {
deniedValues = tpgresource.ConvertStringArr(values.List())
}
}
listPolicy := configured[0].(map[string]interface{})
return &cloudresourcemanager.ListPolicy{
AllValues: allValues,
AllowedValues: allowedValues,
DeniedValues: deniedValues,
SuggestedValue: listPolicy["suggested_value"].(string),
InheritFromParent: listPolicy["inherit_from_parent"].(bool),
ForceSendFields: []string{"InheritFromParent"},
}, nil
}
func CanonicalOrgPolicyConstraint(constraint string) string {
if strings.HasPrefix(constraint, "constraints/") {
return constraint
}
return "constraints/" + constraint
}