mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-06 18:51:13 +00:00
347 lines
8.8 KiB
Go
347 lines
8.8 KiB
Go
package google
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
"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,
|
|
CustomizeDiff: resourceStorageRoleEntityCustomizeDiff,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"bucket": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"default_acl": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
|
|
"predefined_acl": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
ConflictsWith: []string{"role_entity"},
|
|
},
|
|
|
|
"role_entity": &schema.Schema{
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
ConflictsWith: []string{"predefined_acl"},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceStorageRoleEntityCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error {
|
|
keys := diff.GetChangedKeysPrefix("role_entity")
|
|
if len(keys) < 1 {
|
|
return nil
|
|
}
|
|
count := diff.Get("role_entity.#").(int)
|
|
if count < 1 {
|
|
return nil
|
|
}
|
|
state := map[string]struct{}{}
|
|
conf := map[string]struct{}{}
|
|
for i := 0; i < count; i++ {
|
|
old, new := diff.GetChange(fmt.Sprintf("role_entity.%d", i))
|
|
state[old.(string)] = struct{}{}
|
|
conf[new.(string)] = struct{}{}
|
|
}
|
|
if len(state) != len(conf) {
|
|
return nil
|
|
}
|
|
for k, _ := range state {
|
|
if _, ok := conf[k]; !ok {
|
|
return nil
|
|
}
|
|
}
|
|
return diff.Clear("role_entity")
|
|
}
|
|
|
|
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 {
|
|
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)
|
|
}
|
|
|
|
}
|
|
|
|
if len(role_entity) > 0 {
|
|
current, err := config.clientStorage.BucketAccessControls.List(bucket).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error retrieving current ACLs: %s", err)
|
|
}
|
|
for _, v := range role_entity {
|
|
pair, err := getRoleEntityPair(v.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var alreadyInserted bool
|
|
for _, cur := range current.Items {
|
|
if cur.Entity == pair.Entity && cur.Role == pair.Role {
|
|
alreadyInserted = true
|
|
break
|
|
}
|
|
}
|
|
if alreadyInserted {
|
|
log.Printf("[DEBUG]: pair %s-%s already exists, not trying to insert again\n", pair.Role, pair.Entity)
|
|
continue
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
}
|
|
|
|
d.SetId(getBucketAclId(bucket))
|
|
return resourceStorageBucketAclRead(d, meta)
|
|
}
|
|
|
|
func resourceStorageBucketAclRead(d *schema.ResourceData, meta interface{}) error {
|
|
config := meta.(*Config)
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
// The API offers no way to retrieve predefined ACLs,
|
|
// and we can't tell which access controls were created
|
|
// by the predefined roles, so...
|
|
//
|
|
// This is, needless to say, a bad state of affairs and
|
|
// should be fixed.
|
|
if _, ok := d.GetOk("role_entity"); ok {
|
|
res, err := config.clientStorage.BucketAccessControls.List(bucket).Do()
|
|
|
|
if err != nil {
|
|
return handleNotFoundError(err, d, fmt.Sprintf("Storage Bucket ACL for bucket %q", d.Get("bucket").(string)))
|
|
}
|
|
entities := make([]string, 0, len(res.Items))
|
|
for _, item := range res.Items {
|
|
entities = append(entities, item.Role+":"+item.Entity)
|
|
}
|
|
|
|
d.Set("role_entity", entities)
|
|
} else {
|
|
// if we don't set `role_entity` to nil (effectively setting it
|
|
// to empty in Terraform state), because it's computed now,
|
|
// Terraform will think it's missing from state, is supposed
|
|
// to be there, and throw up a diff for role_entity.#. So it
|
|
// must always be set in state.
|
|
d.Set("role_entity", nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceStorageBucketAclUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
config := meta.(*Config)
|
|
|
|
bucket := d.Get("bucket").(string)
|
|
|
|
if d.HasChange("role_entity") {
|
|
bkt, err := config.clientStorage.Buckets.Get(bucket).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error reading bucket %q: %v", bucket, err)
|
|
}
|
|
|
|
project := strconv.FormatUint(bkt.ProjectNumber, 10)
|
|
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 inserted
|
|
if _, ok := old_re_map[pair.Entity]; !ok {
|
|
_, 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, role := range old_re_map {
|
|
if entity == fmt.Sprintf("project-owners-%s", project) && role == "OWNER" {
|
|
log.Printf("Skipping %s-%s; not deleting owner ACL.", role, entity)
|
|
continue
|
|
}
|
|
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)
|
|
|
|
bkt, err := config.clientStorage.Buckets.Get(bucket).Do()
|
|
if err != nil {
|
|
return fmt.Errorf("Error retrieving bucket %q: %v", bucket, err)
|
|
}
|
|
project := strconv.FormatUint(bkt.ProjectNumber, 10)
|
|
|
|
re_local := d.Get("role_entity").([]interface{})
|
|
for _, v := range re_local {
|
|
res, err := getRoleEntityPair(v.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.Entity == fmt.Sprintf("project-owners-%s", project) && res.Role == "OWNER" {
|
|
log.Printf("Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
|
|
continue
|
|
}
|
|
|
|
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
|
|
}
|