terraform-provider-google/google/resource_google_project_iam_policy.go
2018-12-20 21:36:11 -08:00

305 lines
9.1 KiB
Go

package google
import (
"encoding/json"
"fmt"
"log"
"reflect"
"sort"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)
func resourceGoogleProjectIamPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectIamPolicyCreate,
Read: resourceGoogleProjectIamPolicyRead,
Update: resourceGoogleProjectIamPolicyUpdate,
Delete: resourceGoogleProjectIamPolicyDelete,
Importer: &schema.ResourceImporter{
State: resourceGoogleProjectIamPolicyImport,
},
Schema: map[string]*schema.Schema{
"project": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"policy_data": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: jsonPolicyDiffSuppress,
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
"authoritative": {
Removed: "The authoritative field was removed. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information.",
Type: schema.TypeBool,
Optional: true,
},
"restore_policy": {
Removed: "This field was removed alongside the authoritative field. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information.",
Type: schema.TypeString,
Computed: true,
},
"disable_project": {
Removed: "This field was removed alongside the authoritative field. Use lifecycle.prevent_destroy instead.",
Type: schema.TypeBool,
Optional: true,
},
},
}
}
func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project := d.Get("project").(string)
mutexKey := getProjectIamPolicyMutexKey(project)
mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey)
// Get the policy in the template
policy, err := getResourceIamPolicy(d)
if err != nil {
return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
}
log.Printf("[DEBUG] Setting IAM policy for project %q", project)
err = setProjectIamPolicy(policy, config, project)
if err != nil {
return err
}
d.SetId(project)
return resourceGoogleProjectIamPolicyRead(d, meta)
}
func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project := d.Get("project").(string)
policy, err := getProjectIamPolicy(project, config)
if err != nil {
return err
}
policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings, AuditConfigs: policy.AuditConfigs})
if err != nil {
return fmt.Errorf("Error marshaling IAM policy: %v", err)
}
d.Set("etag", policy.Etag)
d.Set("policy_data", string(policyBytes))
d.Set("project", project)
return nil
}
func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project := d.Get("project").(string)
mutexKey := getProjectIamPolicyMutexKey(project)
mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey)
// Get the policy in the template
policy, err := getResourceIamPolicy(d)
if err != nil {
return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
}
log.Printf("[DEBUG] Updating IAM policy for project %q", project)
err = setProjectIamPolicy(policy, config, project)
if err != nil {
return fmt.Errorf("Error setting project IAM policy: %v", err)
}
return resourceGoogleProjectIamPolicyRead(d, meta)
}
func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG]: Deleting google_project_iam_policy")
config := meta.(*Config)
project := d.Get("project").(string)
mutexKey := getProjectIamPolicyMutexKey(project)
mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey)
// Get the existing IAM policy from the API so we can repurpose the etag and audit config
ep, err := getProjectIamPolicy(project, config)
if err != nil {
return fmt.Errorf("Error retrieving IAM policy from project API: %v", err)
}
ep.Bindings = make([]*cloudresourcemanager.Binding, 0)
if err = setProjectIamPolicy(ep, config, project); err != nil {
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
d.SetId("")
return nil
}
func resourceGoogleProjectIamPolicyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("project", d.Id())
return []*schema.ResourceData{d}, nil
}
func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pid string) error {
// Apply the policy
pbytes, _ := json.Marshal(policy)
log.Printf("[DEBUG] Setting policy %#v for project: %s", string(pbytes), pid)
_, err := config.clientResourceManager.Projects.SetIamPolicy(pid,
&cloudresourcemanager.SetIamPolicyRequest{Policy: policy, UpdateMask: "bindings,etag,auditConfigs"}).Do()
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Error applying IAM policy for project %q. Policy is %#v, error is {{err}}", pid, policy), err)
}
return nil
}
// Get a cloudresourcemanager.Policy from a schema.ResourceData
func getResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) {
ps := d.Get("policy_data").(string)
// The policy string is just a marshaled cloudresourcemanager.Policy.
policy := &cloudresourcemanager.Policy{}
if err := json.Unmarshal([]byte(ps), policy); err != nil {
return nil, fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err)
}
return policy, nil
}
// Retrieve the existing IAM Policy for a Project
func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) {
p, err := config.clientResourceManager.Projects.GetIamPolicy(project,
&cloudresourcemanager.GetIamPolicyRequest{}).Do()
if err != nil {
return nil, fmt.Errorf("Error retrieving IAM policy for project %q: %s", project, err)
}
return p, nil
}
func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
var oldPolicy, newPolicy cloudresourcemanager.Policy
if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil {
log.Printf("[ERROR] Could not unmarshal old policy %s: %v", old, err)
return false
}
if err := json.Unmarshal([]byte(new), &newPolicy); err != nil {
log.Printf("[ERROR] Could not unmarshal new policy %s: %v", new, err)
return false
}
if newPolicy.Etag != oldPolicy.Etag {
return false
}
if newPolicy.Version != oldPolicy.Version {
return false
}
if !compareBindings(oldPolicy.Bindings, newPolicy.Bindings) {
return false
}
if !compareAuditConfigs(oldPolicy.AuditConfigs, newPolicy.AuditConfigs) {
return false
}
return true
}
func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding {
db := make([]cloudresourcemanager.Binding, len(b))
for i, v := range b {
db[i] = *v
sort.Strings(db[i].Members)
}
return db
}
func compareBindings(a, b []*cloudresourcemanager.Binding) bool {
a = mergeBindings(a)
b = mergeBindings(b)
sort.Sort(sortableBindings(a))
sort.Sort(sortableBindings(b))
return reflect.DeepEqual(derefBindings(a), derefBindings(b))
}
func compareAuditConfigs(a, b []*cloudresourcemanager.AuditConfig) bool {
a = mergeAuditConfigs(a)
b = mergeAuditConfigs(b)
sort.Sort(sortableAuditConfigs(a))
sort.Sort(sortableAuditConfigs(b))
if len(a) != len(b) {
return false
}
for i, v := range a {
if len(v.AuditLogConfigs) != len(b[i].AuditLogConfigs) {
return false
}
sort.Sort(sortableAuditLogConfigs(v.AuditLogConfigs))
sort.Sort(sortableAuditLogConfigs(b[i].AuditLogConfigs))
for x, logConfig := range v.AuditLogConfigs {
if b[i].AuditLogConfigs[x].LogType != logConfig.LogType {
return false
}
sort.Strings(logConfig.ExemptedMembers)
sort.Strings(b[i].AuditLogConfigs[x].ExemptedMembers)
if len(logConfig.ExemptedMembers) != len(b[i].AuditLogConfigs[x].ExemptedMembers) {
return false
}
for pos, exemptedMember := range logConfig.ExemptedMembers {
if b[i].AuditLogConfigs[x].ExemptedMembers[pos] != exemptedMember {
return false
}
}
}
}
return true
}
type sortableBindings []*cloudresourcemanager.Binding
func (b sortableBindings) Len() int {
return len(b)
}
func (b sortableBindings) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b sortableBindings) Less(i, j int) bool {
return b[i].Role < b[j].Role
}
type sortableAuditConfigs []*cloudresourcemanager.AuditConfig
func (b sortableAuditConfigs) Len() int {
return len(b)
}
func (b sortableAuditConfigs) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b sortableAuditConfigs) Less(i, j int) bool {
return b[i].Service < b[j].Service
}
type sortableAuditLogConfigs []*cloudresourcemanager.AuditLogConfig
func (b sortableAuditLogConfigs) Len() int {
return len(b)
}
func (b sortableAuditLogConfigs) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b sortableAuditLogConfigs) Less(i, j int) bool {
return b[i].LogType < b[j].LogType
}
func getProjectIamPolicyMutexKey(pid string) string {
return fmt.Sprintf("iam-project-%s", pid)
}