Refactor project iam binding and member resources to improve reusability (#744)

* Refactor project iam binding and member resources to improve reusability
* Use default mask when updating project iam policy
* Add a doc comment for the ResourceIamUpdater interface
This commit is contained in:
Vincent Roseberry 2017-11-20 17:01:39 -08:00 committed by GitHub
parent 75a633cf28
commit 1859c558fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 511 additions and 446 deletions

111
google/iam.go Normal file
View File

@ -0,0 +1,111 @@
package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
"log"
"time"
)
// The ResourceIamUpdater interface is implemented for each GCP resource supporting IAM policy.
//
// Implementations should keep track of the resource identifier.
type ResourceIamUpdater interface {
// Fetch the existing IAM policy attached to a resource.
GetResourceIamPolicy() (*cloudresourcemanager.Policy, error)
// Replaces the existing IAM Policy attached to a resource.
SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error
// A mutex guards against concurrent to call to the SetResourceIamPolicy method.
// The mutex key should be made of the resource type and resource id.
// For example: `iam-project-{id}`.
GetMutexKey() string
// Returns the unique resource identifier.
GetResourceId() string
// Textual description of this resource to be used in error message.
// The description should include the unique resource identifier.
DescribeResource() string
}
type newResourceIamUpdaterFunc func(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error)
type iamPolicyModifyFunc func(p *cloudresourcemanager.Policy) error
func iamPolicyReadModifyWrite(updater ResourceIamUpdater, modify iamPolicyModifyFunc) error {
mutexKey := updater.GetMutexKey()
mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey)
for {
backoff := time.Second
log.Printf("[DEBUG]: Retrieving policy for %s\n", updater.DescribeResource())
p, err := updater.GetResourceIamPolicy()
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for %s: %+v\n", updater.DescribeResource(), p)
err = modify(p)
if err != nil {
return err
}
log.Printf("[DEBUG]: Setting policy for %s to %+v\n", updater.DescribeResource(), p)
err = updater.SetResourceIamPolicy(p)
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 %s: too many concurrent policy changes.\n", updater.DescribeResource())
}
continue
}
return fmt.Errorf("Error applying IAM policy for %s: %v", updater.DescribeResource(), err)
}
log.Printf("[DEBUG]: Set policy for %s", updater.DescribeResource())
return nil
}
// Merge multiple Bindings such that Bindings with the same Role result in
// a single Binding with combined Members
func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
bm := rolesToMembersMap(bindings)
rb := make([]*cloudresourcemanager.Binding, 0)
for role, members := range bm {
var b cloudresourcemanager.Binding
b.Role = role
b.Members = make([]string, 0)
for m := range members {
b.Members = append(b.Members, m)
}
rb = append(rb, &b)
}
return rb
}
// Map a role to a map of members, allowing easy merging of multiple bindings.
func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[string]bool {
bm := make(map[string]map[string]bool)
// Get each binding
for _, b := range bindings {
// Initialize members map
if _, ok := bm[b.Role]; !ok {
bm[b.Role] = make(map[string]bool)
}
// Get each member (user/principal) for the binding
for _, m := range b.Members {
// Add the member
bm[b.Role][m] = true
}
}
return bm
}

67
google/iam_project.go Normal file
View File

@ -0,0 +1,67 @@
package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)
var IamProjectSchema = map[string]*schema.Schema{
"project": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
}
type ProjectIamUpdater struct {
resourceId string
Config *Config
}
func NewProjectIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) {
pid, err := getProject(d, config)
if err != nil {
return nil, err
}
return &ProjectIamUpdater{
resourceId: pid,
Config: config,
}, nil
}
func (u *ProjectIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
p, err := u.Config.clientResourceManager.Projects.GetIamPolicy(u.resourceId,
&cloudresourcemanager.GetIamPolicyRequest{}).Do()
if err != nil {
return nil, fmt.Errorf("Error retrieving IAM policy for %s: %s", u.DescribeResource(), err)
}
return p, nil
}
func (u *ProjectIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
_, err := u.Config.clientResourceManager.Projects.SetIamPolicy(u.resourceId, &cloudresourcemanager.SetIamPolicyRequest{
Policy: policy,
}).Do()
if err != nil {
return fmt.Errorf("Error setting IAM policy for %s: %s", u.DescribeResource(), err)
}
return nil
}
func (u *ProjectIamUpdater) GetResourceId() string {
return u.resourceId
}
func (u *ProjectIamUpdater) GetMutexKey() string {
return fmt.Sprintf("iam-project-%s", u.resourceId)
}
func (u *ProjectIamUpdater) DescribeResource() string {
return fmt.Sprintf("project %q", u.resourceId)
}

View File

