-
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 google_project_iam_binding and google_project_iam_member resources. #171
Changes from 7 commits
8e70487
eed8487
11abe24
65a4a36
a84b22d
f9236ab
8a880fd
d3f901b
9c1c0bb
729e9fc
5282ad7
f9eeb36
52d552d
b0e3790
91d227c
655435f
fa2d54f
199ff5d
adc206a
f94c387
7854535
f88e042
ac5df40
d3426d5
6f98217
4b9432d
c89429c
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,246 @@ | ||
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{ | ||
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. Since we're doing a read-modify-write, do we actually need to store the etag? 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. The code doesn't use it for anything, but I couldn't think of a good reason not to expose it. |
||
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 && 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 | ||
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. Totally up to you, but I think this structure would be a bit more clear:
|
||
} | ||
log.Printf("[DEBUG]: Set policy for project %q", pid) | ||
d.SetId(pid + ":" + p.Role) | ||
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. A lot of the resources we've been adding import to lately have settled on "/" as a separator for ids. Maybe do that for consistency? |
||
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 { | ||
return resourceGoogleProjectIamBindingRead(d, meta) | ||
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. Is there a reason we need to do another read here, or can we just set the id to "" and be done? |
||
} | ||
|
||
p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...) | ||
|
||
log.Printf("[DEBUG]: Setting policy for project %q to %+v\n", pid, p) | ||
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. Setting the policy seems to be done exactly the same in create, update, and delete- any way we could have a separate function that gets called by all 3? |
||
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) | ||
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. What's the purpose of the final read? 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. Updates the state file with the state according to the API. In this case, confirms the API no longer has the binding in it, and removes it. If for whatever reason the delete function silently failed and didn't actually delete the binding, Terraform wouldn't lose it. |
||
} | ||
|
||
// 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)) | ||
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 you want you can do |
||
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) | ||
} |
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.
super minor nit: can remove the duplicate type declarations here, and the rest of the schema struct: