diff --git a/minio/resource_minio_iam_group_policy_attachment.go b/minio/resource_minio_iam_group_policy_attachment.go index da7a9824..0c612855 100644 --- a/minio/resource_minio_iam_group_policy_attachment.go +++ b/minio/resource_minio_iam_group_policy_attachment.go @@ -13,6 +13,8 @@ import ( "github.com/minio/madmin-go" ) +var groupPolicyAttachmentLock = NewMutexKV() + func resourceMinioIAMGroupPolicyAttachment() *schema.Resource { return &schema.Resource{ CreateContext: minioCreateGroupPolicyAttachment, @@ -44,6 +46,9 @@ func minioCreateGroupPolicyAttachment(ctx context.Context, d *schema.ResourceDat var groupName = d.Get("group_name").(string) var policyName = d.Get("policy_name").(string) + groupPolicyAttachmentLock.Lock(groupName) + defer groupPolicyAttachmentLock.Unlock(groupName) + policies, err := minioReadGroupPolicies(ctx, minioAdmin, groupName) if err != nil { return err @@ -59,15 +64,20 @@ func minioCreateGroupPolicyAttachment(ctx context.Context, d *schema.ResourceDat d.SetId(id.PrefixedUniqueId(fmt.Sprintf("%s-", groupName))) - return minioReadGroupPolicyAttachment(ctx, d, meta) + return doMinioReadGroupPolicyAttachment(ctx, d, meta, groupName, policyName) } func minioReadGroupPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - minioAdmin := meta.(*S3MinioClient).S3Admin - var groupName = d.Get("group_name").(string) var policyName = d.Get("policy_name").(string) + groupPolicyAttachmentLock.Lock(groupName) + defer groupPolicyAttachmentLock.Unlock(groupName) + + return doMinioReadGroupPolicyAttachment(ctx, d, meta, groupName, policyName) +} +func doMinioReadGroupPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}, groupName, policyName string) diag.Diagnostics { + minioAdmin := meta.(*S3MinioClient).S3Admin policies, err := minioReadGroupPolicies(ctx, minioAdmin, groupName) if err != nil { return err @@ -91,6 +101,9 @@ func minioDeleteGroupPolicyAttachment(ctx context.Context, d *schema.ResourceDat var groupName = d.Get("group_name").(string) var policyName = d.Get("policy_name").(string) + groupPolicyAttachmentLock.Lock(groupName) + defer groupPolicyAttachmentLock.Unlock(groupName) + policies, err := minioReadGroupPolicies(ctx, minioAdmin, groupName) if err != nil { return err @@ -135,5 +148,8 @@ func minioReadGroupPolicies(ctx context.Context, minioAdmin *madmin.AdminClient, if errGroup != nil { return nil, NewResourceError("failed to load group infos", groupName, errGroup) } + if groupInfo.Policy == "" { + return nil, nil + } return strings.Split(groupInfo.Policy, ","), nil } diff --git a/minio/resource_minio_iam_user_policy_attachment.go b/minio/resource_minio_iam_user_policy_attachment.go index a1e46a54..913a9b14 100644 --- a/minio/resource_minio_iam_user_policy_attachment.go +++ b/minio/resource_minio_iam_user_policy_attachment.go @@ -13,6 +13,8 @@ import ( "github.com/minio/madmin-go" ) +var userPolicyAttachmentLock = NewMutexKV() + func resourceMinioIAMUserPolicyAttachment() *schema.Resource { return &schema.Resource{ CreateContext: minioCreateUserPolicyAttachment, @@ -44,12 +46,16 @@ func minioCreateUserPolicyAttachment(ctx context.Context, d *schema.ResourceData var policyName = d.Get("policy_name").(string) minioAdmin := meta.(*S3MinioClient).S3Admin + userPolicyAttachmentLock.Lock(userName) + defer userPolicyAttachmentLock.Unlock(userName) + policies, err := minioReadUserPolicies(ctx, minioAdmin, userName) if err != nil { return err } if !Contains(policies, policyName) { policies = append(policies, policyName) + log.Printf("[DEBUG] Attaching policy %s to user: %s (%v)", policyName, userName, policies) err := minioAdmin.SetPolicy(ctx, strings.Join(policies, ","), userName, false) if err != nil { return NewResourceError("unable to Set User policy", userName+" "+policyName, err) @@ -58,14 +64,20 @@ func minioCreateUserPolicyAttachment(ctx context.Context, d *schema.ResourceData d.SetId(id.PrefixedUniqueId(fmt.Sprintf("%s-", userName))) - return minioReadUserPolicyAttachment(ctx, d, meta) + return doMinioReadUserPolicyAttachment(ctx, d, meta, userName, policyName) } - func minioReadUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - minioAdmin := meta.(*S3MinioClient).S3Admin var userName = d.Get("user_name").(string) var policyName = d.Get("policy_name").(string) + userPolicyAttachmentLock.Lock(userName) + defer userPolicyAttachmentLock.Unlock(userName) + + return doMinioReadUserPolicyAttachment(ctx, d, meta, userName, policyName) +} +func doMinioReadUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}, userName, policyName string) diag.Diagnostics { + minioAdmin := meta.(*S3MinioClient).S3Admin + policies, errUser := minioReadUserPolicies(ctx, minioAdmin, userName) if errUser != nil { return errUser @@ -90,6 +102,9 @@ func minioDeleteUserPolicyAttachment(ctx context.Context, d *schema.ResourceData var userName = d.Get("user_name").(string) var policyName = d.Get("policy_name").(string) + userPolicyAttachmentLock.Lock(userName) + defer userPolicyAttachmentLock.Unlock(userName) + policies, err := minioReadUserPolicies(ctx, minioAdmin, userName) if err != nil { return err @@ -100,6 +115,7 @@ func minioDeleteUserPolicyAttachment(ctx context.Context, d *schema.ResourceData return nil } + log.Printf("[DEBUG] Detaching policy %s from user: %s (%v)", policyName, userName, newPolicies) errIam := minioAdmin.SetPolicy(ctx, strings.Join(newPolicies, ","), userName, false) if errIam != nil { return NewResourceError("unable to delete user policy", userName, errIam) @@ -144,5 +160,8 @@ func minioReadUserPolicies(ctx context.Context, minioAdmin *madmin.AdminClient, return nil, NewResourceError("failed to load user Infos", userName, errUser) } } + if userInfo.PolicyName == "" { + return nil, nil + } return strings.Split(userInfo.PolicyName, ","), nil } diff --git a/minio/utils.go b/minio/utils.go index f7dca189..604ec311 100644 --- a/minio/utils.go +++ b/minio/utils.go @@ -5,6 +5,8 @@ import ( "encoding/base64" "errors" "hash/crc32" + "log" + "sync" "github.com/aws/aws-sdk-go/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -88,3 +90,48 @@ func HashcodeString(s string) int { // v == MinInt return 0 } + +// MutexKV is a simple key/value store for arbitrary mutexes. It can be used to +// serialize changes across arbitrary collaborators that share knowledge of the +// keys they must serialize on. +// +// The initial use case is to let aws_security_group_rule resources serialize +// their access to individual security groups based on SG ID. +type MutexKV struct { + lock sync.Mutex + store map[string]*sync.Mutex +} + +// Locks the mutex for the given key. Caller is responsible for calling Unlock +// for the same key +func (m *MutexKV) Lock(key string) { + log.Printf("[DEBUG] Locking %q", key) + m.get(key).Lock() + log.Printf("[DEBUG] Locked %q", key) +} + +// Unlock the mutex for the given key. Caller must have called Lock for the same key first +func (m *MutexKV) Unlock(key string) { + log.Printf("[DEBUG] Unlocking %q", key) + m.get(key).Unlock() + log.Printf("[DEBUG] Unlocked %q", key) +} + +// Returns a mutex for the given key, no guarantee of its lock status +func (m *MutexKV) get(key string) *sync.Mutex { + m.lock.Lock() + defer m.lock.Unlock() + mutex, ok := m.store[key] + if !ok { + mutex = &sync.Mutex{} + m.store[key] = mutex + } + return mutex +} + +// Returns a properly initialized MutexKV +func NewMutexKV() *MutexKV { + return &MutexKV{ + store: make(map[string]*sync.Mutex), + } +}