mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-03 01:01:06 +00:00
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:
parent
75a633cf28
commit
1859c558fe
111
google/iam.go
Normal file
111
google/iam.go
Normal 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
67
google/iam_project.go
Normal 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)
|
||||||
|
}
|
@ -132,8 +132,8 @@ func Provider() terraform.ResourceProvider {
|
|||||||
"google_organization_policy": resourceGoogleOrganizationPolicy(),
|
"google_organization_policy": resourceGoogleOrganizationPolicy(),
|
||||||
"google_project": resourceGoogleProject(),
|
"google_project": resourceGoogleProject(),
|
||||||
"google_project_iam_policy": resourceGoogleProjectIamPolicy(),
|
"google_project_iam_policy": resourceGoogleProjectIamPolicy(),
|
||||||
"google_project_iam_binding": resourceGoogleProjectIamBinding(),
|
"google_project_iam_binding": ResourceIamBinding(IamProjectSchema, NewProjectIamUpdater),
|
||||||
"google_project_iam_member": resourceGoogleProjectIamMember(),
|
"google_project_iam_member": ResourceIamMember(IamProjectSchema, NewProjectIamUpdater),
|
||||||
"google_project_service": resourceGoogleProjectService(),
|
"google_project_service": resourceGoogleProjectService(),
|
||||||
"google_project_iam_custom_role": resourceGoogleProjectIamCustomRole(),
|
"google_project_iam_custom_role": resourceGoogleProjectIamCustomRole(),
|
||||||
"google_project_services": resourceGoogleProjectServices(),
|
"google_project_services": resourceGoogleProjectServices(),
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -45,6 +45,8 @@ func TestAccGoogleProjectIamMember_multiple(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
org := getTestOrgFromEnv(t)
|
org := getTestOrgFromEnv(t)
|
||||||
|
skipIfEnvNotSet(t, "GOOGLE_ORG")
|
||||||
|
|
||||||
pid := "terraform-" + acctest.RandString(10)
|
pid := "terraform-" + acctest.RandString(10)
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
@ -86,6 +88,8 @@ func TestAccGoogleProjectIamMember_remove(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
org := getTestOrgFromEnv(t)
|
org := getTestOrgFromEnv(t)
|
||||||
|
skipIfEnvNotSet(t, "GOOGLE_ORG")
|
||||||
|
|
||||||
pid := "terraform-" + acctest.RandString(10)
|
pid := "terraform-" + acctest.RandString(10)
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -5,7 +5,6 @@ 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"
|
||||||
@ -342,43 +341,6 @@ func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager
|
|||||||
return bindings
|
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 {
|
func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
|
||||||
var oldPolicy, newPolicy cloudresourcemanager.Policy
|
var oldPolicy, newPolicy cloudresourcemanager.Policy
|
||||||
if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil {
|
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 {
|
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
|
|
||||||
}
|
|
||||||
|
171
google/resource_iam_binding.go
Normal file
171
google/resource_iam_binding.go
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
156
google/resource_iam_member.go
Normal file
156
google/resource_iam_member.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user