@ -132,8 +132,8 @@ func Provider() terraform.ResourceProvider {
"google_organization_policy": resourceGoogleOrganizationPolicy(),
"google_project": resourceGoogleProject(),
"google_project_iam_policy": resourceGoogleProjectIamPolicy(),
"google_project_iam_binding": resourceGoogleProjectIamBinding(),
"google_project_iam_member": resourceGoogleProjectIamMember(),
"google_project_iam_binding": ResourceIamBinding(IamProjectSchema, NewProjectIamUpdater),
"google_project_iam_member": ResourceIamMember(IamProjectSchema, NewProjectIamUpdater),
"google_project_service": resourceGoogleProjectService(),
"google_project_iam_custom_role": resourceGoogleProjectIamCustomRole(),
"google_project_services": resourceGoogleProjectServices(),

View File

@ -1,183 +0,0 @@
package google
import (
"fmt"
"log"
"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": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"members": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"etag": {
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))
err = projectIamPolicyReadModifyWrite(d, config, pid, func(ep *cloudresourcemanager.Policy) error {
// Merge the bindings together
ep.Bindings = mergeBindings(append(ep.Bindings, p))
return nil
})
if err != nil {
return err
}
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 {
log.Printf("[DEBUG]: Binding for role %q not found in policy for %q, removing from state file.\n", eBinding.Role, pid)
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))
err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
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)
}
return nil
})
if err != nil {
return err
}
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))
err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
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", pid, binding.Role)
return nil
}
p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...)
return nil
})
if err != nil {
return err
}
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()
return &cloudresourcemanager.Binding{
Members: convertStringArr(members),
Role: d.Get("role").(string),
}
}
func projectIamBindingMutexKey(pid, role string) string {
return fmt.Sprintf("google-project-iam-binding-%s-%s", pid, role)
}

View File

@ -1,186 +0,0 @@
package google
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)
func resourceGoogleProjectIamMember() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectIamMemberCreate,
Read: resourceGoogleProjectIamMemberRead,
Delete: resourceGoogleProjectIamMemberDelete,
Schema: map[string]*schema.Schema{
"project": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"member": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceGoogleProjectIamMemberCreate(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_member")
p := getResourceIamMember(d)
mutexKV.Lock(projectIamMemberMutexKey(pid, p.Role, p.Members[0]))
defer mutexKV.Unlock(projectIamMemberMutexKey(pid, p.Role, p.Members[0]))
err = projectIamPolicyReadModifyWrite(d, config, pid, func(ep *cloudresourcemanager.Policy) error {
// find the binding
var binding *cloudresourcemanager.Binding
for _, b := range ep.Bindings {
if b.Role != p.Role {
continue
}
binding = b
break
}
if binding == nil {
binding = &cloudresourcemanager.Binding{
Role: p.Role,
Members: p.Members,
}
}
// Merge the bindings together
ep.Bindings = mergeBindings(append(ep.Bindings, p))
return nil
})
if err != nil {
return err
}
d.SetId(pid + "/" + p.Role + "/" + p.Members[0])
return resourceGoogleProjectIamMemberRead(d, meta)
}
func resourceGoogleProjectIamMemberRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}
eMember := getResourceIamMember(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 != eMember.Role {
continue
}
binding = b
break
}
if binding == nil {
log.Printf("[DEBUG]: Binding for role %q does not exist in policy of project %q, removing member %q from state.", eMember.Role, pid, eMember.Members[0])
d.SetId("")
return nil
}
var member string
for _, m := range binding.Members {
if m == eMember.Members[0] {
member = m
}
}
if member == "" {
log.Printf("[DEBUG]: Member %q for binding for role %q does not exist in policy of project %q, removing from state.", eMember.Members[0], eMember.Role, pid)
d.SetId("")
return nil
}
d.Set("etag", p.Etag)
d.Set("member", member)
d.Set("role", binding.Role)
return nil
}
func resourceGoogleProjectIamMemberDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}
member := getResourceIamMember(d)
mutexKV.Lock(projectIamMemberMutexKey(pid, member.Role, member.Members[0]))
defer mutexKV.Unlock(projectIamMemberMutexKey(pid, member.Role, member.Members[0]))
err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
bindingToRemove := -1
for pos, b := range p.Bindings {
if b.Role != member.Role {
continue
}
bindingToRemove = pos
break
}
if bindingToRemove < 0 {
log.Printf("[DEBUG]: Binding for role %q does not exist in policy of project %q, so member %q can't be on it.", member.Role, pid, member.Members[0])
return nil
}
binding := p.Bindings[bindingToRemove]
memberToRemove := -1
for pos, m := range binding.Members {
if m != member.Members[0] {
continue
}
memberToRemove = pos
break
}
if memberToRemove < 0 {
log.Printf("[DEBUG]: Member %q for binding for role %q does not exist in policy of project %q.", member.Members[0], member.Role, pid)
return nil
}
binding.Members = append(binding.Members[:memberToRemove], binding.Members[memberToRemove+1:]...)
p.Bindings[bindingToRemove] = binding
return nil
})
if err != nil {
return err
}
return resourceGoogleProjectIamMemberRead(d, meta)
}
// Get a cloudresourcemanager.Binding from a schema.ResourceData
func getResourceIamMember(d *schema.ResourceData) *cloudresourcemanager.Binding {
return &cloudresourcemanager.Binding{
Members: []string{d.Get("member").(string)},
Role: d.Get("role").(string),
}
}
func projectIamMemberMutexKey(pid, role, member string) string {
return fmt.Sprintf("google-project-iam-member-%s-%s-%s", pid, role, member)
}

