diff --git a/google/provider.go b/google/provider.go index d40b8018d0c..5850e5810de 100644 --- a/google/provider.go +++ b/google/provider.go @@ -192,6 +192,7 @@ func Provider() terraform.ResourceProvider { "google_storage_bucket_iam_member": ResourceIamMember(IamStorageBucketSchema, NewStorageBucketIamUpdater), "google_storage_bucket_object": resourceStorageBucketObject(), "google_storage_object_acl": resourceStorageObjectAcl(), + "google_storage_default_object_acl": resourceStorageDefaultObjectAcl(), }, ConfigureFunc: providerConfigure, diff --git a/google/resource_storage_default_object_acl.go b/google/resource_storage_default_object_acl.go new file mode 100644 index 00000000000..8e86a4aea94 --- /dev/null +++ b/google/resource_storage_default_object_acl.go @@ -0,0 +1,185 @@ +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, + + Schema: map[string]*schema.Schema{ + "bucket": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "role_entity": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + }, + }, + } +} + +func resourceStorageDefaultObjectAclCreate(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{ + 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() + + if err != nil { + return fmt.Errorf("Error setting Default Object ACL for %s on bucket %s: %v", pair.Entity, bucket, err) + } + } + d.SetId(bucket) + return resourceStorageDefaultObjectAclRead(d, meta) +} + +func resourceStorageDefaultObjectAclRead(d *schema.ResourceData, meta interface{}) error { + 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() + + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Storage Default 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) + } + } + + d.Set("role_entity", roleEntities) + + 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) + + 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) + } + } + + return nil +} diff --git a/google/resource_storage_default_object_acl_test.go b/google/resource_storage_default_object_acl_test.go new file mode 100644 index 00000000000..957c5395698 --- /dev/null +++ b/google/resource_storage_default_object_acl_test.go @@ -0,0 +1,187 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccGoogleStorageDefaultObjectAcl_basic(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic1, roleEntityBasic2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + ), + }, + }, + }) +} + +func TestAccGoogleStorageDefaultObjectAcl_upgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic1, roleEntityBasic2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic2, roleEntityBasic3_owner), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntityBasic1), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func TestAccGoogleStorageDefaultObjectAcl_downgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic2, roleEntityBasic3_owner), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic2, roleEntityBasic3_reader), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic3_reader), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntityBasic1), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func testAccCheckGoogleStorageDefaultObjectAcl(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + res, err := config.clientStorage.DefaultObjectAccessControls.Get(bucket, + roleEntity.Entity).Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of storage default Acl for bucket %s: %s", bucket, err) + } + + if res.Role != roleEntity.Role { + return fmt.Errorf("Error, Role mismatch %s != %s", res.Role, roleEntity.Role) + } + + return nil + } +} + +func testAccGoogleStorageDefaultObjectAclDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + + if rs.Type != "google_storage_default_object_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + + _, err := config.clientStorage.DefaultObjectAccessControls.List(bucket).Do() + if err == nil { + return fmt.Errorf("Default Storage Object Acl for bucket %s still exists", bucket) + } + } + return nil +} + +func testAccCheckGoogleStorageDefaultObjectAclDelete(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.DefaultObjectAccessControls.Get(bucket, roleEntity.Entity).Do() + + if err != nil { + return nil + } + + return fmt.Errorf("Error, Object Default Acl Entity still exists %s for bucket %s", + roleEntity.Entity, bucket) + } +} + +func testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntity 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"] +} +`, bucketName, roleEntity) +} + +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) +} diff --git a/website/docs/r/storage_default_object_acl.html.markdown b/website/docs/r/storage_default_object_acl.html.markdown new file mode 100644 index 00000000000..96f05e616d1 --- /dev/null +++ b/website/docs/r/storage_default_object_acl.html.markdown @@ -0,0 +1,43 @@ +--- +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. +--- + +# google\_storage\_default\_object\_acl + +Creates a new default object ACL in Google Cloud Storage service (GCS). 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). + +## Example Usage + +Example creating a default object ACL on a bucket with one owner, and one reader. + +```hcl +resource "google_storage_bucket" "image-store" { + name = "image-store-bucket" + location = "EU" +} + +resource "google_storage_default_object_acl" "image-store-default-acl" { + bucket = "${google_storage_bucket.image-store.name}" + role_entity = [ + "OWNER:user-my.email@gmail.com", + "READER:group-mygroup", + ] +} +``` + +## Argument Reference + +* `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. + +## Attributes Reference + +Only the arguments listed above are exposed as attributes. diff --git a/website/google.erb b/website/google.erb index 3a28109be3f..99590557069 100644 --- a/website/google.erb +++ b/website/google.erb @@ -522,6 +522,10 @@ google_storage_bucket_object + > + google_storage_default_object_acl + + > google_storage_object_acl