Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add google_project_iam_binding and google_project_iam_member resources. #171

Merged
merged 27 commits into from
Jul 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8e70487
Add google_project_iam_binding resource.
paddycarver Jul 4, 2017
eed8487
Update website docs for google_project_iam_binding.
paddycarver Jul 4, 2017
11abe24
Fix reads, make ForceNew.
paddycarver Jul 4, 2017
65a4a36
Remove ID when binding isn't found.
paddycarver Jul 4, 2017
a84b22d
Fix vet errors.
paddycarver Jul 4, 2017
f9236ab
Add the google_project_iam_member resource.
paddycarver Jul 4, 2017
8a880fd
Documentation updates.
paddycarver Jul 4, 2017
d3f901b
Refactor binding update loop for clarity.
paddycarver Jul 25, 2017
9c1c0bb
Just remove deleted bindings not present in the API.
paddycarver Jul 25, 2017
729e9fc
Create an iam policy read/modify/write helper.
paddycarver Jul 25, 2017
5282ad7
Use string slice conversion helper.
paddycarver Jul 25, 2017
f9eeb36
terraform fmt test configs.
paddycarver Jul 25, 2017
52d552d
id => project_id in test configs.
paddycarver Jul 25, 2017
b0e3790
Use the policy r/m/w helper and handle edge case.
paddycarver Jul 25, 2017
91d227c
id => project_id
paddycarver Jul 25, 2017
655435f
Terraform fmt on our test configs.
paddycarver Jul 25, 2017
fa2d54f
Terraform fmt website examples.
paddycarver Jul 25, 2017
199ff5d
Excise unnecessary type declarations.
paddycarver Jul 27, 2017
adc206a
Add test case for updating to remove member.
paddycarver Jul 27, 2017
f94c387
Switch to / as separator.
paddycarver Jul 27, 2017
7854535
Add logging statements, update : to / in IDs.
paddycarver Jul 27, 2017
f88e042
Test adding multiple bindings at once.
paddycarver Jul 27, 2017
ac5df40
Return a type that was needed, rename a test function.
paddycarver Jul 27, 2017
d3426d5
Don't set IDs in RMW loops.
paddycarver Jul 27, 2017
6f98217
Fix embarrassing typo in log message.
paddycarver Jul 27, 2017
4b9432d
Newlines on website source.
paddycarver Jul 27, 2017
c89429c
Newlines, but really this time.
paddycarver Jul 27, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"
"strings"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/mutexkv"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
Expand Down Expand Up @@ -106,6 +107,8 @@ func Provider() terraform.ResourceProvider {
"google_sql_user": resourceSqlUser(),
"google_project": resourceGoogleProject(),
"google_project_iam_policy": resourceGoogleProjectIamPolicy(),
"google_project_iam_binding": resourceGoogleProjectIamBinding(),
"google_project_iam_member": resourceGoogleProjectIamMember(),
"google_project_services": resourceGoogleProjectServices(),
"google_pubsub_topic": resourcePubsubTopic(),
"google_pubsub_subscription": resourcePubsubSubscription(),
Expand Down Expand Up @@ -279,6 +282,18 @@ func handleNotFoundError(err error, d *schema.ResourceData, resource string) err
return fmt.Errorf("Error reading %s: %s", resource, err)
}

func isConflictError(err error) bool {
if e, ok := err.(*googleapi.Error); ok && e.Code == 409 {
return true
} else if !ok && errwrap.ContainsType(err, &googleapi.Error{}) {
e := errwrap.GetType(err, &googleapi.Error{}).(*googleapi.Error)
if e.Code == 409 {
return true
}
}
return false
}

func linkDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
parts := strings.Split(old, "/")
if parts[len(parts)-1] == new {
Expand Down
183 changes: 183 additions & 0 deletions google/resource_google_project_iam_binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package google

import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)

func resourceGoogleProjectIamBinding() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectIamBindingCreate,
Read: resourceGoogleProjectIamBindingRead,
Update: resourceGoogleProjectIamBindingUpdate,
Delete: resourceGoogleProjectIamBindingDelete,

Schema: map[string]*schema.Schema{
"project": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"members": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceGoogleProjectIamBindingCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}

// Get the binding in the template
log.Println("[DEBUG]: Reading google_project_iam_binding")
p := getResourceIamBinding(d)
mutexKV.Lock(projectIamBindingMutexKey(pid, p.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, p.Role))

err = projectIamPolicyReadModifyWrite(d, config, pid, func(ep *cloudresourcemanager.Policy) error {
// Merge the bindings together
ep.Bindings = mergeBindings(append(ep.Bindings, p))
return nil
})
if err != nil {
return err
}
d.SetId(pid + "/" + p.Role)
return resourceGoogleProjectIamBindingRead(d, meta)
}

func resourceGoogleProjectIamBindingRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}

eBinding := getResourceIamBinding(d)

log.Println("[DEBUG]: Retrieving policy for project", pid)
p, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
log.Printf("[DEBUG]: Retrieved policy for project %q: %+v\n", pid, p)

var binding *cloudresourcemanager.Binding
for _, b := range p.Bindings {
if b.Role != eBinding.Role {
continue
}
binding = b
break
}
if binding == nil {
log.Printf("[DEBUG]: Binding for role %q not found in policy for %q, removing from state file.\n", eBinding.Role, pid)
d.SetId("")
return nil
}
d.Set("etag", p.Etag)
d.Set("members", binding.Members)
d.Set("role", binding.Role)
return nil
}

func resourceGoogleProjectIamBindingUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}

binding := getResourceIamBinding(d)
mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role))

err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
var found bool
for pos, b := range p.Bindings {
if b.Role != binding.Role {
continue
}
found = true
p.Bindings[pos] = binding
break
}
if !found {
p.Bindings = append(p.Bindings, binding)
}
return nil
})
if err != nil {
return err
}

return resourceGoogleProjectIamBindingRead(d, meta)
}

func resourceGoogleProjectIamBindingDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid, err := getProject(d, config)
if err != nil {
return err
}

binding := getResourceIamBinding(d)
mutexKV.Lock(projectIamBindingMutexKey(pid, binding.Role))
defer mutexKV.Unlock(projectIamBindingMutexKey(pid, binding.Role))

err = projectIamPolicyReadModifyWrite(d, config, pid, func(p *cloudresourcemanager.Policy) error {
toRemove := -1
for pos, b := range p.Bindings {
if b.Role != binding.Role {
continue
}
toRemove = pos
break
}
if toRemove < 0 {
log.Printf("[DEBUG]: Policy bindings for project %q did not include a binding for role %q", pid, binding.Role)
return nil
}

p.Bindings = append(p.Bindings[:toRemove], p.Bindings[toRemove+1:]...)
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to set the id to "" here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The read call does that. :)

})
if err != nil {
return err
}

return resourceGoogleProjectIamBindingRead(d, meta)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of the final read?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updates the state file with the state according to the API. In this case, confirms the API no longer has the binding in it, and removes it. If for whatever reason the delete function silently failed and didn't actually delete the binding, Terraform wouldn't lose it.

}

// Get a cloudresourcemanager.Binding from a schema.ResourceData
func getResourceIamBinding(d *schema.ResourceData) *cloudresourcemanager.Binding {
members := d.Get("members").(*schema.Set).List()
return &cloudresourcemanager.Binding{
Members: convertStringArr(members),
Role: d.Get("role").(string),
}
}

func projectIamBindingMutexKey(pid, role string) string {
return fmt.Sprintf("google-project-iam-binding-%s-%s", pid, role)
}
Loading