View File

@ -45,6 +45,8 @@ func TestAccGoogleProjectIamMember_multiple(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
skipIfEnvNotSet(t, "GOOGLE_ORG")
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -86,6 +88,8 @@ func TestAccGoogleProjectIamMember_remove(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
skipIfEnvNotSet(t, "GOOGLE_ORG")
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },

View File

@ -5,7 +5,6 @@ import (
"fmt"
"log"
"sort"
"time"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
@ -342,43 +341,6 @@ func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager
return bindings
}
// Map a role to a map of members, allowing easy merging of multiple bindings.
func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[string]bool {
bm := make(map[string]map[string]bool)
// Get each binding
for _, b := range bindings {
// Initialize members map
if _, ok := bm[b.Role]; !ok {
bm[b.Role] = make(map[string]bool)
}
// Get each member (user/principal) for the binding
for _, m := range b.Members {
// Add the member
bm[b.Role][m] = true
}
}
return bm
}
// Merge multiple Bindings such that Bindings with the same Role result in
// a single Binding with combined Members
func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
bm := rolesToMembersMap(bindings)
rb := make([]*cloudresourcemanager.Binding, 0)
for role, members := range bm {
var b cloudresourcemanager.Binding
b.Role = role
b.Members = make([]string, 0)
for m, _ := range members {
b.Members = append(b.Members, m)
}
rb = append(rb, &b)
}
return rb
}
func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
var oldPolicy, newPolicy cloudresourcemanager.Policy
if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil {
@ -433,40 +395,3 @@ func (b sortableBindings) Swap(i, j int) {
func (b sortableBindings) Less(i, j int) bool {
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
}

View File

@ -0,0 +1,171 @@
package google
import (
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
"log"
)
var iamBindingSchema = map[string]*schema.Schema{
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"members": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
}
func ResourceIamBinding(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc) *schema.Resource {
return &schema.Resource{
Create: resourceIamBindingCreate(newUpdaterFunc),
Read: resourceIamBindingRead(newUpdaterFunc),
Update: resourceIamBindingUpdate(newUpdaterFunc),
Delete: resourceIamBindingDelete(newUpdaterFunc),
Schema: mergeSchemas(iamBindingSchema, parentSpecificSchema),
}
}
func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.CreateFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
if err != nil {
return err
}
p := getResourceIamBinding(d)
err = iamPolicyReadModifyWrite(updater, func(ep *cloudresourcemanager.Policy) error {
// Creating a binding does not remove existing members if they are not in the provided members list.
// This prevents removing existing permission without the user's knowledge.
// Instead, a diff is shown in that case after creation. Subsequent calls to update will remove any
// existing members not present in the provided list.
ep.Bindings = mergeBindings(append(ep.Bindings, p))
return nil
})
if err != nil {
return err
}
d.SetId(updater.GetResourceId() + "/" + p.Role)
return resourceIamBindingRead(newUpdaterFunc)(d, meta)
}
}
func resourceIamBindingRead(newUpdaterFunc newResourceIamUpdaterFunc) schema.ReadFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
if err != nil {
return err
}
eBinding := getResourceIamBinding(d)
p, err := updater.GetResourceIamPolicy()
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for %s: %+v\n", updater.DescribeResource(), p)
var binding *cloudresourcemanager.Binding
for _, b := range p.Bindings {
if b.Role != eBinding.Role {
continue
}
binding = b
break
}
if binding == nil {
log.Printf("[DEBUG]: Binding for role %q not found in policy for %s, removing from state file.\n", eBinding.Role, updater.DescribeResource())
d.SetId("")
return nil
}
d.Set("etag", p.Etag)
d.Set("members", binding.Members)
d.Set("role", binding.Role)
return nil
}
}
func resourceIamBindingUpdate(newUpdaterFunc newResourceIamUpdaterFunc) schema.UpdateFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := NewProjectIamUpdater(d, config)
if err != nil {
return err
}
binding := getResourceIamBinding(d)
err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error {
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)
}
return nil
})
if err != nil {
return err
}
return resourceIamBindingRead(newUpdaterFunc)(d, meta)
}
}
func resourceIamBindingDelete(newUpdaterFunc newResourceIamUpdaterFunc) schema.DeleteFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := NewProjectIamUpdater(d, config)
if err != nil {
return err
}
binding := getResourceIamBinding(d)
err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error {
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 %s did not include a binding for role %q", updater.DescribeResource(), binding.Role)
return nil
}
p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...)
return nil
})
if err != nil {
return err
}
return resourceIamBindingRead(newUpdaterFunc)(d, meta)
}
}
func getResourceIamBinding(d *schema.ResourceData) *cloudresourcemanager.Binding {
members := d.Get("members").(*schema.Set).List()
return &cloudresourcemanager.Binding{
Members: convertStringArr(members),
Role: d.Get("role").(string),
}
}

