-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for org policies at the organization level #523
Changes from all commits
ab4f96a
cc9226d
0f19b67
7dd8440
bf2eff1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package google | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestAccGoogleOrganizationPolicy_import(t *testing.T) { | ||
skipIfEnvNotSet(t, "GOOGLE_ORG") | ||
org := os.Getenv("GOOGLE_ORG") | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccGoogleOrganizationPolicy_list_allowAll(org), | ||
}, | ||
{ | ||
ResourceName: "google_organization_policy.listAll", | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
}, | ||
}, | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,318 @@ | ||
package google | ||
|
||
import ( | ||
"fmt" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
"google.golang.org/api/cloudresourcemanager/v1" | ||
"strings" | ||
) | ||
|
||
func resourceGoogleOrganizationPolicy() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceGoogleOrganizationPolicyCreate, | ||
Read: resourceGoogleOrganizationPolicyRead, | ||
Update: resourceGoogleOrganizationPolicyUpdate, | ||
Delete: resourceGoogleOrganizationPolicyDelete, | ||
|
||
Importer: &schema.ResourceImporter{ | ||
State: resourceGoogleOrganizationPolicyImportState, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"org_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"constraint": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
DiffSuppressFunc: linkDiffSuppress, | ||
}, | ||
"boolean_policy": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
ConflictsWith: []string{"list_policy"}, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"enforced": { | ||
Type: schema.TypeBool, | ||
Required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"list_policy": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
ConflictsWith: []string{"boolean_policy"}, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"allow": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
ConflictsWith: []string{"list_policy.0.deny"}, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"all": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Default: false, | ||
ConflictsWith: []string{"list_policy.0.allow.0.values"}, | ||
}, | ||
"values": { | ||
Type: schema.TypeSet, | ||
Optional: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Set: schema.HashString, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"deny": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"all": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Default: false, | ||
ConflictsWith: []string{"list_policy.0.deny.0.values"}, | ||
}, | ||
"values": { | ||
Type: schema.TypeSet, | ||
Optional: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Set: schema.HashString, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"suggested_value": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Computed: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"version": { | ||
Type: schema.TypeInt, | ||
Optional: true, | ||
Computed: true, | ||
}, | ||
"etag": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"update_time": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceGoogleOrganizationPolicyCreate(d *schema.ResourceData, meta interface{}) error { | ||
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.(*Config) | ||
org := "organizations/" + d.Get("org_id").(string) | ||
|
||
policy, err := config.clientResourceManager.Organizations.GetOrgPolicy(org, &cloudresourcemanager.GetOrgPolicyRequest{ | ||
Constraint: canonicalOrgPolicyConstraint(d.Get("constraint").(string)), | ||
}).Do() | ||
|
||
if err != nil { | ||
return handleNotFoundError(err, d, fmt.Sprintf("Organization policy for %s", org)) | ||
} | ||
|
||
d.Set("constraint", policy.Constraint) | ||
d.Set("boolean_policy", flattenBooleanOrganizationPolicy(policy.BooleanPolicy)) | ||
d.Set("list_policy", flattenListOrganizationPolicy(policy.ListPolicy)) | ||
d.Set("version", policy.Version) | ||
d.Set("etag", policy.Etag) | ||
d.Set("update_time", policy.UpdateTime) | ||
|
||
return nil | ||
} | ||
|
||
func resourceGoogleOrganizationPolicyUpdate(d *schema.ResourceData, meta interface{}) error { | ||
if err := setOrganizationPolicy(d, meta); err != nil { | ||
return err | ||
} | ||
|
||
return resourceGoogleOrganizationPolicyRead(d, meta) | ||
} | ||
|
||
func resourceGoogleOrganizationPolicyDelete(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
org := "organizations/" + d.Get("org_id").(string) | ||
|
||
_, err := config.clientResourceManager.Organizations.ClearOrgPolicy(org, &cloudresourcemanager.ClearOrgPolicyRequest{ | ||
Constraint: canonicalOrgPolicyConstraint(d.Get("constraint").(string)), | ||
}).Do() | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceGoogleOrganizationPolicyImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
parts := strings.Split(d.Id(), ":") | ||
if len(parts) != 2 { | ||
return nil, fmt.Errorf("Invalid id format. Expecting {org_id}:{constraint}, got '%s' instead.", d.Id()) | ||
} | ||
|
||
d.Set("org_id", parts[0]) | ||
d.Set("constraint", parts[1]) | ||
|
||
return []*schema.ResourceData{d}, nil | ||
} | ||
|
||
func setOrganizationPolicy(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
org := "organizations/" + d.Get("org_id").(string) | ||
|
||
listPolicy, err := expandListOrganizationPolicy(d.Get("list_policy").([]interface{})) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = config.clientResourceManager.Organizations.SetOrgPolicy(org, &cloudresourcemanager.SetOrgPolicyRequest{ | ||
Policy: &cloudresourcemanager.OrgPolicy{ | ||
Constraint: canonicalOrgPolicyConstraint(d.Get("constraint").(string)), | ||
BooleanPolicy: expandBooleanOrganizationPolicy(d.Get("boolean_policy").([]interface{})), | ||
ListPolicy: listPolicy, | ||
Version: int64(d.Get("version").(int)), | ||
Etag: d.Get("etag").(string), | ||
}, | ||
}).Do() | ||
|
||
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 expandBooleanOrganizationPolicy(configured []interface{}) *cloudresourcemanager.BooleanPolicy { | ||
if len(configured) == 0 { | ||
return nil | ||
} | ||
|
||
booleanPolicy := configured[0].(map[string]interface{}) | ||
return &cloudresourcemanager.BooleanPolicy{ | ||
Enforced: booleanPolicy["enforced"].(bool), | ||
} | ||
} | ||
|
||
func flattenListOrganizationPolicy(policy *cloudresourcemanager.ListPolicy) []map[string]interface{} { | ||
lPolicies := make([]map[string]interface{}, 0, 1) | ||
|
||
if policy == nil { | ||
return lPolicies | ||
} | ||
|
||
listPolicy := map[string]interface{}{} | ||
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, convertStringArrToInterface(policy.AllowedValues)), | ||
}} | ||
case len(policy.DeniedValues) > 0: | ||
listPolicy["deny"] = []interface{}{map[string]interface{}{ | ||
"values": schema.NewSet(schema.HashString, convertStringArrToInterface(policy.DeniedValues)), | ||
}} | ||
} | ||
|
||
lPolicies = append(lPolicies, listPolicy) | ||
|
||
return lPolicies | ||
} | ||
|
||
func expandListOrganizationPolicy(configured []interface{}) (*cloudresourcemanager.ListPolicy, error) { | ||
if len(configured) == 0 { | ||
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 = 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 = convertStringArr(values.List()) | ||
} | ||
} | ||
|
||
listPolicy := configured[0].(map[string]interface{}) | ||
return &cloudresourcemanager.ListPolicy{ | ||
AllValues: allValues, | ||
AllowedValues: allowedValues, | ||
DeniedValues: deniedValues, | ||
SuggestedValue: listPolicy["suggested_value"].(string), | ||
}, nil | ||
} | ||
|
||
func canonicalOrgPolicyConstraint(constraint string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we allow people to set a non-canonical version, wouldn't that lead to diffs next plan once the constraint comes back from the api with a canonical version? Should be an easy enough fix with a diffsuppress There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already a |
||
if strings.HasPrefix(constraint, "constraints/") { | ||
return constraint | ||
} | ||
return "constraints/" + constraint | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you have this conflict with values? (should be possible with
list_policy.0.allow.0.values
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I thought ConflictsWith didn't work with list but when there is only one item in the list, it does. I will fix this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done