Create an iam policy read/modify/write helper.

We were repeating that logic a lot, so this helper just reads a policy,
calls the passed modify function on the policy, then writes the policy
back and takes care of the optimistic concurrency logic for the caller.
So now all the caller has to do is the unique part, which is the modify
function.
This commit is contained in:
Paddy 2017-07-25 11:43:49 -07:00
parent 9c1c0bbc52
commit 729e9fc501
2 changed files with 53 additions and 77 deletions

View File

@ -3,7 +3,6 @@ package google
import ( import (
"fmt" "fmt"
"log" "log"
"time"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1" "google.golang.org/api/cloudresourcemanager/v1"
@ -55,35 +54,14 @@ func resourceGoogleProjectIamBindingCreate(d *schema.ResourceData, meta interfac
mutexKV.Lock(projectIamBindingMutexKey(pid, p.Role)) mutexKV.Lock(projectIamBindingMutexKey(pid, p.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, p.Role)) defer mutexKV.Unlock(projectIamBindingMutexKey(pid, p.Role))
for { err = projectIamPolicyReadModifyWrite(d, config, pid, func(ep *cloudresourcemanager.Policy) error {
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 // Merge the bindings together
ep.Bindings = mergeBindings(append(ep.Bindings, p)) ep.Bindings = mergeBindings(append(ep.Bindings, p))
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, ep) return nil
err = setProjectIamPolicy(ep, config, pid) })
if err == nil { if err != nil {
// update was successful, yay return err
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) d.SetId(pid + ":" + p.Role)
return resourceGoogleProjectIamBindingRead(d, meta) return resourceGoogleProjectIamBindingRead(d, meta)
} }
@ -133,15 +111,7 @@ func resourceGoogleProjectIamBindingUpdate(d *schema.ResourceData, meta interfac
mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role)) mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role)) defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role))
for { err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
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 var found bool
for pos, b := range p.Bindings { for pos, b := range p.Bindings {
if b.Role != binding.Role { if b.Role != binding.Role {
@ -154,23 +124,11 @@ func resourceGoogleProjectIamBindingUpdate(d *schema.ResourceData, meta interfac
if !found { if !found {
p.Bindings = append(p.Bindings, binding) p.Bindings = append(p.Bindings, binding)
} }
return nil
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, p) })
err = setProjectIamPolicy(p, config, pid) if err != nil {
if err != nil && isConflictError(err) { return 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) return resourceGoogleProjectIamBindingRead(d, meta)
} }
@ -186,15 +144,7 @@ func resourceGoogleProjectIamBindingDelete(d *schema.ResourceData, meta interfac
mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role)) mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role)) defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role))
for { err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
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 toRemove := -1
for pos, b := range p.Bindings { for pos, b := range p.Bindings {
if b.Role != binding.Role { if b.Role != binding.Role {
@ -210,23 +160,11 @@ func resourceGoogleProjectIamBindingDelete(d *schema.ResourceData, meta interfac
} }
p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...) p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...)
return nil
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, p) })
err = setProjectIamPolicy(p, config, pid) if err != nil {
if err != nil && isConflictError(err) { return 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) return resourceGoogleProjectIamBindingRead(d, meta)
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"sort" "sort"
"time"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -418,3 +419,40 @@ func (b sortableBindings) Swap(i, j int) {
func (b sortableBindings) Less(i, j int) bool { func (b sortableBindings) Less(i, j int) bool {
return b[i].Role < b[j].Role return b[i].Role < b[j].Role
} }
type iamPolicyModifyFunc func(p *cloudresourcemanager.Policy) error
func projectIamPolicyReadModifyWrite(d *schema.ResourceData, config *Config, pid string, modify iamPolicyModifyFunc) error {
for {
backoff := time.Second
log.Printf("[DEBUG]: Retrieving policy for project %q\n", pid)
p, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for project %q: %+v\n", pid, p)
err = modify(p)
if err != nil {
return err
}
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, p)
err = setProjectIamPolicy(p, config, pid)
if err == nil {
break
}
if 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
}
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
log.Printf("[DEBUG]: Set policy for project %q\n", pid)
return nil
}