View File

@ -0,0 +1,156 @@
package google
import (
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
"log"
)
var IamMemberBaseSchema = map[string]*schema.Schema{
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"member": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
}
func ResourceIamMember(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc) *schema.Resource {
return &schema.Resource{
Create: resourceIamMemberCreate(newUpdaterFunc),
Read: resourceIamMemberRead(newUpdaterFunc),
Delete: resourceIamMemberDelete(newUpdaterFunc),
Schema: mergeSchemas(IamMemberBaseSchema, parentSpecificSchema),
}
}
func getResourceIamMember(d *schema.ResourceData) *cloudresourcemanager.Binding {
return &cloudresourcemanager.Binding{
Members: []string{d.Get("member").(string)},
Role: d.Get("role").(string),
}
}
func resourceIamMemberCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.CreateFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
if err != nil {
return err
}
p := getResourceIamMember(d)
err = iamPolicyReadModifyWrite(updater, func(ep *cloudresourcemanager.Policy) error {
// Merge the bindings together
ep.Bindings = mergeBindings(append(ep.Bindings, p))
return nil
})
if err != nil {
return err
}
d.SetId(updater.GetResourceId() + "/" + p.Role + "/" + p.Members[0])
return resourceIamMemberRead(newUpdaterFunc)(d, meta)
}
}
func resourceIamMemberRead(newUpdaterFunc newResourceIamUpdaterFunc) schema.ReadFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
if err != nil {
return err
}
eMember := getResourceIamMember(d)
p, err := updater.GetResourceIamPolicy()
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for %s: %+v\n", updater.DescribeResource(), p)
var binding *cloudresourcemanager.Binding
for _, b := range p.Bindings {
if b.Role != eMember.Role {
continue
}
binding = b
break
}
if binding == nil {
log.Printf("[DEBUG]: Binding for role %q does not exist in policy of %s, removing member %q from state.", eMember.Role, updater.DescribeResource(), eMember.Members[0])
d.SetId("")
return nil
}
var member string
for _, m := range binding.Members {
if m == eMember.Members[0] {
member = m
}
}
if member == "" {
log.Printf("[DEBUG]: Member %q for binding for role %q does not exist in policy of %s, removing from state.", eMember.Members[0], eMember.Role, updater.DescribeResource())
d.SetId("")
return nil
}
d.Set("etag", p.Etag)
d.Set("member", member)
d.Set("role", binding.Role)
return nil
}
}
func resourceIamMemberDelete(newUpdaterFunc newResourceIamUpdaterFunc) schema.DeleteFunc {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
if err != nil {
return err
}
member := getResourceIamMember(d)
err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error {
bindingToRemove := -1
for pos, b := range p.Bindings {
if b.Role != member.Role {
continue
}
bindingToRemove = pos
break
}
if bindingToRemove < 0 {
log.Printf("[DEBUG]: Binding for role %q does not exist in policy of project %q, so member %q can't be on it.", member.Role, updater.GetResourceId(), member.Members[0])
return nil
}
binding := p.Bindings[bindingToRemove]
memberToRemove := -1
for pos, m := range binding.Members {
if m != member.Members[0] {
continue
}
memberToRemove = pos
break
}
if memberToRemove < 0 {
log.Printf("[DEBUG]: Member %q for binding for role %q does not exist in policy of project %q.", member.Members[0], member.Role, updater.GetResourceId())
return nil
}
binding.Members = append(binding.Members[:memberToRemove], binding.Members[memberToRemove+1:]...)
p.Bindings[bindingToRemove] = binding
return nil
})
if err != nil {
return err
}
return resourceIamMemberRead(newUpdaterFunc)(d, meta)
}
}