Skip to content

Commit

Permalink
remove drift detection and update ability
Browse files Browse the repository at this point in the history
  • Loading branch information
danawillow committed Oct 24, 2019
1 parent e943dc2 commit 96e7ce3
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 210 deletions.
180 changes: 60 additions & 120 deletions third_party/terraform/resources/resource_iam_binding.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,23 @@ var iamBindingSchema = map[string]*schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"expression": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"title": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
Expand All @@ -64,39 +68,18 @@ func ResourceIamBinding(parentSpecificSchema map[string]*schema.Schema, newUpdat
// Resource that batches requests to the same IAM policy across multiple IAM fine-grained resources
func ResourceIamBindingWithBatching(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc, resourceIdParser resourceIdParserFunc, enableBatching bool) *schema.Resource {
return &schema.Resource{
Create: resourceIamBindingCreate(newUpdaterFunc, enableBatching),
Create: resourceIamBindingCreateUpdate(newUpdaterFunc, enableBatching),
Read: resourceIamBindingRead(newUpdaterFunc),
Update: resourceIamBindingUpdate(newUpdaterFunc, enableBatching),
Update: resourceIamBindingCreateUpdate(newUpdaterFunc, enableBatching),
Delete: resourceIamBindingDelete(newUpdaterFunc, enableBatching),
Schema: mergeSchemas(iamBindingSchema, parentSpecificSchema),
Importer: &schema.ResourceImporter{
State: iamBindingImport(resourceIdParser),
},
SchemaVersion: 1,
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceIAMBindingResourceV0(parentSpecificSchema).CoreConfigSchema().ImpliedType(),
Upgrade: func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
// IAM conditions require parsing information from the ID, which means the ID needs
// a separator that won't occur naturally in the project or role.
// Previous format: projects/{project}/{resourceType}/{resourceName}/roles/{role}
// New format: projects/{project}/{resourceType}/{resourceName} roles/{role}
oldId := rawState["id"].(string)
role := rawState["role"].(string)
rawState["id"] = strings.TrimSuffix(oldId, "/"+role) + " " + role
return rawState, nil
},
Version: 0,
},
State: iamBindingImport(newUpdaterFunc, resourceIdParser),
},
}
}

func resourceIAMBindingResourceV0(parentSpecificSchema map[string]*schema.Schema) *schema.Resource {
return &schema.Resource{Schema: mergeSchemas(iamBindingSchema, parentSpecificSchema)}
}

func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc, enableBatching bool) func(*schema.ResourceData, interface{}) error {
func resourceIamBindingCreateUpdate(newUpdaterFunc newResourceIamUpdaterFunc, enableBatching bool) func(*schema.ResourceData, interface{}) error {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
Expand All @@ -123,11 +106,8 @@ func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc, enableBa
if err != nil {
return err
}
conditionTitle := ""
if binding.Condition != nil {
conditionTitle = binding.Condition.Title
}
d.SetId(updater.GetResourceId() + " " + binding.Role + " " + conditionTitle)

d.SetId(updater.GetResourceId() + "/" + binding.Role)
return resourceIamBindingRead(newUpdaterFunc)(d, meta)
}
}
Expand All @@ -140,37 +120,26 @@ func resourceIamBindingRead(newUpdaterFunc newResourceIamUpdaterFunc) schema.Rea
return err
}

var role, conditionTitle string
s := strings.SplitN(d.Id(), " ", 3)
if len(s) == 2 {
role = s[1]
} else if len(s) == 3 {
role, conditionTitle = s[1], s[2]
} else {
return fmt.Errorf("Unexpected resource ID %s", d.Id())
}
eBinding := getResourceIamBinding(d)
eCondition := conditionKeyFromCondition(eBinding.Condition)
p, err := iamPolicyReadWithRetry(updater)
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Resource %q with IAM Binding (Role %q)", updater.DescribeResource(), role))
return handleNotFoundError(err, d, fmt.Sprintf("Resource %q with IAM Binding (Role %q)", updater.DescribeResource(), eBinding.Role))
}
log.Printf("[DEBUG]: Retrieved policy for %s: %+v", updater.DescribeResource(), p)
log.Printf("[DEBUG] Retrieved policy for %s: %+v", updater.DescribeResource(), p)
log.Printf("[DEBUG] Looking for binding with role %q and condition %+v", eBinding.Role, eCondition)

log.Printf("Looking for binding with role %s and condition %s", role, conditionTitle)
var binding *cloudresourcemanager.Binding
for _, b := range p.Bindings {
if b.Role == role && ((b.Condition == nil && conditionTitle == "") || (b.Condition != nil && b.Condition.Title == conditionTitle)) {
c := ""
if b.Condition != nil {
c = b.Condition.Title
}
log.Printf("Found binding with role %s and condition %s", b.Role, c)
if b.Role == eBinding.Role && conditionKeyFromCondition(b.Condition) == eCondition {
binding = b
break
}
}

if binding == nil {
log.Printf("[DEBUG]: Binding for role %q and condition %q not found in policy for %s, assuming it has no members.", role, conditionTitle, updater.DescribeResource())
d.Set("role", role)
log.Printf("[DEBUG] Binding for role %q and condition %+v not found in policy for %s, assuming it has no members.", eBinding.Role, eCondition, updater.DescribeResource())
d.Set("role", eBinding.Role)
d.Set("members", nil)
return nil
} else {
Expand All @@ -185,61 +154,34 @@ func resourceIamBindingRead(newUpdaterFunc newResourceIamUpdaterFunc) schema.Rea
}
}

