diff --git a/.gitignore b/.gitignore index bd0d916d0f70067196dc18173c227bd9ebd2696b..426a8c1a79d406b8d12c270caa7bbf4f354ffa3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ example/ .idea/ build/terraform* +**/*.swp # Binaries for programs and plugins *.exe diff --git a/docs/r/vted_group.md b/docs/r/vted_group.md index ec9fc88e340e742f1187814554fcc7457a28eaf8..15800a12f6d1c6d39ab3e81e9cb4afd717ebf6c2 100644 --- a/docs/r/vted_group.md +++ b/docs/r/vted_group.md @@ -12,9 +12,15 @@ resource "vted_group" "my_group" { suppress_display = true suppress_members = true - admins = ["mikesir"] + admins { + people = ["mikesir"] + services = ["gateway"] + } contacts = ["mikesir", "ceharris"] - managers = ["mikesir", "ceharris"] + managers { + people = ["mikesir", "ceharris"] + services = ["gateway"] + } members { people = ["mikesir", "ceharris"] @@ -29,9 +35,9 @@ The following arguments are supported: - `name` - (Required) The name of the group. - `expiration_date` - (Optional) The expiration date of the group. Supported formats are RFC3339 and `YYYY-MM-DD`. Note that when using `YYYY-MM-DD`, the time is set to 00:00 UTC, which might cause date display differences. -- `admins` - (Required) A collection of PIDs that are allowed to administer the group. +- `admins` - (Required) A map of the `people` (PIDs) and `services` (uusids) that are allowed to administer the group. **Note**: The TF provider service account is always a service admin for the group. - `contacts` - (Required) A collection of PIDs that are contacts for the group. -- `managers` - (Optional) A collection of PIDs that are allowed to manage the group membership. +- `managers` - (Optional) A map of the `people` (PIDs) and `services` (uusids) that are allowed to manage the group membership. - `members` - (Optional) A map of the `people` (PIDs) and `groups` (uusids) that are members of this group - `suppress_display` - (Optional) Boolean, defaults to true. Is this group visible in public listings? - `suppress_members` - (Optional) Boolean, defaults to true. Is this group's membership visible in public queries? diff --git a/vted/data_source_vted_group.go b/vted/data_source_vted_group.go index 758163aca818c9a011060c4fc6cc62ed88262154..a28105ac92f2164ea612e193e30d1c86c8187e8a 100644 --- a/vted/data_source_vted_group.go +++ b/vted/data_source_vted_group.go @@ -52,8 +52,8 @@ func dataSourceVtEdGroup() *schema.Resource { }, "managers": { Type: schema.TypeList, - Description: "The contacts of the group", - MinItems: 1, + Description: "The managers of the group", + MinItems: 0, Computed: true, Elem: PersonResource(), }, diff --git a/vted/resource_vted_group.go b/vted/resource_vted_group.go index ca831daf52d1fd5b696e6b566c13c59f1ff81b24..75430738340ec5d9102465000908829cba2c9f47 100644 --- a/vted/resource_vted_group.go +++ b/vted/resource_vted_group.go @@ -58,12 +58,28 @@ func resourceVtEdGroup() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "admins": &schema.Schema{ - Type: schema.TypeSet, - Description: "PIDs of administrators for the group", + Type: schema.TypeList, + Description: "Administrators for the group", Required: true, MinItems: 1, - Set: schema.HashString, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "people": { + Type: schema.TypeSet, + Description: "PIDs of the person administrators of the group", + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "services": { + Type: schema.TypeSet, + Description: "UUSIDs of the service administrators of the group", + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, }, "contacts": &schema.Schema{ Type: schema.TypeSet, @@ -74,11 +90,27 @@ func resourceVtEdGroup() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "managers": &schema.Schema{ - Type: schema.TypeSet, - Description: "PIDs of managers for the group", + Type: schema.TypeList, + Description: "Managers for the group", Optional: true, - Set: schema.HashString, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "people": { + Type: schema.TypeSet, + Description: "PIDs of the person managers of the group", + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "services": { + Type: schema.TypeSet, + Description: "UUSIDs of the service managers of the group", + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, }, "members": &schema.Schema{ Type: schema.TypeList, @@ -113,10 +145,12 @@ func resourceVtEdGroupCreate(d *schema.ResourceData, meta interface{}) error { groupName := d.Get("name").(string) - err := validateAdminsAndContacts(d.Get("admins").(*schema.Set), d.Get("contacts").(*schema.Set)) + err := validateAdminsAndContacts(d.Get("admins").([]interface{}), d.Get("contacts").(*schema.Set)) if err != nil { return err } + // provider service ID always gets added as a service admin for the group + d.Get("admins").([]interface{})[0].(map[string]interface{})["services"].(*schema.Set).Add(providerConfig.serviceId) _, err = edClient.Post("/groups", groupConfigToFormBody(d)) if err != nil { @@ -130,12 +164,12 @@ func resourceVtEdGroupCreate(d *schema.ResourceData, meta interface{}) error { return err } - err = syncReplicationTargets(groupName, d.Get("replication_targets").(*schema.Set), nil, edClient) + err = syncManagersAdmins(groupName, "managers", d.Get("managers").([]interface{}), nil, edClient) if err != nil { return err } - err = syncRole(groupName, "managers", d.Get("managers").(*schema.Set), nil, edClient, "person") + err = syncManagersAdmins(groupName, "administrators", d.Get("admins").([]interface{}), nil, edClient) if err != nil { return err } @@ -145,9 +179,51 @@ func resourceVtEdGroupCreate(d *schema.ResourceData, meta interface{}) error { return err } + err = syncReplicationTargets(groupName, d.Get("replication_targets").(*schema.Set), nil, edClient) + if err != nil { + return err + } + return resourceVtEdGroupRead(d, meta) } +func syncManagersAdmins(groupName string, roleName string, newCollection []interface{}, oldCollection []interface{}, client *EdClient) error { + var newPeopleSet *schema.Set + var oldPeopleSet *schema.Set + var newServicesSet *schema.Set + var oldServicesSet *schema.Set + + if len(newCollection) > 0 && newCollection[0] != nil { + newMembers := newCollection[0].(map[string]interface{}) + newPeopleSet = newMembers["people"].(*schema.Set) + newServicesSet = newMembers["services"].(*schema.Set) + } else { + newPeopleSet = schema.NewSet(schema.HashString, []interface{}{}) + newServicesSet = schema.NewSet(schema.HashString, []interface{}{}) + } + + if len(oldCollection) > 0 { + oldMembers := oldCollection[0].(map[string]interface{}) + oldPeopleSet = oldMembers["people"].(*schema.Set) + oldServicesSet = oldMembers["services"].(*schema.Set) + } else { + oldPeopleSet = schema.NewSet(schema.HashString, []interface{}{}) + oldServicesSet = schema.NewSet(schema.HashString, []interface{}{}) + } + + err := syncRole(groupName, roleName, newPeopleSet, oldPeopleSet, client, "person") + if err != nil { + return err + } + + err = syncRole(groupName, roleName, newServicesSet, oldServicesSet, client, "service") + if err != nil { + return err + } + + return nil +} + func syncMembers(groupName string, newMemberCollection []interface{}, oldMemberCollection []interface{}, client *EdClient) error { var newPeopleSet *schema.Set var oldPeopleSet *schema.Set @@ -198,10 +274,10 @@ func resourceVtEdGroupRead(d *schema.ResourceData, meta interface{}) error { d.Set("creation_date", groupInfo.Get("creationDate").String()) d.Set("suppress_members", groupInfo.Get("suppressMembers").Bool()) d.Set("suppress_display", groupInfo.Get("suppressDisplay").Bool()) + d.Set("admins", convertManagersAdmins(groupInfo.Get("administrators").Array())) d.Set("replication_targets", groupInfo.Get("targets").Array()) - d.Set("admins", convertFlattenedPeople(groupInfo.Get("administrators").Array())) d.Set("contacts", convertFlattenedPeople(groupInfo.Get("contacts").Array())) - d.Set("managers", convertFlattenedPeople(groupInfo.Get("managers").Array())) + d.Set("managers", convertManagersAdmins(groupInfo.Get("managers").Array())) d.Set("viewers", convertViewers(groupInfo.Get("viewers").Array())) d.Set("members", convertMembers(groupInfo.Get("members").Array())) @@ -227,9 +303,12 @@ func resourceVtEdGroupUpdate(d *schema.ResourceData, meta interface{}) error { } } + // provider service ID always gets added as a service admin for the group + d.Get("admins").([]interface{})[0].(map[string]interface{})["services"].(*schema.Set).Add(providerConfig.serviceId) + if d.HasChange("admins") { oldValue, newValue := d.GetChange("admins") - err := syncRole(groupName, "administrators", newValue.(*schema.Set), oldValue.(*schema.Set), edClient, "person") + err := syncManagersAdmins(groupName, "administrators", newValue.([]interface{}), oldValue.([]interface{}), edClient) if err != nil { return err } @@ -245,7 +324,7 @@ func resourceVtEdGroupUpdate(d *schema.ResourceData, meta interface{}) error { if d.HasChange("managers") { oldValue, newValue := d.GetChange("managers") - err := syncRole(groupName, "managers", newValue.(*schema.Set), oldValue.(*schema.Set), edClient, "person") + err := syncManagersAdmins(groupName, "managers", newValue.([]interface{}), oldValue.([]interface{}), edClient) if err != nil { return err } @@ -301,18 +380,29 @@ func suppressTimeChange() func(k string, old string, new string, d *schema.Resou } } -func validateAdminsAndContacts(admins *schema.Set, contacts *schema.Set) error { +func validateAdminsAndContacts(admins []interface{}, contacts *schema.Set) error { + var adminPeopleSet *schema.Set + var adminServicesSet *schema.Set + adminsAndContacts := make(map[string]bool) - for _, n := range admins.List() { - adminsAndContacts[n.(string)] = true + if len(admins) > 0 && admins[0] != nil { + adminMap := admins[0].(map[string]interface{}) + adminPeopleSet = adminMap["people"].(*schema.Set) + adminServicesSet = adminMap["services"].(*schema.Set) + for _, n := range adminPeopleSet.List() { + adminsAndContacts[n.(string)] = true + } + for _, n := range adminServicesSet.List() { + adminsAndContacts[n.(string)] = true + } } for _, n := range contacts.List() { adminsAndContacts[n.(string)] = true } - if len(adminsAndContacts) == 1 { + if len(adminsAndContacts) <= 1 { return errors.New("two distinct users are required for admins and contacts") } @@ -324,7 +414,9 @@ func groupConfigToFormBody(d *schema.ResourceData) string { formDataObj := url.Values{} formDataObj.Set("uugid", d.Get("name").(string)) - for _, name := range d.Get("admins").(*schema.Set).List() { + // Creation only supports people admins + adminMap := d.Get("admins").([]interface{})[0].(map[string]interface{}) + for _, name := range adminMap["people"].(*schema.Set).List() { formDataObj.Add("administrator", name.(string)) } @@ -469,6 +561,29 @@ func parseDateString(date string) (time.Time, error) { return parsedDate, nil } +func convertManagersAdmins(members []gjson.Result) []map[string]*schema.Set { + people := schema.NewSet(schema.HashString, []interface{}{}) + services := schema.NewSet(schema.HashString, []interface{}{}) + + for _, member := range members { + memberKind := member.Get("kind").String() + if memberKind == "person" { + people.Add(member.Get("pid").String()) + } else if memberKind == "service" { + services.Add(member.Get("uusid").String()) + } + } + + values := map[string]*schema.Set{ + "people": people, + "services": services, + } + + return []map[string]*schema.Set{ + values, + } +} + func convertMembers(members []gjson.Result) []map[string]*schema.Set { people := schema.NewSet(schema.HashString, []interface{}{}) groups := schema.NewSet(schema.HashString, []interface{}{})