diff --git a/provider.go b/provider.go index a023b81c..7c958721 100644 --- a/provider.go +++ b/provider.go @@ -54,7 +54,9 @@ func Provider() terraform.ResourceProvider { "google_dns_record_set": resourceDnsRecordSet(), "google_compute_instance_group_manager": resourceComputeInstanceGroupManager(), "google_storage_bucket": resourceStorageBucket(), + "google_storage_bucket_acl": resourceStorageBucketAcl(), "google_storage_bucket_object": resourceStorageBucketObject(), + "google_storage_object_acl": resourceStorageObjectAcl(), }, ConfigureFunc: providerConfigure, diff --git a/resource_storage_bucket.go b/resource_storage_bucket.go index de03d5f6..64e4fd43 100644 --- a/resource_storage_bucket.go +++ b/resource_storage_bucket.go @@ -24,10 +24,10 @@ func resourceStorageBucket() *schema.Resource { ForceNew: true, }, "predefined_acl": &schema.Schema{ - Type: schema.TypeString, - Default: "projectPrivate", - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Deprecated: "Please use resource \"storage_bucket_acl.predefined_acl\" instead.", + Optional: true, + ForceNew: true, }, "location": &schema.Schema{ Type: schema.TypeString, @@ -69,7 +69,6 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error // Get the bucket and acl bucket := d.Get("name").(string) - acl := d.Get("predefined_acl").(string) location := d.Get("location").(string) // Create a bucket, setting the acl, location and name. @@ -95,7 +94,12 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error } } - res, err := config.clientStorage.Buckets.Insert(config.Project, sb).PredefinedAcl(acl).Do() + call := config.clientStorage.Buckets.Insert(config.Project, sb) + if v, ok := d.GetOk("predefined_acl"); ok { + call = call.PredefinedAcl(v.(string)) + } + + res, err := call.Do() if err != nil { fmt.Printf("Error creating bucket %s: %v", bucket, err) diff --git a/resource_storage_bucket_acl.go b/resource_storage_bucket_acl.go new file mode 100644 index 00000000..1c2ef2ab --- /dev/null +++ b/resource_storage_bucket_acl.go @@ -0,0 +1,292 @@ +package google + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + + "google.golang.org/api/storage/v1" +) + +func resourceStorageBucketAcl() *schema.Resource { + return &schema.Resource{ + Create: resourceStorageBucketAclCreate, + Read: resourceStorageBucketAclRead, + Update: resourceStorageBucketAclUpdate, + Delete: resourceStorageBucketAclDelete, + + Schema: map[string]*schema.Schema{ + "bucket": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "predefined_acl": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "role_entity": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "default_acl": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +type RoleEntity struct { + Role string + Entity string +} + +func getBucketAclId(bucket string) string { + return bucket + "-acl" +} + +func getRoleEntityPair(role_entity string) (*RoleEntity, error) { + split := strings.Split(role_entity, ":") + if len(split) != 2 { + return nil, fmt.Errorf("Error, each role entity pair must be " + + "formatted as ROLE:entity") + } + + return &RoleEntity{Role: split[0], Entity: split[1]}, nil +} + +func resourceStorageBucketAclCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + predefined_acl := "" + default_acl := "" + role_entity := make([]interface{}, 0) + + if v, ok := d.GetOk("predefined_acl"); ok { + predefined_acl = v.(string) + } + + if v, ok := d.GetOk("role_entity"); ok { + role_entity = v.([]interface{}) + } + + if v, ok := d.GetOk("default_acl"); ok { + default_acl = v.(string) + } + + if len(predefined_acl) > 0 { + if len(role_entity) > 0 { + return fmt.Errorf("Error, you cannot specify both " + + "\"predefined_acl\" and \"role_entity\""); + } + + res, err := config.clientStorage.Buckets.Get(bucket).Do() + + if err != nil { + return fmt.Errorf("Error reading bucket %s: %v", bucket, err) + } + + res, err = config.clientStorage.Buckets.Update(bucket, + res).PredefinedAcl(predefined_acl).Do() + + if err != nil { + return fmt.Errorf("Error updating bucket %s: %v", bucket, err) + } + + return resourceStorageBucketAclRead(d, meta); + } else if len(role_entity) > 0 { + for _, v := range(role_entity) { + pair, err := getRoleEntityPair(v.(string)) + + bucketAccessControl := &storage.BucketAccessControl{ + Role: pair.Role, + Entity: pair.Entity, + } + + log.Printf("[DEBUG]: storing re %s-%s", pair.Role, pair.Entity) + + _, err = config.clientStorage.BucketAccessControls.Insert(bucket, bucketAccessControl).Do() + + if err != nil { + return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err) + } + } + + return resourceStorageBucketAclRead(d, meta); + } + + if len(default_acl) > 0 { + res, err := config.clientStorage.Buckets.Get(bucket).Do() + + if err != nil { + return fmt.Errorf("Error reading bucket %s: %v", bucket, err) + } + + res, err = config.clientStorage.Buckets.Update(bucket, + res).PredefinedDefaultObjectAcl(default_acl).Do() + + if err != nil { + return fmt.Errorf("Error updating bucket %s: %v", bucket, err) + } + + return resourceStorageBucketAclRead(d, meta); + } + + return nil +} + + +func resourceStorageBucketAclRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + + // Predefined ACLs cannot easily be parsed once they have been processed + // by the GCP server + if _, ok := d.GetOk("predefined_acl"); !ok { + role_entity := make([]interface{}, 0) + re_local := d.Get("role_entity").([]interface{}) + re_local_map := make(map[string]string) + for _, v := range(re_local) { + res, err := getRoleEntityPair(v.(string)) + + if err != nil { + return fmt.Errorf( + "Old state has malformed Role/Entity pair: %v", err) + } + + re_local_map[res.Entity] = res.Role + } + + res, err := config.clientStorage.BucketAccessControls.List(bucket).Do() + + if err != nil { + return err + } + + for _, v := range(res.Items) { + log.Printf("[DEBUG]: examining re %s-%s", v.Role, v.Entity) + // We only store updates to the locally defined access controls + if _, in := re_local_map[v.Entity]; in { + role_entity = append(role_entity, fmt.Sprintf("%s:%s", v.Role, v.Entity)) + log.Printf("[DEBUG]: saving re %s-%s", v.Role, v.Entity) + } + } + + d.Set("role_entity", role_entity) + } + + d.SetId(getBucketAclId(bucket)) + return nil +} + +func resourceStorageBucketAclUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + + if d.HasChange("role_entity") { + o, n := d.GetChange("role_entity") + old_re, new_re := o.([]interface{}), n.([]interface{}) + + old_re_map := make(map[string]string) + for _, v := range(old_re) { + res, err := getRoleEntityPair(v.(string)) + + if err != nil { + return fmt.Errorf( + "Old state has malformed Role/Entity pair: %v", err) + } + + old_re_map[res.Entity] = res.Role + } + + for _, v := range(new_re) { + pair, err := getRoleEntityPair(v.(string)) + + bucketAccessControl := &storage.BucketAccessControl{ + Role: pair.Role, + Entity: pair.Entity, + } + + // If the old state is missing this entity, it needs to + // be created. Otherwise it is updated + if _, ok := old_re_map[pair.Entity]; ok { + _, err = config.clientStorage.BucketAccessControls.Update( + bucket, pair.Entity, bucketAccessControl).Do() + } else { + _, err = config.clientStorage.BucketAccessControls.Insert( + bucket, bucketAccessControl).Do() + } + + // Now we only store the keys that have to be removed + delete(old_re_map, pair.Entity) + + if err != nil { + return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err) + } + } + + for entity, _ := range(old_re_map) { + log.Printf("[DEBUG]: removing entity %s", entity) + err := config.clientStorage.BucketAccessControls.Delete(bucket, entity).Do() + + if err != nil { + return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err) + } + } + + return resourceStorageBucketAclRead(d, meta); + } + + if d.HasChange("default_acl") { + default_acl := d.Get("default_acl").(string) + + res, err := config.clientStorage.Buckets.Get(bucket).Do() + + if err != nil { + return fmt.Errorf("Error reading bucket %s: %v", bucket, err) + } + + res, err = config.clientStorage.Buckets.Update(bucket, + res).PredefinedDefaultObjectAcl(default_acl).Do() + + if err != nil { + return fmt.Errorf("Error updating bucket %s: %v", bucket, err) + } + + return resourceStorageBucketAclRead(d, meta); + } + + return nil +} + +func resourceStorageBucketAclDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + + re_local := d.Get("role_entity").([]interface{}) + for _, v := range(re_local) { + res, err := getRoleEntityPair(v.(string)) + if err != nil { + return err + } + + log.Printf("[DEBUG]: removing entity %s", res.Entity) + + err = config.clientStorage.BucketAccessControls.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/resource_storage_bucket_acl_test.go b/resource_storage_bucket_acl_test.go new file mode 100644 index 00000000..afcb991c --- /dev/null +++ b/resource_storage_bucket_acl_test.go @@ -0,0 +1,232 @@ +package google + +import ( + "fmt" + "testing" + "math/rand" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + //"google.golang.org/api/storage/v1" +) + +var roleEntityBasic1 = "OWNER:user-omeemail@gmail.com" + +var roleEntityBasic2 = "READER:user-anotheremail@gmail.com" + +var roleEntityBasic3_owner = "OWNER:user-yetanotheremail@gmail.com" + +var roleEntityBasic3_reader = "READER:user-yetanotheremail@gmail.com" + +var testAclBucketName = fmt.Sprintf("%s-%d", "tf-test-acl-bucket", rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + +func TestAccGoogleStorageBucketAcl_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic1, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic2), + ), + }, + }, + }) +} + +func TestAccGoogleStorageBucketAcl_upgrade(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic1, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic2), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic2, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasicDelete, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAclDelete(testAclBucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAclDelete(testAclBucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAclDelete(testAclBucketName, roleEntityBasic3_owner), + ), + }, + }, + }) +} + +func TestAccGoogleStorageBucketAcl_downgrade(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic2, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic3, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAcl(testAclBucketName, roleEntityBasic3_reader), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasicDelete, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAclDelete(testAclBucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAclDelete(testAclBucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAclDelete(testAclBucketName, roleEntityBasic3_owner), + ), + }, + }, + }) +} + +func TestAccGoogleStorageBucketAcl_predefined(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclPredefined, + }, + }, + }) +} + +func testAccCheckGoogleStorageBucketAclDelete(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.BucketAccessControls.Get(bucket, roleEntity.Entity).Do() + + if err != nil { + return nil + } + + return fmt.Errorf("Error, entity %s still exists", roleEntity.Entity) + } +} + +func testAccCheckGoogleStorageBucketAcl(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + res, err := config.clientStorage.BucketAccessControls.Get(bucket, roleEntity.Entity).Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of 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 testAccGoogleStorageBucketAclDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + + _, err := config.clientStorage.BucketAccessControls.List(bucket).Do() + + if err == nil { + return fmt.Errorf("Acl for bucket %s still exists", bucket) + } + } + + return nil +} + +var testGoogleStorageBucketsAclBasic1 = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, testAclBucketName, roleEntityBasic1, roleEntityBasic2) + +var testGoogleStorageBucketsAclBasic2 = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, testAclBucketName, roleEntityBasic2, roleEntityBasic3_owner) + +var testGoogleStorageBucketsAclBasicDelete = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = [] +} +`, testAclBucketName) + +var testGoogleStorageBucketsAclBasic3 = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, testAclBucketName, roleEntityBasic2, roleEntityBasic3_reader) + + +var testGoogleStorageBucketsAclPredefined = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + predefined_acl = "projectPrivate" + default_acl = "projectPrivate" +} +`, testAclBucketName) diff --git a/resource_storage_bucket_object.go b/resource_storage_bucket_object.go index bd8e4f04..473349d3 100644 --- a/resource_storage_bucket_object.go +++ b/resource_storage_bucket_object.go @@ -33,7 +33,7 @@ func resourceStorageBucketObject() *schema.Resource { }, "predefined_acl": &schema.Schema{ Type: schema.TypeString, - Default: "projectPrivate", + Deprecated: "Please use resource \"storage_object_acl.predefined_acl\" instead.", Optional: true, ForceNew: true, }, @@ -59,7 +59,6 @@ func resourceStorageBucketObjectCreate(d *schema.ResourceData, meta interface{}) bucket := d.Get("bucket").(string) name := d.Get("name").(string) source := d.Get("source").(string) - acl := d.Get("predefined_acl").(string) file, err := os.Open(source) if err != nil { @@ -72,7 +71,10 @@ func resourceStorageBucketObjectCreate(d *schema.ResourceData, meta interface{}) insertCall := objectsService.Insert(bucket, object) insertCall.Name(name) insertCall.Media(file) - insertCall.PredefinedAcl(acl) + if v, ok := d.GetOk("predefined_acl"); ok { + insertCall.PredefinedAcl(v.(string)) + } + _, err = insertCall.Do() diff --git a/resource_storage_object_acl.go b/resource_storage_object_acl.go new file mode 100644 index 00000000..86745328 --- /dev/null +++ b/resource_storage_object_acl.go @@ -0,0 +1,254 @@ +package google + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + + "google.golang.org/api/storage/v1" +) + +func resourceStorageObjectAcl() *schema.Resource { + return &schema.Resource{ + Create: resourceStorageObjectAclCreate, + Read: resourceStorageObjectAclRead, + Update: resourceStorageObjectAclUpdate, + Delete: resourceStorageObjectAclDelete, + + Schema: map[string]*schema.Schema{ + "bucket": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "object": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "role_entity": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "predefined_acl": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func getObjectAclId(object string) string { + return object + "-acl" +} + +func resourceStorageObjectAclCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + object := d.Get("object").(string) + + predefined_acl := "" + role_entity := make([]interface{}, 0) + + if v, ok := d.GetOk("predefined_acl"); ok { + predefined_acl = v.(string) + } + + if v, ok := d.GetOk("role_entity"); ok { + role_entity = v.([]interface{}) + } + + if len(predefined_acl) > 0 { + if len(role_entity) > 0 { + return fmt.Errorf("Error, you cannot specify both " + + "\"predefined_acl\" and \"role_entity\""); + } + + res, err := config.clientStorage.Objects.Get(bucket, object).Do() + + if err != nil { + return fmt.Errorf("Error reading object %s: %v", bucket, err) + } + + res, err = config.clientStorage.Objects.Update(bucket,object, + res).PredefinedAcl(predefined_acl).Do() + + if err != nil { + return fmt.Errorf("Error updating object %s: %v", bucket, err) + } + + return resourceStorageBucketAclRead(d, meta); + } else if len(role_entity) > 0 { + for _, v := range(role_entity) { + pair, err := getRoleEntityPair(v.(string)) + + objectAccessControl := &storage.ObjectAccessControl{ + Role: pair.Role, + Entity: pair.Entity, + } + + log.Printf("[DEBUG]: setting role = %s, entity = %s", pair.Role, pair.Entity) + + _, err = config.clientStorage.ObjectAccessControls.Insert(bucket, + object, objectAccessControl).Do() + + if err != nil { + return fmt.Errorf("Error setting ACL for %s on object %s: %v", pair.Entity, object, err) + } + } + + return resourceStorageObjectAclRead(d, meta); + } + + return fmt.Errorf("Error, you must specify either " + + "\"predefined_acl\" or \"role_entity\""); +} + + +func resourceStorageObjectAclRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + object := d.Get("object").(string) + + // Predefined ACLs cannot easily be parsed once they have been processed + // by the GCP server + if _, ok := d.GetOk("predefined_acl"); !ok { + role_entity := make([]interface{}, 0) + re_local := d.Get("role_entity").([]interface{}) + re_local_map := make(map[string]string) + for _, v := range(re_local) { + res, err := getRoleEntityPair(v.(string)) + + if err != nil { + return fmt.Errorf( + "Old state has malformed Role/Entity pair: %v", err) + } + + re_local_map[res.Entity] = res.Role + } + + res, err := config.clientStorage.ObjectAccessControls.List(bucket, object).Do() + + if err != nil { + return err + } + + for _, v := range(res.Items) { + role := "" + entity := "" + for key, val := range (v.(map[string]interface{})) { + if key == "role" { + role = val.(string) + } else if key == "entity" { + entity = val.(string) + } + } + if _, in := re_local_map[entity]; in { + role_entity = append(role_entity, fmt.Sprintf("%s:%s", role, entity)) + log.Printf("[DEBUG]: saving re %s-%s", role, entity) + } + } + + d.Set("role_entity", role_entity) + } + + d.SetId(getObjectAclId(object)) + return nil +} + +func resourceStorageObjectAclUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + object := d.Get("object").(string) + + if d.HasChange("role_entity") { + o, n := d.GetChange("role_entity") + old_re, new_re := o.([]interface{}), n.([]interface{}) + + old_re_map := make(map[string]string) + for _, v := range(old_re) { + res, err := getRoleEntityPair(v.(string)) + + if err != nil { + return fmt.Errorf( + "Old state has malformed Role/Entity pair: %v", err) + } + + old_re_map[res.Entity] = res.Role + } + + for _, v := range(new_re) { + pair, err := getRoleEntityPair(v.(string)) + + objectAccessControl := &storage.ObjectAccessControl{ + Role: pair.Role, + Entity: pair.Entity, + } + + // If the old state is missing this entity, it needs to + // be created. Otherwise it is updated + if _, ok := old_re_map[pair.Entity]; ok { + _, err = config.clientStorage.ObjectAccessControls.Update( + bucket, object, pair.Entity, objectAccessControl).Do() + } else { + _, err = config.clientStorage.ObjectAccessControls.Insert( + bucket, object, objectAccessControl).Do() + } + + // Now we only store the keys that have to be removed + delete(old_re_map, pair.Entity) + + if err != nil { + return fmt.Errorf("Error updating ACL for object %s: %v", bucket, err) + } + } + + for entity, _ := range(old_re_map) { + log.Printf("[DEBUG]: removing entity %s", entity) + err := config.clientStorage.ObjectAccessControls.Delete(bucket, object, entity).Do() + + if err != nil { + return fmt.Errorf("Error updating ACL for object %s: %v", bucket, err) + } + } + + return resourceStorageObjectAclRead(d, meta); + } + + return nil +} + +func resourceStorageObjectAclDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + bucket := d.Get("bucket").(string) + object := d.Get("object").(string) + + re_local := d.Get("role_entity").([]interface{}) + for _, v := range(re_local) { + res, err := getRoleEntityPair(v.(string)) + if err != nil { + return err + } + + entity := res.Entity + + log.Printf("[DEBUG]: removing entity %s", entity) + + err = config.clientStorage.ObjectAccessControls.Delete(bucket, object, + entity).Do() + + if err != nil { + return fmt.Errorf("Error deleting entity %s ACL: %s", + entity, err) + } + } + + return nil +} diff --git a/resource_storage_object_acl_test.go b/resource_storage_object_acl_test.go new file mode 100644 index 00000000..f0154aca --- /dev/null +++ b/resource_storage_object_acl_test.go @@ -0,0 +1,310 @@ +package google + +import ( + "fmt" + "testing" + "math/rand" + "io/ioutil" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + //"google.golang.org/api/storage/v1" +) + +var tfObjectAcl, errObjectAcl = ioutil.TempFile("", "tf-gce-test") +var testAclObjectName = fmt.Sprintf("%s-%d", "tf-test-acl-object", + rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + +func TestAccGoogleStorageObjectAcl_basic(t *testing.T) { + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic1, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic2), + ), + }, + }, + }) +} + +func TestAccGoogleStorageObjectAcl_upgrade(t *testing.T) { + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic1, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic2), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic2, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasicDelete, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAclDelete(testAclBucketName, + testAclObjectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAclDelete(testAclBucketName, + testAclObjectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAclDelete(testAclBucketName, + testAclObjectName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func TestAccGoogleStorageObjectAcl_downgrade(t *testing.T) { + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic2, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic3, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAcl(testAclBucketName, + testAclObjectName, roleEntityBasic3_reader), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasicDelete, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAclDelete(testAclBucketName, + testAclObjectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAclDelete(testAclBucketName, + testAclObjectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAclDelete(testAclBucketName, + testAclObjectName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func TestAccGoogleStorageObjectAcl_predefined(t *testing.T) { + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccGoogleStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclPredefined, + }, + }, + }) +} + +func testAccCheckGoogleStorageObjectAcl(bucket, object, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + res, err := config.clientStorage.ObjectAccessControls.Get(bucket, + object, roleEntity.Entity).Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of 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 testAccCheckGoogleStorageObjectAclDelete(bucket, object, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.ObjectAccessControls.Get(bucket, + object, roleEntity.Entity).Do() + + if err != nil { + return nil + } + + return fmt.Errorf("Error, Entity still exists %s", roleEntity.Entity) + } +} + +func testAccGoogleStorageObjectAclDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + object := rs.Primary.Attributes["object"] + + _, err := config.clientStorage.ObjectAccessControls.List(bucket, object).Do() + + if err == nil { + return fmt.Errorf("Acl for bucket %s still exists", bucket) + } + } + + return nil +} + +var testGoogleStorageObjectsAclBasicDelete = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = [] +} +`, testAclBucketName, testAclObjectName, tfObjectAcl.Name()) + +var testGoogleStorageObjectsAclBasic1 = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, testAclBucketName, testAclObjectName, tfObjectAcl.Name(), + roleEntityBasic1, roleEntityBasic2) + +var testGoogleStorageObjectsAclBasic2 = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, testAclBucketName, testAclObjectName, tfObjectAcl.Name(), + roleEntityBasic2, roleEntityBasic3_owner) + +var testGoogleStorageObjectsAclBasic3 = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, testAclBucketName, testAclObjectName, tfObjectAcl.Name(), + roleEntityBasic2, roleEntityBasic3_reader) + +var testGoogleStorageObjectsAclPredefined = fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + predefined_acl = "projectPrivate" +} +`, testAclBucketName, testAclObjectName, tfObjectAcl.Name())