terraform-provider-google/google/resource_google_project_iam_binding.go
Paddy 9c1c0bbc52 Just remove deleted bindings not present in the API.
We can just set the ID of bindings that are scheduled to be deleted but
don't exist in the API, we don't need an entire separate read request.
2017-07-25 11:12:15 -07:00

250 lines
6.8 KiB
Go

package google
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)
func resourceGoogleProjectIamBinding() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectIamBindingCreate,
Read: resourceGoogleProjectIamBindingRead,
Update: resourceGoogleProjectIamBindingUpdate,
Delete: resourceGoogleProjectIamBindingDelete,
Schema: map[string]*schema.Schema{
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"role": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"members": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"etag": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceGoogleProjectIamBindingCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}
// Get the binding in the template
log.Println("[DEBUG]: Reading google_project_iam_binding")
p := getResourceIamBinding(d)
mutexKV.Lock(projectIamBindingMutexKey(pid, p.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, p.Role))
for {
backoff := time.Second
// Get the existing bindings
log.Println("[DEBUG]: Retrieving policy for project", pid)
ep, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for project %q: %+v\n", pid, ep)
// Merge the bindings together
ep.Bindings = mergeBindings(append(ep.Bindings, p))
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, ep)
err = setProjectIamPolicy(ep, config, pid)
if err == nil {
// update was successful, yay
break
} else if isConflitError(err) {
log.Printf("[DEBUG]: Concurrent policy changes, restarting read-modify-write after %s\n", backoff)
time.Sleep(backoff)
backoff = backoff * 2
if backoff > 30*time.Second {
return fmt.Errorf("Error applying IAM policy to project %q: too many concurrent policy changes.\n", pid)
}
continue
}
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
log.Printf("[DEBUG]: Set policy for project %q", pid)
d.SetId(pid + ":" + p.Role)
return resourceGoogleProjectIamBindingRead(d, meta)
}
func resourceGoogleProjectIamBindingRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}
eBinding := getResourceIamBinding(d)
log.Println("[DEBUG]: Retrieving policy for project", pid)
p, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for project %q: %+v\n", pid, p)
var binding *cloudresourcemanager.Binding
for _, b := range p.Bindings {
if b.Role != eBinding.Role {
continue
}
binding = b
break
}
if binding == nil {
d.SetId("")
return nil
}
d.Set("etag", p.Etag)
d.Set("members", binding.Members)
d.Set("role", binding.Role)
return nil
}
func resourceGoogleProjectIamBindingUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}
binding := getResourceIamBinding(d)
mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role))
for {
backoff := time.Second
log.Println("[DEBUG]: Retrieving policy for project", pid)
p, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for project %q: %+v\n", pid, p)
var found bool
for pos, b := range p.Bindings {
if b.Role != binding.Role {
continue
}
found = true
p.Bindings[pos] = binding
break
}
if !found {
p.Bindings = append(p.Bindings, binding)
}
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, p)
err = setProjectIamPolicy(p, config, pid)
if err != nil && isConflictError(err) {
log.Printf("[DEBUG]: Concurrent policy changes, restarting read-modify-write after %s\n", backoff)
time.Sleep(backoff)
backoff = backoff * 2
if backoff > 30*time.Second {
return fmt.Errorf("Error applying IAM policy to project %q: too many concurrent policy changes.\n", pid)
}
continue
} else if err != nil {
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
break
}
log.Printf("[DEBUG]: Set policy for project %q\n", pid)
return resourceGoogleProjectIamBindingRead(d, meta)
}
func resourceGoogleProjectIamBindingDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}
binding := getResourceIamBinding(d)
mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role))
for {
backoff := time.Second
log.Println("[DEBUG]: Retrieving policy for project", pid)
p, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for project %q: %+v\n", pid, p)
toRemove := -1
for pos, b := range p.Bindings {
if b.Role != binding.Role {
continue
}
toRemove = pos
break
}
if toRemove < 0 {
log.Printf("[DEBUG]: Policy bindings for project %q did not include a binding for role %q, no need to delete", pid, binding.Role)
d.SetId("")
return nil
}
p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...)
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, p)
err = setProjectIamPolicy(p, config, pid)
if err != nil && isConflictError(err) {
log.Printf("[DEBUG]: Concurrent policy changes, restarting read-modify-write after %s\n", backoff)
time.Sleep(backoff)
backoff = backoff * 2
if backoff > 30*time.Second {
return fmt.Errorf("Error applying IAM policy to project %q: too many concurrent policy changes.\n", pid)
}
continue
} else if err != nil {
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
break
}
log.Printf("[DEBUG]: Set policy for project %q\n", pid)
return resourceGoogleProjectIamBindingRead(d, meta)
}
// Get a cloudresourcemanager.Binding from a schema.ResourceData
func getResourceIamBinding(d *schema.ResourceData) *cloudresourcemanager.Binding {
members := d.Get("members").(*schema.Set).List()
m := make([]string, 0, len(members))
for _, member := range members {
m = append(m, member.(string))
}
return &cloudresourcemanager.Binding{
Members: m,
Role: d.Get("role").(string),
}
}
func projectIamBindingMutexKey(pid, role string) string {
return fmt.Sprintf("google-project-iam-binding-%s-%s", pid, role)
}