WIP: providers/google: Support IAM permissions for GCP projects
This change adds a data source to allow declaring IAM policies, as well as a new resource to represent an existing GCP project. The project resource may reference an IAM policy, allowing a user to set project-wide permissions.
This commit is contained in:
parent
daa360a029
commit
4cdb064e16
|
@ -13,6 +13,7 @@ import (
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
"golang.org/x/oauth2/jwt"
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/api/cloudresourcemanager/v1"
|
||||||
"google.golang.org/api/compute/v1"
|
"google.golang.org/api/compute/v1"
|
||||||
"google.golang.org/api/container/v1"
|
"google.golang.org/api/container/v1"
|
||||||
"google.golang.org/api/dns/v1"
|
"google.golang.org/api/dns/v1"
|
||||||
|
@ -28,12 +29,13 @@ type Config struct {
|
||||||
Project string
|
Project string
|
||||||
Region string
|
Region string
|
||||||
|
|
||||||
clientCompute *compute.Service
|
clientCompute *compute.Service
|
||||||
clientContainer *container.Service
|
clientContainer *container.Service
|
||||||
clientDns *dns.Service
|
clientDns *dns.Service
|
||||||
clientStorage *storage.Service
|
clientPubsub *pubsub.Service
|
||||||
clientSqlAdmin *sqladmin.Service
|
clientResourceManager *cloudresourcemanager.Service
|
||||||
clientPubsub *pubsub.Service
|
clientStorage *storage.Service
|
||||||
|
clientSqlAdmin *sqladmin.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) loadAndValidate() error {
|
func (c *Config) loadAndValidate() error {
|
||||||
|
@ -133,6 +135,13 @@ func (c *Config) loadAndValidate() error {
|
||||||
}
|
}
|
||||||
c.clientPubsub.UserAgent = userAgent
|
c.clientPubsub.UserAgent = userAgent
|
||||||
|
|
||||||
|
log.Printf("[INFO] Instatiating Google CloudResourceManager Client...")
|
||||||
|
c.clientResourceManager, err = cloudresourcemanager.New(client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.clientPubsub.UserAgent = userAgent
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"google.golang.org/api/cloudresourcemanager/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dataSourceGoogleIamPolicy() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Read: dataSourceGoogleIamPolicyRead,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"binding": {
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"role": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"members": {
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: schema.HashString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"policy": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataSourceGoogleIamPolicyMembers(d *schema.Set) []string {
|
||||||
|
var members []string
|
||||||
|
members = make([]string, d.Len())
|
||||||
|
|
||||||
|
for i, v := range d.List() {
|
||||||
|
members[i] = v.(string)
|
||||||
|
}
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataSourceGoogleIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
doc := &cloudresourcemanager.Policy{}
|
||||||
|
|
||||||
|
var bindings []*cloudresourcemanager.Binding
|
||||||
|
|
||||||
|
bindingStatements := d.Get("binding").(*schema.Set)
|
||||||
|
bindings = make([]*cloudresourcemanager.Binding, bindingStatements.Len())
|
||||||
|
doc.Bindings = bindings
|
||||||
|
|
||||||
|
for i, bindingRaw := range bindingStatements.List() {
|
||||||
|
bindingStatement := bindingRaw.(map[string]interface{})
|
||||||
|
doc.Bindings[i] = &cloudresourcemanager.Binding{
|
||||||
|
Role: bindingStatement["role"].(string),
|
||||||
|
Members: dataSourceGoogleIamPolicyMembers(bindingStatement["members"].(*schema.Set)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonDoc, err := json.MarshalIndent(doc, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
// should never happen if the above code is correct
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jsonString := string(jsonDoc)
|
||||||
|
|
||||||
|
d.Set("policy", jsonString)
|
||||||
|
d.SetId(strconv.Itoa(hashcode.String(jsonString)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -56,6 +56,10 @@ func Provider() terraform.ResourceProvider {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
DataSourcesMap: map[string]*schema.Resource{
|
||||||
|
"google_iam_policy": dataSourceGoogleIamPolicy(),
|
||||||
|
},
|
||||||
|
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"google_compute_autoscaler": resourceComputeAutoscaler(),
|
"google_compute_autoscaler": resourceComputeAutoscaler(),
|
||||||
"google_compute_address": resourceComputeAddress(),
|
"google_compute_address": resourceComputeAddress(),
|
||||||
|
@ -89,6 +93,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"google_sql_database": resourceSqlDatabase(),
|
"google_sql_database": resourceSqlDatabase(),
|
||||||
"google_sql_database_instance": resourceSqlDatabaseInstance(),
|
"google_sql_database_instance": resourceSqlDatabaseInstance(),
|
||||||
"google_sql_user": resourceSqlUser(),
|
"google_sql_user": resourceSqlUser(),
|
||||||
|
"google_project": resourceGoogleProject(),
|
||||||
"google_pubsub_topic": resourcePubsubTopic(),
|
"google_pubsub_topic": resourcePubsubTopic(),
|
||||||
"google_pubsub_subscription": resourcePubsubSubscription(),
|
"google_pubsub_subscription": resourcePubsubSubscription(),
|
||||||
"google_storage_bucket": resourceStorageBucket(),
|
"google_storage_bucket": resourceStorageBucket(),
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"google.golang.org/api/cloudresourcemanager/v1"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceGoogleProject() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceGoogleProjectCreate,
|
||||||
|
Read: resourceGoogleProjectRead,
|
||||||
|
Update: resourceGoogleProjectUpdate,
|
||||||
|
Delete: resourceGoogleProjectDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"project": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"policy": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"number": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
project, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(project)
|
||||||
|
if err := resourceGoogleProjectRead(d, meta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the IAM policy if it is set
|
||||||
|
if pString, ok := d.GetOk("policy"); ok {
|
||||||
|
// The policy string is just a marshaled cloudresourcemanager.Policy.
|
||||||
|
// Unmarshal it to a struct.
|
||||||
|
var policy cloudresourcemanager.Policy
|
||||||
|
if err = json.Unmarshal([]byte(pString.(string)), &policy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve existing IAM policy from project. This will be merged
|
||||||
|
// with the policy defined here.
|
||||||
|
// TODO(evanbrown): Add an 'authoritative' flag that allows policy
|
||||||
|
// in manifest to overwrite existing policy.
|
||||||
|
p, err := getProjectIamPolicy(project, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings)
|
||||||
|
|
||||||
|
// Merge the existing policy bindings with those defined in this manifest.
|
||||||
|
p.Bindings = mergeBindings(append(p.Bindings, policy.Bindings...))
|
||||||
|
|
||||||
|
// Apply the merged policy
|
||||||
|
log.Printf("[DEBUG] Setting new policy for project: %#v", p)
|
||||||
|
_, err = config.clientResourceManager.Projects.SetIamPolicy(project,
|
||||||
|
&cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error applying IAM policy for project %q: %s", project, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
project, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm the project exists.
|
||||||
|
// TODO(evanbrown): Support project creation
|
||||||
|
p, err := config.clientResourceManager.Projects.Get(project).Do()
|
||||||
|
if err != nil {
|
||||||
|
if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound {
|
||||||
|
return fmt.Errorf("Project %q does not exist. The Google provider does not currently support new project creation.", project)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Error checking project %q: %s", project, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("number", strconv.FormatInt(int64(p.ProjectNumber), 10))
|
||||||
|
d.Set("name", p.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
project, err := getProject(d, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy has changed
|
||||||
|
if ok := d.HasChange("policy"); ok {
|
||||||
|
// The policy string is just a marshaled cloudresourcemanager.Policy.
|
||||||
|
// Unmarshal it to a struct that contains the old and new policies
|
||||||
|
oldPString, newPString := d.GetChange("policy")
|
||||||
|
var oldPolicy, newPolicy cloudresourcemanager.Policy
|
||||||
|
if err = json.Unmarshal([]byte(newPString.(string)), &newPolicy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal([]byte(oldPString.(string)), &oldPolicy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find any Roles and Members that were removed (i.e., those that are present
|
||||||
|
// in the old but absent in the new
|
||||||
|
oldMap := rolesToMembersMap(oldPolicy.Bindings)
|
||||||
|
newMap := rolesToMembersMap(newPolicy.Bindings)
|
||||||
|
deleted := make(map[string]string)
|
||||||
|
|
||||||
|
// Get each role and its associated members in the old state
|
||||||
|
for role, members := range oldMap {
|
||||||
|
// The role exists in the new state
|
||||||
|
if _, ok := newMap[role]; ok {
|
||||||
|
// Check each memeber
|
||||||
|
for member, _ := range members {
|
||||||
|
// Member does not exist in new state, so it was deleted
|
||||||
|
if _, ok = newMap[role][member]; !ok {
|
||||||
|
deleted[role] = member
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This indicates an entire role was deleted. Mark all members
|
||||||
|
// for delete.
|
||||||
|
for member, _ := range members {
|
||||||
|
deleted[role] = member
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Roles and Members to be deleted: %#v", deleted)
|
||||||
|
|
||||||
|
// Retrieve existing IAM policy from project. This will be merged
|
||||||
|
// with the policy in the current state
|
||||||
|
// TODO(evanbrown): Add an 'authoritative' flag that allows policy
|
||||||
|
// in manifest to overwrite existing policy.
|
||||||
|
p, err := getProjectIamPolicy(project, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings)
|
||||||
|
|
||||||
|
// Merge existing policy with policy in the current state
|
||||||
|
log.Printf("[DEBUG] Merging new bindings from project: %#v", newPolicy.Bindings)
|
||||||
|
mergedBindings := mergeBindings(append(p.Bindings, newPolicy.Bindings...))
|
||||||
|
|
||||||
|
// Remove any roles and members that were explicitly deleted
|
||||||
|
mergedBindingsMap := rolesToMembersMap(mergedBindings)
|
||||||
|
for role, member := range deleted {
|
||||||
|
delete(mergedBindingsMap[role], member)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Bindings = rolesToMembersBinding(mergedBindingsMap)
|
||||||
|
log.Printf("[DEBUG] Setting new policy for project: %#v", p)
|
||||||
|
|
||||||
|
dump, _ := json.MarshalIndent(p.Bindings, " ", " ")
|
||||||
|
log.Printf(string(dump))
|
||||||
|
_, err = config.clientResourceManager.Projects.SetIamPolicy(project,
|
||||||
|
&cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error applying IAM policy for project %q: %s", project, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceGoogleProjectDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) {
|
||||||
|
p, err := config.clientResourceManager.Projects.GetIamPolicy(project,
|
||||||
|
&cloudresourcemanager.GetIamPolicyRequest{}).Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error retrieving IAM policy for project %q: %s", project, err)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a map of roles->members to a list of Binding
|
||||||
|
func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager.Binding {
|
||||||
|
bindings := make([]*cloudresourcemanager.Binding, 0)
|
||||||
|
for role, members := range m {
|
||||||
|
b := cloudresourcemanager.Binding{
|
||||||
|
Role: role,
|
||||||
|
Members: make([]string, 0),
|
||||||
|
}
|
||||||
|
for m, _ := range members {
|
||||||
|
b.Members = append(b.Members, m)
|
||||||
|
}
|
||||||
|
bindings = append(bindings, &b)
|
||||||
|
}
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map a role to a map of members, allowing easy merging of multiple bindings.
|
||||||
|
func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[string]bool {
|
||||||
|
bm := make(map[string]map[string]bool)
|
||||||
|
// Get each binding
|
||||||
|
for _, b := range bindings {
|
||||||
|
// Initialize members map
|
||||||
|
if _, ok := bm[b.Role]; !ok {
|
||||||
|
bm[b.Role] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
// Get each member (user/principal) for the binding
|
||||||
|
for _, m := range b.Members {
|
||||||
|
// Add the member
|
||||||
|
bm[b.Role][m] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge multiple Bindings such that Bindings with the same Role result in
|
||||||
|
// a single Binding with combined Members
|
||||||
|
func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
|
||||||
|
bm := rolesToMembersMap(bindings)
|
||||||
|
rb := make([]*cloudresourcemanager.Binding, 0)
|
||||||
|
|
||||||
|
for role, members := range bm {
|
||||||
|
var b cloudresourcemanager.Binding
|
||||||
|
b.Role = role
|
||||||
|
b.Members = make([]string, 0)
|
||||||
|
for m, _ := range members {
|
||||||
|
b.Members = append(b.Members, m)
|
||||||
|
}
|
||||||
|
rb = append(rb, &b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rb
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/api/cloudresourcemanager/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Binding []cloudresourcemanager.Binding
|
||||||
|
|
||||||
|
func (b Binding) Len() int {
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Binding) Swap(i, j int) {
|
||||||
|
b[i], b[j] = b[j], b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Binding) Less(i, j int) bool {
|
||||||
|
return b[i].Role < b[j].Role
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIamMapRolesToMembers(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
input []cloudresourcemanager.Binding
|
||||||
|
expect map[string]map[string]bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: map[string]map[string]bool{
|
||||||
|
"role-1": map[string]bool{
|
||||||
|
"member-1": true,
|
||||||
|
"member-2": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: map[string]map[string]bool{
|
||||||
|
"role-1": map[string]bool{
|
||||||
|
"member-1": true,
|
||||||
|
"member-2": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: map[string]map[string]bool{
|
||||||
|
"role-1": map[string]bool{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
got := mapRolesToMembers(test.input)
|
||||||
|
if !reflect.DeepEqual(got, test.expect) {
|
||||||
|
t.Errorf("got %+v, expected %+v", got, test.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIamMergeBindings(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
input []cloudresourcemanager.Binding
|
||||||
|
expect []cloudresourcemanager.Binding
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
"member-3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-3",
|
||||||
|
"member-4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-2",
|
||||||
|
"member-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-2",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-3",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-2",
|
||||||
|
Members: []string{
|
||||||
|
"member-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: []cloudresourcemanager.Binding{
|
||||||
|
{
|
||||||
|
Role: "role-1",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
"member-3",
|
||||||
|
"member-4",
|
||||||
|
"member-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-2",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
"member-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "role-3",
|
||||||
|
Members: []string{
|
||||||
|
"member-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
got := mergeBindings(test.input)
|
||||||
|
sort.Sort(Binding(got))
|
||||||
|
for i, _ := range got {
|
||||||
|
sort.Strings(got[i].Members)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.expect) {
|
||||||
|
t.Errorf("\ngot %+v\nexpected %+v", got, test.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue