Skip to content

Commit

Permalink
Add support for org policies at the organization level (hashicorp#523)
Browse files Browse the repository at this point in the history
* Fetch latest resource manager client
* Add new resource to manage Org Policy at the organization level.
* Update documentation
  • Loading branch information
rosbo authored and Nic Cope committed Oct 16, 2017
1 parent 61ead60 commit ecc976f
Show file tree
Hide file tree
Showing 11 changed files with 8,570 additions and 1,877 deletions.
28 changes: 28 additions & 0 deletions google/import_google_organization_policy_test.go
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,
},
},
})
}
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ func Provider() terraform.ResourceProvider {
"google_sql_database": resourceSqlDatabase(),
"google_sql_database_instance": resourceSqlDatabaseInstance(),
"google_sql_user": resourceSqlUser(),
"google_organization_policy": resourceGoogleOrganizationPolicy(),
"google_project": resourceGoogleProject(),
"google_project_iam_policy": resourceGoogleProjectIamPolicy(),
"google_project_iam_binding": resourceGoogleProjectIamBinding(),
Expand Down
6 changes: 1 addition & 5 deletions google/resource_compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -839,13 +839,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
// Set the service accounts
serviceAccounts := make([]map[string]interface{}, 0, 1)
for _, serviceAccount := range instance.ServiceAccounts {
scopes := make([]interface{}, len(serviceAccount.Scopes))
for i, scope := range serviceAccount.Scopes {
scopes[i] = scope
}
serviceAccounts = append(serviceAccounts, map[string]interface{}{
"email": serviceAccount.Email,
"scopes": schema.NewSet(stringScopeHashcode, scopes),
"scopes": schema.NewSet(stringScopeHashcode, convertStringArrToInterface(serviceAccount.Scopes)),
})
}
d.Set("service_account", serviceAccounts)
Expand Down
6 changes: 4 additions & 2 deletions google/resource_google_folder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package google

import (
"encoding/json"
"fmt"
"github.com/hashicorp/terraform/helper/schema"
resourceManagerV2Beta1 "google.golang.org/api/cloudresourcemanager/v2beta1"
Expand Down Expand Up @@ -75,8 +76,9 @@ func resourceGoogleFolderCreate(d *schema.ResourceData, meta interface{}) error
}

// Requires 3 successive checks for safety. Nested IFs are used to avoid 3 error statement with the same message.
if response, ok := waitOp.Response.(map[string]interface{}); ok {
if val, ok := response["name"]; ok {
var responseMap map[string]interface{}
if err := json.Unmarshal(waitOp.Response, &responseMap); err == nil {
if val, ok := responseMap["name"]; ok {
if name, ok := val.(string); ok {
d.SetId(name)
return resourceGoogleFolderRead(d, meta)
Expand Down
318 changes: 318 additions & 0 deletions google/resource_google_organization_policy.go
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 {
if strings.HasPrefix(constraint, "constraints/") {
return constraint
}
return "constraints/" + constraint
}
Loading

0 comments on commit ecc976f

Please sign in to comment.