diff --git a/google/resource_storage_default_object_acl.go b/google/resource_storage_default_object_acl.go index a88d0823..1bb76b49 100644 --- a/google/resource_storage_default_object_acl.go +++ b/google/resource_storage_default_object_acl.go @@ -2,61 +2,69 @@ package google import ( "fmt" - "log" - "github.com/hashicorp/terraform/helper/schema" "google.golang.org/api/storage/v1" ) func resourceStorageDefaultObjectAcl() *schema.Resource { return &schema.Resource{ - Create: resourceStorageDefaultObjectAclCreate, - Read: resourceStorageDefaultObjectAclRead, - Update: resourceStorageDefaultObjectAclUpdate, - Delete: resourceStorageDefaultObjectAclDelete, - CustomizeDiff: resourceStorageRoleEntityCustomizeDiff, + Create: resourceStorageDefaultObjectAclCreateUpdate, + Read: resourceStorageDefaultObjectAclRead, + Update: resourceStorageDefaultObjectAclCreateUpdate, + Delete: resourceStorageDefaultObjectAclDelete, Schema: map[string]*schema.Schema{ - "bucket": &schema.Schema{ + "bucket": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "role_entity": &schema.Schema{ - Type: schema.TypeList, + "role_entity": { + Type: schema.TypeSet, Optional: true, Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateRoleEntityPair, + }, }, }, } } -func resourceStorageDefaultObjectAclCreate(d *schema.ResourceData, meta interface{}) error { +func resourceStorageDefaultObjectAclCreateUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) bucket := d.Get("bucket").(string) - roleEntity := d.Get("role_entity").([]interface{}) - - for _, v := range roleEntity { - pair, err := getRoleEntityPair(v.(string)) - - ObjectAccessControl := &storage.ObjectAccessControl{ + defaultObjectAcl := []*storage.ObjectAccessControl{} + for _, v := range d.Get("role_entity").(*schema.Set).List() { + pair := getValidatedRoleEntityPair(v.(string)) + defaultObjectAcl = append(defaultObjectAcl, &storage.ObjectAccessControl{ Role: pair.Role, Entity: pair.Entity, - } + }) + } - log.Printf("[DEBUG]: setting role = %s, entity = %s on bucket %s", pair.Role, pair.Entity, bucket) - - _, err = config.clientStorage.DefaultObjectAccessControls.Insert(bucket, ObjectAccessControl).Do() + res, err := config.clientStorage.Buckets.Get(bucket).Do() + if err != nil { + return fmt.Errorf("Error reading bucket %s: %v", bucket, err) + } + // Even with ForceSendFields the empty array wasn't working. Luckily, this is the same thing + if len(defaultObjectAcl) == 0 { + _, err = config.clientStorage.Buckets.Update(bucket, res).IfMetagenerationMatch(res.Metageneration).PredefinedDefaultObjectAcl("private").Do() if err != nil { - return fmt.Errorf("Error setting Default Object ACL for %s on bucket %s: %v", pair.Entity, bucket, err) + return fmt.Errorf("Error updating default object acl to empty for bucket %s: %v", bucket, err) + } + } else { + res.DefaultObjectAcl = defaultObjectAcl + _, err = config.clientStorage.Buckets.Update(bucket, res).IfMetagenerationMatch(res.Metageneration).Do() + if err != nil { + return fmt.Errorf("Error updating default object acl for bucket %s: %v", bucket, err) } } - d.SetId(bucket) + return resourceStorageDefaultObjectAclRead(d, meta) } @@ -64,123 +72,39 @@ func resourceStorageDefaultObjectAclRead(d *schema.ResourceData, meta interface{ config := meta.(*Config) bucket := d.Get("bucket").(string) - - roleEntities := make([]interface{}, 0) - reLocal := d.Get("role_entity").([]interface{}) - reLocalMap := make(map[string]string) - for _, v := range reLocal { - res, err := getRoleEntityPair(v.(string)) - - if err != nil { - return fmt.Errorf( - "Old state has malformed Role/Entity pair: %v", err) - } - - reLocalMap[res.Entity] = res.Role - } - - res, err := config.clientStorage.DefaultObjectAccessControls.List(bucket).Do() - + res, err := config.clientStorage.Buckets.Get(bucket).Projection("full").Do() if err != nil { - return handleNotFoundError(err, d, fmt.Sprintf("Storage Default Object ACL for bucket %q", d.Get("bucket").(string))) + return handleNotFoundError(err, d, fmt.Sprintf("Default Storage Object ACL for Bucket %q", d.Get("bucket").(string))) } - for _, v := range res.Items { - role := v.Role - entity := v.Entity - // We only store updates to the locally defined access controls - if _, in := reLocalMap[entity]; in { - roleEntities = append(roleEntities, fmt.Sprintf("%s:%s", role, entity)) - log.Printf("[DEBUG]: saving re %s-%s", v.Role, v.Entity) - } + var roleEntities []string + for _, roleEntity := range res.DefaultObjectAcl { + role := roleEntity.Role + entity := roleEntity.Entity + roleEntities = append(roleEntities, fmt.Sprintf("%s:%s", role, entity)) } - d.Set("role_entity", roleEntities) + err = d.Set("role_entity", roleEntities) + if err != nil { + return err + } + d.SetId(bucket) return nil } -func resourceStorageDefaultObjectAclUpdate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - bucket := d.Get("bucket").(string) - - if !d.HasChange("role_entity") { - return nil - } - o, n := d.GetChange("role_entity") - oldRe := o.([]interface{}) - newRe := n.([]interface{}) - - oldReMap := make(map[string]string) - for _, v := range oldRe { - res, err := getRoleEntityPair(v.(string)) - - if err != nil { - return fmt.Errorf( - "Old state has malformed Role/Entity pair: %v", err) - } - - oldReMap[res.Entity] = res.Role - } - - for _, v := range newRe { - pair, err := getRoleEntityPair(v.(string)) - - ObjectAccessControl := &storage.ObjectAccessControl{ - Role: pair.Role, - Entity: pair.Entity, - } - - // If the old state is present for the entity, it is updated - // If the old state is missing, it is inserted - if _, ok := oldReMap[pair.Entity]; ok { - _, err = config.clientStorage.DefaultObjectAccessControls.Update( - bucket, pair.Entity, ObjectAccessControl).Do() - } else { - _, err = config.clientStorage.DefaultObjectAccessControls.Insert( - bucket, ObjectAccessControl).Do() - } - - // Now we only store the keys that have to be removed - delete(oldReMap, pair.Entity) - - if err != nil { - return fmt.Errorf("Error updating Storage Default Object ACL for bucket %s: %v", bucket, err) - } - } - - for entity := range oldReMap { - log.Printf("[DEBUG]: removing entity %s", entity) - err := config.clientStorage.DefaultObjectAccessControls.Delete(bucket, entity).Do() - - if err != nil { - return fmt.Errorf("Error updating Storage Default Object ACL for bucket %s: %v", bucket, err) - } - } - - return resourceStorageDefaultObjectAclRead(d, meta) -} - func resourceStorageDefaultObjectAclDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) bucket := d.Get("bucket").(string) + res, err := config.clientStorage.Buckets.Get(bucket).Do() + if err != nil { + return fmt.Errorf("Error reading bucket %s: %v", bucket, err) + } - reLocal := d.Get("role_entity").([]interface{}) - for _, v := range reLocal { - res, err := getRoleEntityPair(v.(string)) - if err != nil { - return err - } - - log.Printf("[DEBUG]: removing entity %s", res.Entity) - - err = config.clientStorage.DefaultObjectAccessControls.Delete(bucket, res.Entity).Do() - - if err != nil { - return fmt.Errorf("Error deleting entity %s ACL: %s", res.Entity, err) - } + _, err = config.clientStorage.Buckets.Update(bucket, res).IfMetagenerationMatch(res.Metageneration).PredefinedDefaultObjectAcl("private").Do() + if err != nil { + return fmt.Errorf("Error deleting (updating to private) default object acl for bucket %s: %v", bucket, err) } return nil diff --git a/google/resource_storage_default_object_acl_test.go b/google/resource_storage_default_object_acl_test.go index bf4de1b9..f0688149 100644 --- a/google/resource_storage_default_object_acl_test.go +++ b/google/resource_storage_default_object_acl_test.go @@ -12,7 +12,6 @@ func TestAccStorageDefaultObjectAcl_basic(t *testing.T) { t.Parallel() bucketName := testBucketName() - resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -29,6 +28,22 @@ func TestAccStorageDefaultObjectAcl_basic(t *testing.T) { }) } +func TestAccStorageDefaultObjectAcl_noRoleEntity(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclNoRoleEntity(bucketName), + }, + }, + }) +} + func TestAccStorageDefaultObjectAcl_upgrade(t *testing.T) { t.Parallel() @@ -178,6 +193,19 @@ func testAccCheckGoogleStorageDefaultObjectAclDelete(bucket, roleEntityS string) } } +func testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntity1, roleEntity2 string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_default_object_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, bucketName, roleEntity1, roleEntity2) +} + func testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntity string) string { return fmt.Sprintf(` resource "google_storage_bucket" "bucket" { @@ -191,7 +219,7 @@ resource "google_storage_default_object_acl" "acl" { `, bucketName, roleEntity) } -func testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntity1, roleEntity2 string) string { +func testGoogleStorageDefaultObjectsAclNoRoleEntity(bucketName string) string { return fmt.Sprintf(` resource "google_storage_bucket" "bucket" { name = "%s" @@ -199,9 +227,9 @@ resource "google_storage_bucket" "bucket" { resource "google_storage_default_object_acl" "acl" { bucket = "${google_storage_bucket.bucket.name}" - role_entity = ["%s", "%s"] + role_entity = [] } -`, bucketName, roleEntity1, roleEntity2) +`, bucketName) } func testGoogleStorageDefaultObjectAclUnordered(bucketName string) string { diff --git a/website/docs/r/storage_default_object_acl.html.markdown b/website/docs/r/storage_default_object_acl.html.markdown index 96f05e61..9295575f 100644 --- a/website/docs/r/storage_default_object_acl.html.markdown +++ b/website/docs/r/storage_default_object_acl.html.markdown @@ -3,12 +3,19 @@ layout: "google" page_title: "Google: google_storage_default_object_acl" sidebar_current: "docs-google-storage-default-object-acl" description: |- - Creates a new default object ACL in Google Cloud Storage. + Authoritatively manages the default object ACLs for a Google Cloud Storage bucket --- # google\_storage\_default\_object\_acl -Creates a new default object ACL in Google Cloud Storage service (GCS). For more information see +Authoritatively manages the default object ACLs for a Google Cloud Storage bucket +without managing the bucket itself. + +-> Note that for each object, its creator will have the `"OWNER"` role in addition +to the default ACL that has been defined. + + +For more information see [the official documentation](https://cloud.google.com/storage/docs/access-control/lists) and [API](https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls). @@ -36,7 +43,11 @@ resource "google_storage_default_object_acl" "image-store-default-acl" { * `bucket` - (Required) The name of the bucket it applies to. -* `role_entity` - (Required) List of role/entity pairs in the form `ROLE:entity`. See [GCS Object ACL documentation](https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls) for more details. +--- + +* `role_entity` - (Optional) List of role/entity pairs in the form `ROLE:entity`. +See [GCS Object ACL documentation](https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls) for more details. +Omitting the field is the same as providing an empty list. ## Attributes Reference