func resourceIamBindingUpdate(newUpdaterFunc newResourceIamUpdaterFunc, enableBatching bool) func(*schema.ResourceData, interface{}) error {
return func(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
updater, err := newUpdaterFunc(d, config)
if err != nil {
return err
}

oBinding, nBinding := getResourceIamBindingChange(d)
modifyF := func(ep *cloudresourcemanager.Policy) error {
cleaned := removeAllBindingsWithRoleAndCondition(ep.Bindings, oBinding.Role, oBinding.Condition)
cleaned = removeAllBindingsWithRoleAndCondition(cleaned, nBinding.Role, nBinding.Condition)
ep.Bindings = append(cleaned, nBinding)
<% unless version == 'ga' -%>
ep.Version = 3
<% end -%>
return nil
}

if enableBatching {
err = BatchRequestModifyIamPolicy(updater, modifyF, config, fmt.Sprintf(
"Set IAM Binding for role %q on %q", nBinding.Role, updater.DescribeResource()))
} else {
err = iamPolicyReadModifyWrite(updater, modifyF)
}
if err != nil {
return err
}

conditionTitle := ""
if nBinding.Condition != nil {
conditionTitle = nBinding.Condition.Title
}
d.SetId(updater.GetResourceId() + " " + nBinding.Role + " " + conditionTitle)
return resourceIamBindingRead(newUpdaterFunc)(d, meta)
}
}

func iamBindingImport(resourceIdParser resourceIdParserFunc) schema.StateFunc {
func iamBindingImport(newUpdaterFunc newResourceIamUpdaterFunc, resourceIdParser resourceIdParserFunc) schema.StateFunc {
return func(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
if resourceIdParser == nil {
return nil, errors.New("Import not supported for this IAM resource.")
}
config := m.(*Config)
s := strings.Fields(d.Id())
var id, role, conditionTitle string
var id, role string
<% if version == 'ga' -%>
if len(s) != 2 {
d.SetId("")
return nil, fmt.Errorf("Wrong number of parts to Binding id %s; expected 'resource_name role'.", s)
}
id, role = s[0], s[1]
<% else -%>
if len(s) < 2 {
d.SetId("")
return nil, fmt.Errorf("Wrong number of parts to Binding id %s; expected 'resource_name role [condition_title]'.", s)
}

var conditionTitle string
if len(s) == 2 {
id, role = s[0], s[1]
} else if len(s) >= 3 {
} else {
// condition titles can have any characters in them, so re-join the split string
id, role, conditionTitle = s[0], s[1], strings.Join(s[2:], " ")
} else {
d.SetId("")
return nil, fmt.Errorf("Wrong number of parts to Binding id %s; expected 'resource_name role [condition_title]'.", s)
}
<% end -%>

// Set the ID only to the first part so all IAM types can share the same resourceIdParserFunc.
d.SetId(id)
Expand All @@ -251,8 +193,30 @@ func iamBindingImport(resourceIdParser resourceIdParserFunc) schema.StateFunc {

// Set the ID again so that the ID matches the ID it would have if it had been created via TF.
// Use the current ID in case it changed in the resourceIdParserFunc.
// Use " " as the delimiter because projects can have ':'s and roles can have '/'s.
d.SetId(d.Id() + " " + role + " " + conditionTitle)
d.SetId(d.Id() + "/" + role)

<% unless version == 'ga' -%>
// Read the upstream policy so we can set the full condition.
updater, err := newUpdaterFunc(d, config)
if err != nil {
return nil, err
}
p, err := iamPolicyReadWithRetry(updater)
if err != nil {
return nil, err
}
var binding *cloudresourcemanager.Binding
for _, b := range p.Bindings {
if (b.Role == role && conditionKeyFromCondition(b.Condition).Title == conditionTitle) {
if binding != nil {
return nil, fmt.Errorf("Cannot import IAM member with condition title %q, it matches multiple conditions", conditionTitle)
}
binding = b
}
}
d.Set("condition", flattenIamCondition(binding.Condition))
<% end -%>

// It is possible to return multiple bindings, since we can learn about all the bindings
// for this resource here. Unfortunately, `terraform import` has some messy behavior here -
// there's no way to know at this point which resource is being imported, so it's not possible
Expand Down Expand Up @@ -306,30 +270,6 @@ func getResourceIamBinding(d *schema.ResourceData) *cloudresourcemanager.Binding
}
}

func getResourceIamBindingChange(d *schema.ResourceData) (*cloudresourcemanager.Binding, *cloudresourcemanager.Binding) {
oMembers, nMembers := d.GetChange("members")
oRole, nRole := d.GetChange("role")
<% unless version == 'ga' -%>
oCondition, nCondition := d.GetChange("condition")
<% end -%>

oBinding := &cloudresourcemanager.Binding{
Members: convertStringArr(oMembers.(*schema.Set).List()),
Role: oRole.(string),
<% unless version == 'ga' -%>
Condition: expandIamCondition(oCondition),
<% end -%>
}
nBinding := &cloudresourcemanager.Binding{
Members: convertStringArr(nMembers.(*schema.Set).List()),
Role: nRole.(string),
<% unless version == 'ga' -%>
Condition: expandIamCondition(nCondition),
<% end -%>
}
return oBinding, nBinding
}

<% unless version == 'ga' -%>
func expandIamCondition(v interface{}) *cloudresourcemanager.Expr {
l := v.([]interface{})
Expand Down
Loading

0 comments on commit 96e7ce3

Please sign in to comment.