From 067a152d4085a2eb49a014f08ef4893fc0635daa Mon Sep 17 00:00:00 2001 From: carnellj-genesys <109529583+carnellj-genesys@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:43:29 -0400 Subject: [PATCH] Devtooling 485 fix group delete v2 (#927) * DEVTOOLING-485: Refactored group into package and then fixed bug * DEVTOOLING-485: Added caching * DEVTOOLING-485: Added caching * DEVTOOLING-485: Fixing the group delete --- .../inboundcall_flow_example.yaml | 3 +- .../inboundcall_flow_example2.yaml | 41 +- .../inboundcall_flow_example3.yaml | 40 +- .../trigger_workflow_example.yaml | 2 +- .../data_source_genesyscloud_group.go | 37 +- .../data_source_genesyscloud_group_test.go | 6 +- .../group/genesyscloud_group_init_test.go | 56 ++ .../group/genesyscloud_group_proxy.go | 185 ++++++ .../group/resource_genesyscloud_group.go | 333 ++++++++++ .../resource_genesyscloud_group_schema.go | 139 +++++ .../resource_genesyscloud_group_test.go | 77 ++- .../resource_genesyscloud_group_utils.go | 162 +++++ .../genesyscloud_group_roles_init_test.go | 3 +- ...esource_genesyscloud_group_roles_schema.go | 31 +- .../resource_genesyscloud_group_roles_test.go | 27 +- .../genesyscloud_integration_init_test.go | 3 +- .../resource_genesyscloud_integration_test.go | 45 +- ...d_recording_media_retention_policy_test.go | 2 +- ...d_recording_media_retention_policy_test.go | 18 +- genesyscloud/resource_genesyscloud_group.go | 582 ------------------ genesyscloud/resource_genesyscloud_init.go | 3 - .../resource_genesyscloud_init_test.go | 5 +- ...esyscloud_orgauthorization_pairing_test.go | 67 +- ...esource_genesyscloud_routing_queue_test.go | 17 +- genesyscloud/resource_genesyscloud_user.go | 9 - .../resource_genesyscloud_user_test.go | 56 +- ...phony_providers_edges_trunkbasesettings.go | 1 + .../tfexporter/tf_exporter_resource_test.go | 5 +- go.sum | 23 + main.go | 2 + 30 files changed, 1220 insertions(+), 760 deletions(-) rename genesyscloud/{ => group}/data_source_genesyscloud_group.go (50%) rename genesyscloud/{ => group}/data_source_genesyscloud_group_test.go (94%) create mode 100644 genesyscloud/group/genesyscloud_group_init_test.go create mode 100644 genesyscloud/group/genesyscloud_group_proxy.go create mode 100644 genesyscloud/group/resource_genesyscloud_group.go create mode 100644 genesyscloud/group/resource_genesyscloud_group_schema.go rename genesyscloud/{ => group}/resource_genesyscloud_group_test.go (85%) create mode 100644 genesyscloud/group/resource_genesyscloud_group_utils.go delete mode 100644 genesyscloud/resource_genesyscloud_group.go diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml index 017cf8f5f..4e44fd573 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml @@ -1,5 +1,6 @@ inboundCall: - name: Terraform Flow Test-b9eac7f8-cbf3-4a7f-95fc-f088c95deb6c + name: Terraform Flow Test-e74202b4-a83b-4247-a7d1-b1e94e752344 + description: test description 1 defaultLanguage: en-us startUpRef: ./menus/menu[mainMenu] initialGreeting: diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml index 683bc0e74..b8ad0ebab 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml @@ -1,24 +1,17 @@ -inboundEmail: - name: Terraform Flow Test-4598b11f-bbf0-4533-96bc-cc271ac1d5a3 - division: New Home - startUpRef: "/inboundEmail/states/state[Initial State_10]" - defaultLanguage: en-us - supportedLanguages: - en-us: - defaultLanguageSkill: - noValue: true - settingsInboundEmailHandling: - emailHandling: - disconnect: - none: true - settingsErrorHandling: - errorHandling: - disconnect: - none: true - states: - - state: - name: Initial State - refId: Initial State_10 - actions: - - disconnect: - name: Disconnect +inboundCall: + name: Terraform Flow Test-e74202b4-a83b-4247-a7d1-b1e94e752344 + description: test description 2 + defaultLanguage: en-us + startUpRef: ./menus/menu[mainMenu] + initialGreeting: + tts: Archy says hi!!!!! + menus: + - menu: + name: Main Menu + audio: + tts: You are at the Main Menu, press 9 to disconnect. + refId: mainMenu + choices: + - menuDisconnect: + name: Disconnect + dtmf: digit_9 \ No newline at end of file diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml index 3ecaf7a4a..64dcd1f1b 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml @@ -1,16 +1,24 @@ -inboundCall: - name: Terraform Flow Test-09cb1249-e934-4fb7-b35b-a96940f494c7 - defaultLanguage: en-us - startUpRef: ./menus/menu[mainMenu] - initialGreeting: - tts: Archy says hi!!!!! - menus: - - menu: - name: Main Menu - audio: - tts: You are at the Main Menu, press 9 to disconnect. - refId: mainMenu - choices: - - menuDisconnect: - name: Disconnect - dtmf: digit_9 \ No newline at end of file +inboundEmail: + name: Terraform Flow Test-e74202b4-a83b-4247-a7d1-b1e94e752344 + description: test description 1 + startUpRef: "/inboundEmail/states/state[Initial State_10]" + defaultLanguage: en-us + supportedLanguages: + en-us: + defaultLanguageSkill: + noValue: true + settingsInboundEmailHandling: + emailHandling: + disconnect: + none: true + settingsErrorHandling: + errorHandling: + disconnect: + none: true + states: + - state: + name: Initial State + refId: Initial State_10 + actions: + - disconnect: + name: Disconnect diff --git a/examples/resources/genesyscloud_processautomation_trigger/trigger_workflow_example.yaml b/examples/resources/genesyscloud_processautomation_trigger/trigger_workflow_example.yaml index a24fb916d..a65e9fcc9 100644 --- a/examples/resources/genesyscloud_processautomation_trigger/trigger_workflow_example.yaml +++ b/examples/resources/genesyscloud_processautomation_trigger/trigger_workflow_example.yaml @@ -1,5 +1,5 @@ workflow: - name: terraform-provider-test-97450725-2830-42de-a230-5d763ecce39f + name: terraform-provider-test-72a265af-6a5d-4843-85f9-49a97ec5b77c division: New Home startUpRef: "/workflow/states/state[Initial State_10]" defaultLanguage: en-us diff --git a/genesyscloud/data_source_genesyscloud_group.go b/genesyscloud/group/data_source_genesyscloud_group.go similarity index 50% rename from genesyscloud/data_source_genesyscloud_group.go rename to genesyscloud/group/data_source_genesyscloud_group.go index 4126d9417..3eef4063f 100644 --- a/genesyscloud/data_source_genesyscloud_group.go +++ b/genesyscloud/group/data_source_genesyscloud_group.go @@ -1,4 +1,4 @@ -package genesyscloud +package group import ( "context" @@ -11,47 +11,26 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2" ) -func DataSourceGroup() *schema.Resource { - return &schema.Resource{ - Description: "Data source for Genesys Cloud Groups. Select a group by name.", - ReadContext: provider.ReadWithPooledClient(dataSourceGroupRead), - Schema: map[string]*schema.Schema{ - "name": { - Description: "Group name.", - Type: schema.TypeString, - Required: true, - }, - }, - } -} - func dataSourceGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sdkConfig := m.(*provider.ProviderMeta).ClientConfig - groupsAPI := platformclientv2.NewGroupsApiWithConfig(sdkConfig) + gp := getGroupProxy(sdkConfig) - exactSearchType := "EXACT" - nameField := "name" nameStr := d.Get("name").(string) - searchCriteria := platformclientv2.Groupsearchcriteria{ - VarType: &exactSearchType, - Value: &nameStr, - Fields: &[]string{nameField}, - } - return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - groups, _, getErr := groupsAPI.PostGroupsSearch(platformclientv2.Groupsearchrequest{ - Query: &[]platformclientv2.Groupsearchcriteria{searchCriteria}, - }) + groups, _, getErr := gp.getGroupsByName(ctx, nameStr) if getErr != nil { return retry.NonRetryableError(fmt.Errorf("Error requesting group %s: %s", nameStr, getErr)) } + if *groups.Total > 1 { + return retry.NonRetryableError(fmt.Errorf("Multiple groups found with name %s ", nameStr)) + } + if *groups.Total == 0 { - return retry.RetryableError(fmt.Errorf("No groups found with search criteria %v ", searchCriteria)) + return retry.RetryableError(fmt.Errorf("No groups found with name %s ", nameStr)) } // Select first group in the list diff --git a/genesyscloud/data_source_genesyscloud_group_test.go b/genesyscloud/group/data_source_genesyscloud_group_test.go similarity index 94% rename from genesyscloud/data_source_genesyscloud_group_test.go rename to genesyscloud/group/data_source_genesyscloud_group_test.go index fede7ea72..90dc1ba91 100644 --- a/genesyscloud/data_source_genesyscloud_group_test.go +++ b/genesyscloud/group/data_source_genesyscloud_group_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package group import ( "fmt" @@ -26,8 +26,8 @@ func TestAccDataSourceGroup(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + - generateGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + + GenerateGroupResource( groupResource, groupName, util.NullValue, // No description diff --git a/genesyscloud/group/genesyscloud_group_init_test.go b/genesyscloud/group/genesyscloud_group_init_test.go new file mode 100644 index 000000000..30115053b --- /dev/null +++ b/genesyscloud/group/genesyscloud_group_init_test.go @@ -0,0 +1,56 @@ +package group + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "sync" + "terraform-provider-genesyscloud/genesyscloud" + "testing" +) + +var providerDataSources map[string]*schema.Resource + +// providerResources holds a map of all registered resources +var providerResources map[string]*schema.Resource + +type registerTestInstance struct { + resourceMapMutex sync.RWMutex + datasourceMapMutex sync.RWMutex +} + +// registerTestResources registers all resources used in the tests +func (r *registerTestInstance) registerTestResources() { + r.resourceMapMutex.Lock() + defer r.resourceMapMutex.Unlock() + + providerResources[resourceName] = ResourceGroup() + providerResources["genesyscloud_user"] = genesyscloud.ResourceUser() + +} + +// registerTestDataSources registers all data sources used in the tests. +func (r *registerTestInstance) registerTestDataSources() { + r.datasourceMapMutex.Lock() + defer r.datasourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceGroup() +} + +// initTestResources initializes all test resources and data sources. +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() + regInstance.registerTestDataSources() +} + +// TestMain is a "setup" function called by the testing framework when run the test +func TestMain(m *testing.M) { + // Run setup function before starting the test suite for the flow_outcome package + initTestResources() + + // Run the test suite for the flow_outcome package + m.Run() +} diff --git a/genesyscloud/group/genesyscloud_group_proxy.go b/genesyscloud/group/genesyscloud_group_proxy.go new file mode 100644 index 000000000..6e496637a --- /dev/null +++ b/genesyscloud/group/genesyscloud_group_proxy.go @@ -0,0 +1,185 @@ +package group + +import ( + "context" + "fmt" + "github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" +) + +var internalProxy *groupProxy + +type createGroupFunc func(ctx context.Context, p *groupProxy, group *platformclientv2.Groupcreate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) +type getAllGroupFunc func(ctx context.Context, p *groupProxy) (*[]platformclientv2.Group, *platformclientv2.APIResponse, error) +type updateGroupFunc func(ctx context.Context, p *groupProxy, id string, group *platformclientv2.Groupupdate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) +type getGroupByIdFunc func(ctx context.Context, p *groupProxy, id string) (*platformclientv2.Group, *platformclientv2.APIResponse, error) +type addGroupMembersFunc func(ctx context.Context, p *groupProxy, id string, members *platformclientv2.Groupmembersupdate) (*interface{}, *platformclientv2.APIResponse, error) +type deleteGroupMembersFunc func(ctx context.Context, p *groupProxy, id string, members string) (*interface{}, *platformclientv2.APIResponse, error) +type getGroupMembersFunc func(ctx context.Context, p *groupProxy, id string) (*[]string, *platformclientv2.APIResponse, error) +type getGroupByNameFunc func(ctx context.Context, p *groupProxy, name string) (*platformclientv2.Groupssearchresponse, *platformclientv2.APIResponse, error) +type deleteGroupFunc func(ctx context.Context, p *groupProxy, id string) (*platformclientv2.APIResponse, error) + +type groupProxy struct { + clientConfig *platformclientv2.Configuration + groupsApi *platformclientv2.GroupsApi + createGroupAttr createGroupFunc + getAllGroupAttr getAllGroupFunc + updateGroupAttr updateGroupFunc + deleteGroupAttr deleteGroupFunc + getGroupByNameAttr getGroupByNameFunc + getGroupByIdAttr getGroupByIdFunc + addGroupMembersAttr addGroupMembersFunc + deleteGroupMembersAttr deleteGroupMembersFunc + getGroupMembersAttr getGroupMembersFunc + groupCache rc.CacheInterface[platformclientv2.Group] +} + +func newGroupProxy(clientConfig *platformclientv2.Configuration) *groupProxy { + api := platformclientv2.NewGroupsApiWithConfig(clientConfig) + groupCache := rc.NewResourceCache[platformclientv2.Group]() + return &groupProxy{ + clientConfig: clientConfig, + groupsApi: api, + createGroupAttr: createGroupFn, + getAllGroupAttr: getAllGroupFn, + updateGroupAttr: updateGroupFn, + deleteGroupAttr: deleteGroupFn, + getGroupByNameAttr: getGroupByNameFn, + getGroupByIdAttr: getGroupByIdFn, + addGroupMembersAttr: addGroupMembersFn, + deleteGroupMembersAttr: deleteGroupMembersFn, + getGroupMembersAttr: getGroupMembersFn, + groupCache: groupCache, + } +} + +func getGroupProxy(clientConfig *platformclientv2.Configuration) *groupProxy { + if internalProxy == nil { + internalProxy = newGroupProxy(clientConfig) + } + + return internalProxy +} + +func (p *groupProxy) createGroup(ctx context.Context, group *platformclientv2.Groupcreate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) { + return p.createGroupAttr(ctx, p, group) +} + +func (p *groupProxy) updateGroup(ctx context.Context, id string, group *platformclientv2.Groupupdate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) { + return p.updateGroupAttr(ctx, p, id, group) +} + +func (p *groupProxy) deleteGroup(ctx context.Context, id string) (*platformclientv2.APIResponse, error) { + return p.deleteGroupAttr(ctx, p, id) +} + +func (p *groupProxy) getAllGroups(ctx context.Context) (*[]platformclientv2.Group, *platformclientv2.APIResponse, error) { + return p.getAllGroupAttr(ctx, p) +} + +func (p *groupProxy) getGroupById(ctx context.Context, id string) (*platformclientv2.Group, *platformclientv2.APIResponse, error) { + return p.getGroupByIdAttr(ctx, p, id) +} + +func (p *groupProxy) addGroupMembers(ctx context.Context, id string, members *platformclientv2.Groupmembersupdate) (*interface{}, *platformclientv2.APIResponse, error) { + return p.addGroupMembersAttr(ctx, p, id, members) +} + +func (p *groupProxy) deleteGroupMembers(ctx context.Context, id string, members string) (*interface{}, *platformclientv2.APIResponse, error) { + return p.deleteGroupMembersAttr(ctx, p, id, members) +} + +func (p *groupProxy) getGroupMembers(ctx context.Context, id string) (*[]string, *platformclientv2.APIResponse, error) { + return p.getGroupMembersAttr(ctx, p, id) +} + +func (p *groupProxy) getGroupsByName(ctx context.Context, name string) (*platformclientv2.Groupssearchresponse, *platformclientv2.APIResponse, error) { + return p.getGroupByNameAttr(ctx, p, name) +} + +func createGroupFn(ctx context.Context, p *groupProxy, group *platformclientv2.Groupcreate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) { + return p.groupsApi.PostGroups(*group) +} + +func updateGroupFn(ctx context.Context, p *groupProxy, id string, group *platformclientv2.Groupupdate) (*platformclientv2.Group, *platformclientv2.APIResponse, error) { + return p.groupsApi.PutGroup(id, *group) +} + +func deleteGroupFn(ctx context.Context, p *groupProxy, id string) (*platformclientv2.APIResponse, error) { + return p.groupsApi.DeleteGroup(id) +} + +func getGroupByIdFn(ctx context.Context, p *groupProxy, id string) (*platformclientv2.Group, *platformclientv2.APIResponse, error) { + group := rc.GetCache(p.groupCache, id) + if group != nil { + return group, nil, nil + } + return p.groupsApi.GetGroup(id) +} +func addGroupMembersFn(ctx context.Context, p *groupProxy, id string, members *platformclientv2.Groupmembersupdate) (*interface{}, *platformclientv2.APIResponse, error) { + return p.groupsApi.PostGroupMembers(id, *members) +} + +func deleteGroupMembersFn(ctx context.Context, p *groupProxy, id string, members string) (*interface{}, *platformclientv2.APIResponse, error) { + return p.groupsApi.DeleteGroupMembers(id, members) +} + +func getGroupMembersFn(ctx context.Context, p *groupProxy, id string) (*[]string, *platformclientv2.APIResponse, error) { + members, response, err := p.groupsApi.GetGroupIndividuals(id) + + if err != nil { + return nil, response, err + } + + var existingMembers []string + if members.Entities != nil { + for _, member := range *members.Entities { + existingMembers = append(existingMembers, *member.Id) + } + } + return &existingMembers, nil, nil +} + +func getGroupByNameFn(ctx context.Context, p *groupProxy, name string) (*platformclientv2.Groupssearchresponse, *platformclientv2.APIResponse, error) { + exactSearchType := "EXACT" + nameField := "name" + nameStr := name + + searchCriteria := platformclientv2.Groupsearchcriteria{ + VarType: &exactSearchType, + Value: &nameStr, + Fields: &[]string{nameField}, + } + + groups, resp, getErr := p.groupsApi.PostGroupsSearch(platformclientv2.Groupsearchrequest{ + Query: &[]platformclientv2.Groupsearchcriteria{searchCriteria}, + }) + + return groups, resp, getErr +} + +func getAllGroupFn(ctx context.Context, p *groupProxy) (*[]platformclientv2.Group, *platformclientv2.APIResponse, error) { + var allGroups []platformclientv2.Group + const pageSize = 100 + + groups, resp, getErr := p.groupsApi.GetGroups(pageSize, 1, nil, nil, "") + if getErr != nil { + return nil, resp, fmt.Errorf("Failed to get first page of groups: %v", getErr) + } + + allGroups = append(allGroups, *groups.Entities...) + + for pageNum := 2; pageNum <= *groups.PageCount; pageNum++ { + groups, resp, getErr := p.groupsApi.GetGroups(pageSize, pageNum, nil, nil, "") + if getErr != nil { + return nil, resp, fmt.Errorf("Failed to get page of groups: %v", getErr) + } + allGroups = append(allGroups, *groups.Entities...) + } + + for _, group := range allGroups { + rc.SetCache(p.groupCache, *group.Id, group) + } + + return &allGroups, nil, nil +} diff --git a/genesyscloud/group/resource_genesyscloud_group.go b/genesyscloud/group/resource_genesyscloud_group.go new file mode 100644 index 000000000..42bb9d22e --- /dev/null +++ b/genesyscloud/group/resource_genesyscloud_group.go @@ -0,0 +1,333 @@ +package group + +import ( + "context" + "fmt" + "log" + "strings" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + + "terraform-provider-genesyscloud/genesyscloud/consistency_checker" + "terraform-provider-genesyscloud/genesyscloud/util/chunks" + lists "terraform-provider-genesyscloud/genesyscloud/util/lists" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2" +) + +func GetAllGroups(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + resources := make(resourceExporter.ResourceIDMetaMap) + groupProxy := getGroupProxy(clientConfig) + + groups, resp, err := groupProxy.getAllGroups(ctx) + if err != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to retrieve all groups: %s", err), resp) + } + + for _, group := range *groups { + resources[*group.Id] = &resourceExporter.ResourceMeta{Name: *group.Name} + } + + return resources, nil +} + +func createGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + description := d.Get("description").(string) + groupType := d.Get("type").(string) + visibility := d.Get("visibility").(string) + rulesVisible := d.Get("rules_visible").(bool) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getGroupProxy(sdkConfig) + + addresses, err := buildSdkGroupAddresses(d) + if err != nil { + return diag.Errorf("%v", err) + } + + createGroup := &platformclientv2.Groupcreate{ + Name: &name, + VarType: &groupType, + Visibility: &visibility, + RulesVisible: &rulesVisible, + Addresses: addresses, + OwnerIds: lists.BuildSdkStringListFromInterfaceArray(d, "owner_ids"), + } + log.Printf("Creating group %s", name) + group, resp, err := gp.createGroup(ctx, createGroup) + + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create group %s: %s", name, err), resp) + } + + d.SetId(*group.Id) + + // Description can only be set in a PUT. This is a bug with the API and has been reported + if description != "" { + diagErr := updateGroup(ctx, d, meta) + if diagErr != nil { + return diagErr + } + } + + diagErr := updateGroupMembers(ctx, d, sdkConfig) + if diagErr != nil { + return diagErr + } + + log.Printf("Created group %s %s", name, *group.Id) + return readGroup(ctx, d, meta) +} + +func readGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + + gp := getGroupProxy(sdkConfig) + + log.Printf("Reading group %s", d.Id()) + + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + + group, resp, getErr := gp.getGroupById(ctx, d.Id()) + if getErr != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(fmt.Errorf("Failed to read group %s: %s", d.Id(), getErr)) + } + return retry.NonRetryableError(fmt.Errorf("Failed to read group %s: %s", d.Id(), getErr)) + } + + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceGroup()) + + resourcedata.SetNillableValue(d, "name", group.Name) + resourcedata.SetNillableValue(d, "type", group.VarType) + resourcedata.SetNillableValue(d, "visibility", group.Visibility) + resourcedata.SetNillableValue(d, "rules_visible", group.RulesVisible) + resourcedata.SetNillableValue(d, "description", group.Description) + + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "owner_ids", group.Owners, flattenGroupOwners) + + if group.Addresses != nil { + d.Set("addresses", flattenGroupAddresses(d, group.Addresses)) + } else { + d.Set("addresses", nil) + } + + members, err := readGroupMembers(ctx, d.Id(), sdkConfig) + if err != nil { + return retry.NonRetryableError(fmt.Errorf("%v", err)) + } + d.Set("member_ids", members) + + log.Printf("Read group %s %s", d.Id(), *group.Name) + return cc.CheckState() + }) +} + +func updateGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + description := d.Get("description").(string) + visibility := d.Get("visibility").(string) + rulesVisible := d.Get("rules_visible").(bool) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getGroupProxy(sdkConfig) + + diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + // Get current group version + group, resp, getErr := gp.getGroupById(ctx, d.Id()) + if getErr != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read group %s: %s", d.Id(), getErr), resp) + + } + + addresses, err := buildSdkGroupAddresses(d) + if err != nil { + return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error while trying to buildSdkGroupAddresses for group id: %s", d.Id()), err) + } + + log.Printf("Updating group %s", name) + updateGroup := &platformclientv2.Groupupdate{ + Version: group.Version, + Name: &name, + Description: &description, + Visibility: &visibility, + RulesVisible: &rulesVisible, + Addresses: addresses, + OwnerIds: lists.BuildSdkStringListFromInterfaceArray(d, "owner_ids"), + } + _, resp, putErr := gp.updateGroup(ctx, d.Id(), updateGroup) + if putErr != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update group %s: %s", d.Id(), putErr), resp) + } + + return resp, nil + }) + if diagErr != nil { + return diagErr + } + + diagErr = updateGroupMembers(ctx, d, sdkConfig) + if diagErr != nil { + return diagErr + } + + log.Printf("Updated group %s", name) + return readGroup(ctx, d, meta) +} + +func deleteGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getGroupProxy(sdkConfig) + + util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + // Directory occasionally returns version errors on deletes if an object was updated at the same time. + log.Printf("Deleting group %s", name) + resp, err := gp.deleteGroup(ctx, d.Id()) + if err != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete group %s: %s", name, err), resp) + } + return nil, nil + }) + + return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError { + group, resp, err := gp.getGroupById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + log.Printf("Group %s deleted", name) + return nil + } + return retry.NonRetryableError(fmt.Errorf("Error deleting group %s: %s", d.Id(), err)) + } + + if group.State != nil && *group.State == "deleted" { + log.Printf("Group %s deleted", name) + return nil + } + + /* + This extra delete call is being added here because of DEVTOOLING-485. Basically we are in a transition + state with the groups API. We have two services BEVY and Directory that are managing groups. Bevy is dual + writing to directory. However, Directory always returns a 200 on the delete and then fails asynchronously. + As a result the delete sometimes does not occur and then we just keep picking it up as it has. + After talking with Joe Fruland, the team lead for directory we are putting this extra DELETE in here + to keep trying to delete in case of this situation. + */ + resp, err = gp.deleteGroup(ctx, d.Id()) + if err != nil { + log.Printf("Error while trying to delete group %s inside of the delete retry. Correlation id of failed call %s", + name, resp.CorrelationID) + } + + return retry.RetryableError(fmt.Errorf("Group %s still exists", d.Id())) + }) +} + +func updateGroupMembers(ctx context.Context, d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + gp := getGroupProxy(sdkConfig) + if d.HasChange("member_ids") { + if membersConfig := d.Get("member_ids"); membersConfig != nil { + configMemberIds := *lists.SetToStringList(membersConfig.(*schema.Set)) + existingMemberIds, err := getGroupMemberIds(ctx, d, sdkConfig) + if err != nil { + return err + } + + maxMembersPerRequest := 50 + membersToRemoveList := lists.SliceDifference(existingMemberIds, configMemberIds) + chunkedMemberIdsDelete := chunks.ChunkBy(membersToRemoveList, maxMembersPerRequest) + + chunkProcessor := func(membersToRemove []string) diag.Diagnostics { + if len(membersToRemove) > 0 { + if diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + _, resp, err := gp.deleteGroupMembers(ctx, d.Id(), strings.Join(membersToRemove, ",")) + if err != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to remove members from group %s: %s", d.Id(), err), resp) + } + return resp, nil + }); diagErr != nil { + return diagErr + } + } + return nil + } + + if err := chunks.ProcessChunks(chunkedMemberIdsDelete, chunkProcessor); err != nil { + return err + } + + membersToAdd := lists.SliceDifference(configMemberIds, existingMemberIds) + if len(membersToAdd) < 1 { + return nil + } + + chunkedMemberIds := lists.ChunkStringSlice(membersToAdd, maxMembersPerRequest) + for _, chunk := range chunkedMemberIds { + if err := addGroupMembers(ctx, d, chunk, sdkConfig); err != nil { + return err + } + } + } + } + return nil +} + +func readGroupMembers(ctx context.Context, groupID string, sdkConfig *platformclientv2.Configuration) (*schema.Set, diag.Diagnostics) { + gp := getGroupProxy(sdkConfig) + members, resp, err := gp.getGroupMembers(ctx, groupID) + + if err != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read members for group %s: %s", groupID, err), resp) + } + + interfaceList := make([]interface{}, len(*members)) + for i, v := range *members { + interfaceList[i] = v + } + return schema.NewSet(schema.HashString, interfaceList), nil +} + +func getGroupMemberIds(ctx context.Context, d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) ([]string, diag.Diagnostics) { + gp := getGroupProxy(sdkConfig) + members, resp, err := gp.getGroupMembers(ctx, d.Id()) + if err != nil { + + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Unable to retrieve members for group %s. %s", d.Id(), err), resp) + } + return *members, nil +} + +func addGroupMembers(ctx context.Context, d *schema.ResourceData, membersToAdd []string, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + gp := getGroupProxy(sdkConfig) + if diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + // Need the current group version to add members + groupInfo, resp, getErr := gp.getGroupById(ctx, d.Id()) + if getErr != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read group %s: %s", d.Id(), getErr), resp) + } + + groupMemberUpdate := &platformclientv2.Groupmembersupdate{ + MemberIds: &membersToAdd, + Version: groupInfo.Version, + } + _, resp, postErr := gp.addGroupMembers(ctx, d.Id(), groupMemberUpdate) + if postErr != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to add group members %s: %s", d.Id(), postErr), resp) + } + return resp, nil + }); diagErr != nil { + return diagErr + } + + return nil +} diff --git a/genesyscloud/group/resource_genesyscloud_group_schema.go b/genesyscloud/group/resource_genesyscloud_group_schema.go new file mode 100644 index 000000000..765901bd0 --- /dev/null +++ b/genesyscloud/group/resource_genesyscloud_group_schema.go @@ -0,0 +1,139 @@ +package group + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" + "terraform-provider-genesyscloud/genesyscloud/validators" +) + +const resourceName = "genesyscloud_group" + +var ( + groupPhoneType = "PHONE" + groupAddressResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "number": { + Description: "Phone number for this contact type. Must be in an E.164 number format.", + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validators.ValidatePhoneNumber, + }, + "extension": { + Description: "Phone extension.", + Type: schema.TypeString, + Optional: true, + }, + "type": { + Description: "Contact type of the address. (GROUPRING | GROUPPHONE)", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"GROUPRING", "GROUPPHONE"}, false), + }, + }, + } +) + +// SetRegistrar registers all of the resources, datasources and exporters in the package +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceGroup()) + regInstance.RegisterDataSource(resourceName, DataSourceGroup()) + regInstance.RegisterExporter(resourceName, GroupExporter()) +} + +func GroupExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(GetAllGroups), + RefAttrs: map[string]*resourceExporter.RefAttrSettings{ + "owner_ids": {RefType: "genesyscloud_user"}, + "member_ids": {RefType: "genesyscloud_user"}, + }, + CustomValidateExports: map[string][]string{ + "E164": {"addresses.number"}, + }, + } +} + +func ResourceGroup() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud Directory Group", + + CreateContext: provider.CreateWithPooledClient(createGroup), + ReadContext: provider.ReadWithPooledClient(readGroup), + UpdateContext: provider.UpdateWithPooledClient(updateGroup), + DeleteContext: provider.DeleteWithPooledClient(deleteGroup), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "name": { + Description: "Group name.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Group description.", + Type: schema.TypeString, + Optional: true, + }, + "type": { + Description: "Group type (official | social). This cannot be modified. Changing type attribute will cause the existing genesys_group object to dropped and recreated with a new ID.", + Type: schema.TypeString, + Optional: true, + Default: "official", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"official", "social"}, false), + }, + "visibility": { + Description: "Who can view this group (public | owners | members).", + Type: schema.TypeString, + Optional: true, + Default: "public", + ValidateFunc: validation.StringInSlice([]string{"public", "owners", "members"}, false), + }, + "rules_visible": { + Description: "Are membership rules visible to the person requesting to view the group.", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "addresses": { + Description: "Contact numbers for this group.", + Type: schema.TypeList, + Optional: true, + Elem: groupAddressResource, + }, + "owner_ids": { + Description: "IDs of owners of the group.", + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + }, + "member_ids": { + Description: "IDs of members assigned to the group. If not set, this resource will not manage group members.", + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func DataSourceGroup() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Genesys Cloud Groups. Select a group by name.", + ReadContext: provider.ReadWithPooledClient(dataSourceGroupRead), + Schema: map[string]*schema.Schema{ + "name": { + Description: "Group name.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} diff --git a/genesyscloud/resource_genesyscloud_group_test.go b/genesyscloud/group/resource_genesyscloud_group_test.go similarity index 85% rename from genesyscloud/resource_genesyscloud_group_test.go rename to genesyscloud/group/resource_genesyscloud_group_test.go index 564b695da..112c50a7f 100644 --- a/genesyscloud/resource_genesyscloud_group_test.go +++ b/genesyscloud/group/resource_genesyscloud_group_test.go @@ -1,8 +1,9 @@ -package genesyscloud +package group import ( "fmt" "strconv" + "strings" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -34,8 +35,8 @@ func TestAccResourceGroupBasic(t *testing.T) { Steps: []resource.TestStep{ { // Create a basic group - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + - generateGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + + GenerateGroupResource( groupResource1, groupName, strconv.Quote(groupDesc1), @@ -54,7 +55,7 @@ func TestAccResourceGroupBasic(t *testing.T) { }, { // Update group - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateGroupResource( groupResource1, groupName, strconv.Quote(groupDesc2), @@ -104,7 +105,7 @@ func TestAccResourceGroupAddresses(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource1, groupName, generateGroupAddress( @@ -122,7 +123,7 @@ func TestAccResourceGroupAddresses(t *testing.T) { }, { // Update phone number & type - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource1, groupName, generateGroupAddress( @@ -140,7 +141,7 @@ func TestAccResourceGroupAddresses(t *testing.T) { }, { // Remove number and set extension - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource1, groupName, generateGroupAddress( @@ -158,7 +159,7 @@ func TestAccResourceGroupAddresses(t *testing.T) { }, { // Update the extension - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource1, groupName, generateGroupAddress( @@ -212,11 +213,11 @@ func TestAccResourceGroupMembers(t *testing.T) { groupName, GenerateGroupOwners("genesyscloud_user."+userResource1+".id"), generateGroupMembers("genesyscloud_user."+userResource2+".id"), - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource1, userEmail1, userName1, - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource2, userEmail2, userName2, @@ -228,7 +229,7 @@ func TestAccResourceGroupMembers(t *testing.T) { }, { // Make the owner a member - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource, groupName, GenerateGroupOwners("genesyscloud_user."+userResource1+".id"), @@ -236,11 +237,11 @@ func TestAccResourceGroupMembers(t *testing.T) { "genesyscloud_user."+userResource1+".id", "genesyscloud_user."+userResource2+".id", ), - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource1, userEmail1, userName1, - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource2, userEmail2, userName2, @@ -253,18 +254,18 @@ func TestAccResourceGroupMembers(t *testing.T) { }, { // Remove a member and change the owner - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource, groupName, GenerateGroupOwners("genesyscloud_user."+userResource2+".id"), generateGroupMembers( "genesyscloud_user."+userResource1+".id", ), - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource1, userEmail1, userName1, - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource2, userEmail2, userName2, @@ -276,12 +277,12 @@ func TestAccResourceGroupMembers(t *testing.T) { }, { // Remove all members while deleting the user - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( groupResource, groupName, GenerateGroupOwners("genesyscloud_user."+userResource2+".id"), "member_ids = []", - ) + GenerateBasicUserResource( + ) + generateBasicUserResource( userResource2, userEmail2, userName2, @@ -353,3 +354,43 @@ func validateGroupMember(groupResourceName string, userResourceName string, attr return fmt.Errorf("%s %s not found for group %s in state", attrName, userID, groupID) } } + +// Duplicating this code within the function to not break a cyclid dependency +func generateUserWithCustomAttrs(resourceID string, email string, name string, attrs ...string) string { + return fmt.Sprintf(`resource "genesyscloud_user" "%s" { + email = "%s" + name = "%s" + %s + } + `, resourceID, email, name, strings.Join(attrs, "\n")) +} + +// Basic user with minimum required fields +func generateBasicUserResource(resourceID string, email string, name string) string { + return generateUserResource(resourceID, email, name, util.NullValue, util.NullValue, util.NullValue, util.NullValue, util.NullValue, "", "") +} + +func generateUserResource( + resourceID string, + email string, + name string, + state string, + title string, + department string, + manager string, + acdAutoAnswer string, + profileSkills string, + certifications string) string { + return fmt.Sprintf(`resource "genesyscloud_user" "%s" { + email = "%s" + name = "%s" + state = %s + title = %s + department = %s + manager = %s + acd_auto_answer = %s + profile_skills = [%s] + certifications = [%s] + } + `, resourceID, email, name, state, title, department, manager, acdAutoAnswer, profileSkills, certifications) +} diff --git a/genesyscloud/group/resource_genesyscloud_group_utils.go b/genesyscloud/group/resource_genesyscloud_group_utils.go new file mode 100644 index 000000000..82763f04b --- /dev/null +++ b/genesyscloud/group/resource_genesyscloud_group_utils.go @@ -0,0 +1,162 @@ +package group + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2" + "log" + "strings" + "terraform-provider-genesyscloud/genesyscloud/util" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" +) + +// 'number' and 'extension' conflict with eachother. However, one must be set. +// This function validates that the user has satisfied these conditions +func validateAddressesMap(m map[string]interface{}) error { + number, _ := m["number"].(string) + extension, _ := m["extension"].(string) + + if (number != "" && extension != "") || + (number == "" && extension == "") { + return fmt.Errorf("Either 'number' or 'extension' must be set inside addresses, but both cannot be set.") + } + + return nil +} + +func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2.Groupcontact) []interface{} { + addressSlice := make([]interface{}, 0) + for _, address := range *addresses { + if address.MediaType != nil { + if *address.MediaType == groupPhoneType { + phoneNumber := make(map[string]interface{}) + + // Strip off any parentheses from phone numbers + if address.Address != nil { + phoneNumber["number"] = strings.Trim(*address.Address, "()") + } + + resourcedata.SetMapValueIfNotNil(phoneNumber, "extension", address.Extension) + resourcedata.SetMapValueIfNotNil(phoneNumber, "type", address.VarType) + + // Sometimes the number or extension is only returned in Display + if address.Address == nil && + address.Extension == nil && + address.Display != nil { + setExtensionOrNumberBasedOnDisplay(d, phoneNumber, &address) + } + + addressSlice = append(addressSlice, phoneNumber) + } else { + log.Printf("Unknown address media type %s", *address.MediaType) + } + } + } + return addressSlice +} + +/** +* The api can sometimes return only the display which holds the value +* that was stored in either `address` or `extension` +* This function establishes which field was set in the schema data (`extension` or `address`) +* and then sets that field in the map to the value that came back in `display` + */ +func setExtensionOrNumberBasedOnDisplay(d *schema.ResourceData, addressMap map[string]interface{}, address *platformclientv2.Groupcontact) { + display := strings.Trim(*address.Display, "()") + schemaAddresses := d.Get("addresses").([]interface{}) + for _, a := range schemaAddresses { + currentAddress, ok := a.(map[string]interface{}) + if !ok { + continue + } + addressType, _ := currentAddress["type"].(string) + if addressType != *address.VarType { + continue + } + if ext, _ := currentAddress["extension"].(string); ext != "" { + addressMap["extension"] = display + } else if number, _ := currentAddress["number"].(string); number != "" { + addressMap["number"] = display + } + } +} + +func flattenGroupOwners(owners *[]platformclientv2.User) []interface{} { + interfaceList := make([]interface{}, len(*owners)) + for i, v := range *owners { + interfaceList[i] = *v.Id + } + return interfaceList +} + +func buildSdkGroupAddresses(d *schema.ResourceData) (*[]platformclientv2.Groupcontact, error) { + if addressSlice, ok := d.Get("addresses").([]interface{}); ok && len(addressSlice) > 0 { + sdkContacts := make([]platformclientv2.Groupcontact, len(addressSlice)) + for i, configPhone := range addressSlice { + phoneMap := configPhone.(map[string]interface{}) + phoneType := phoneMap["type"].(string) + contact := platformclientv2.Groupcontact{ + VarType: &phoneType, + MediaType: &groupPhoneType, // Only option is PHONE + } + + if err := validateAddressesMap(phoneMap); err != nil { + return nil, err + } + + if phoneNum, ok := phoneMap["number"].(string); ok && phoneNum != "" { + contact.Address = &phoneNum + } + + if phoneExt := phoneMap["extension"].(string); ok && phoneExt != "" { + contact.Extension = &phoneExt + } + + sdkContacts[i] = contact + } + return &sdkContacts, nil + } + return nil, nil +} + +func GenerateBasicGroupResource(resourceID string, name string, nestedBlocks ...string) string { + return GenerateGroupResource(resourceID, name, util.NullValue, util.NullValue, util.NullValue, util.TrueValue, nestedBlocks...) +} + +func GenerateGroupResource( + resourceID string, + name string, + desc string, + groupType string, + visibility string, + rulesVisible string, + nestedBlocks ...string) string { + return fmt.Sprintf(`resource "genesyscloud_group" "%s" { + name = "%s" + description = %s + type = %s + visibility = %s + rules_visible = %s + %s + } + `, resourceID, name, desc, groupType, visibility, rulesVisible, strings.Join(nestedBlocks, "\n")) +} + +func generateGroupAddress(number string, phoneType string, extension string) string { + return fmt.Sprintf(`addresses { + number = %s + type = "%s" + extension = %s + } + `, number, phoneType, extension) +} + +func GenerateGroupOwners(userIDs ...string) string { + return fmt.Sprintf(`owner_ids = [%s] + `, strings.Join(userIDs, ",")) +} + +func generateGroupMembers(userIDs ...string) string { + return fmt.Sprintf(`member_ids = [%s] + `, strings.Join(userIDs, ",")) +} diff --git a/genesyscloud/group_roles/genesyscloud_group_roles_init_test.go b/genesyscloud/group_roles/genesyscloud_group_roles_init_test.go index 7164c8517..549942044 100644 --- a/genesyscloud/group_roles/genesyscloud_group_roles_init_test.go +++ b/genesyscloud/group_roles/genesyscloud_group_roles_init_test.go @@ -5,6 +5,7 @@ import ( "sync" gcloud "terraform-provider-genesyscloud/genesyscloud" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" + "terraform-provider-genesyscloud/genesyscloud/group" "testing" ) @@ -26,7 +27,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_group_roles"] = ResourceGroupRoles() providerResources["genesyscloud_user"] = gcloud.ResourceUser() - providerResources["genesyscloud_group"] = gcloud.ResourceGroup() + providerResources["genesyscloud_group"] = group.ResourceGroup() providerResources["genesyscloud_auth_role"] = authRole.ResourceAuthRole() providerResources["genesyscloud_auth_division"] = gcloud.ResourceAuthDivision() } diff --git a/genesyscloud/group_roles/resource_genesyscloud_group_roles_schema.go b/genesyscloud/group_roles/resource_genesyscloud_group_roles_schema.go index e9df2e2ef..b889a268a 100644 --- a/genesyscloud/group_roles/resource_genesyscloud_group_roles_schema.go +++ b/genesyscloud/group_roles/resource_genesyscloud_group_roles_schema.go @@ -1,8 +1,11 @@ package group_roles import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "terraform-provider-genesyscloud/genesyscloud" + "github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2" + "terraform-provider-genesyscloud/genesyscloud/provider" resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" @@ -67,7 +70,7 @@ func ResourceGroupRoles() *schema.Resource { // GroupRolesExporter returns the resourceExporter object used to hold the genesyscloud_group_roles exporter's config func GroupRolesExporter() *resourceExporter.ResourceExporter { return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(genesyscloud.GetAllGroups), + GetResourcesFunc: provider.GetAllWithPooledClient(getAllGroups), RefAttrs: map[string]*resourceExporter.RefAttrSettings{ "group_id": {RefType: "genesyscloud_group"}, "roles.role_id": {RefType: "genesyscloud_auth_role"}, @@ -78,3 +81,27 @@ func GroupRolesExporter() *resourceExporter.ResourceExporter { }, } } + +// Duplicated this from the group package to break a cyclical dependency. We should be asking ourselves why we are doing this at some point +func getAllGroups(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + resources := make(resourceExporter.ResourceIDMetaMap) + groupsAPI := platformclientv2.NewGroupsApiWithConfig(clientConfig) + + for pageNum := 1; ; pageNum++ { + const pageSize = 100 + groups, _, getErr := groupsAPI.GetGroups(pageSize, pageNum, nil, nil, "") + if getErr != nil { + return nil, diag.Errorf("Failed to get page of groups: %v", getErr) + } + + if groups.Entities == nil || len(*groups.Entities) == 0 { + break + } + + for _, group := range *groups.Entities { + resources[*group.Id] = &resourceExporter.ResourceMeta{Name: *group.Name} + } + } + + return resources, nil +} diff --git a/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go b/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go index 06cbdcd7f..f7a39554e 100644 --- a/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go +++ b/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud" + "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "terraform-provider-genesyscloud/genesyscloud/util/lists" @@ -40,10 +41,10 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { Steps: []resource.TestStep{ { // Create group with 1 role in default division - Config: genesyscloud.GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + genesyscloud.GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + group.GenerateBasicGroupResource( groupResource1, groupName, - genesyscloud.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + authRole.GenerateAuthRoleResource( roleResource1, roleName1, @@ -59,10 +60,10 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { }, { // Create another role and division and add to the group - Config: genesyscloud.GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + genesyscloud.GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + group.GenerateBasicGroupResource( groupResource1, groupName, - genesyscloud.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + authRole.GenerateAuthRoleResource( roleResource1, roleName1, @@ -84,10 +85,10 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { }, { // Remove a role from the group and modify division - Config: genesyscloud.GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + genesyscloud.GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + group.GenerateBasicGroupResource( groupResource1, groupName, - genesyscloud.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + authRole.GenerateAuthRoleResource( roleResource1, roleName1, @@ -103,10 +104,10 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { }, { // Remove all roles from the group - Config: genesyscloud.GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + genesyscloud.GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + group.GenerateBasicGroupResource( groupResource1, groupName, - genesyscloud.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + authRole.GenerateAuthRoleResource( roleResource1, roleName1, @@ -212,3 +213,13 @@ func generateResourceRoles(skillID string, divisionIds ...string) string { } `, skillID, divAttr) } + +// TODO: Duplicating this code within the function to not break a cyclic dependency +func generateUserWithCustomAttrs(resourceID string, email string, name string, attrs ...string) string { + return fmt.Sprintf(`resource "genesyscloud_user" "%s" { + email = "%s" + name = "%s" + %s + } + `, resourceID, email, name, strings.Join(attrs, "\n")) +} diff --git a/genesyscloud/integration/genesyscloud_integration_init_test.go b/genesyscloud/integration/genesyscloud_integration_init_test.go index 6900d3f90..c3af2d91a 100644 --- a/genesyscloud/integration/genesyscloud_integration_init_test.go +++ b/genesyscloud/integration/genesyscloud_integration_init_test.go @@ -2,6 +2,7 @@ package integration import ( "sync" + "terraform-provider-genesyscloud/genesyscloud/group" "testing" gcloud "terraform-provider-genesyscloud/genesyscloud" @@ -34,7 +35,7 @@ func (r *registerTestInstance) registerTestResources() { defer r.resourceMapMutex.Unlock() providerResources["genesyscloud_integration"] = ResourceIntegration() - providerResources["genesyscloud_group"] = gcloud.ResourceGroup() + providerResources["genesyscloud_group"] = group.ResourceGroup() providerResources["genesyscloud_integration_credential"] = integrationCred.ResourceIntegrationCredential() providerResources["genesyscloud_user"] = gcloud.ResourceUser() } diff --git a/genesyscloud/integration/resource_genesyscloud_integration_test.go b/genesyscloud/integration/resource_genesyscloud_integration_test.go index c69c7d35d..28512c662 100644 --- a/genesyscloud/integration/resource_genesyscloud_integration_test.go +++ b/genesyscloud/integration/resource_genesyscloud_integration_test.go @@ -3,11 +3,11 @@ package integration import ( "fmt" "strconv" + "strings" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" - gcloud "terraform-provider-genesyscloud/genesyscloud" integrationCred "terraform-provider-genesyscloud/genesyscloud/integration_credential" "github.com/google/uuid" @@ -163,10 +163,10 @@ func TestAccResourceIntegration(t *testing.T) { ImportStateVerify: true, }, { // Create a group first and use it as reference for a new integration - Config: gcloud.GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + gcloud.GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateBasicGroupResource( groupResource1, groupName, - gcloud.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + GenerateIntegrationResource( inteResource1, strconv.Quote(enabledState), @@ -384,3 +384,42 @@ func testVerifyIntegrationDestroyed(state *terraform.State) error { // Success. All integrations destroyed return nil } + +// TODO: Duplicating this code within the function to not break a cyclic dependency +func generateUserWithCustomAttrs(resourceID string, email string, name string, attrs ...string) string { + return fmt.Sprintf(`resource "genesyscloud_user" "%s" { + email = "%s" + name = "%s" + %s + } + `, resourceID, email, name, strings.Join(attrs, "\n")) +} + +// TODO: Duplicating this code within the function to not break a cyclic dependency +func generateBasicGroupResource(resourceID string, name string, nestedBlocks ...string) string { + return generateGroupResource(resourceID, name, util.NullValue, util.NullValue, util.NullValue, util.TrueValue, nestedBlocks...) +} + +func generateGroupResource( + resourceID string, + name string, + desc string, + groupType string, + visibility string, + rulesVisible string, + nestedBlocks ...string) string { + return fmt.Sprintf(`resource "genesyscloud_group" "%s" { + name = "%s" + description = %s + type = %s + visibility = %s + rules_visible = %s + %s + } + `, resourceID, name, desc, groupType, visibility, rulesVisible, strings.Join(nestedBlocks, "\n")) +} + +func generateGroupOwners(userIDs ...string) string { + return fmt.Sprintf(`owner_ids = [%s] + `, strings.Join(userIDs, ",")) +} diff --git a/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go b/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go index cb09d5355..941d8f7fe 100644 --- a/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go +++ b/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go @@ -167,7 +167,7 @@ func TestAccDataSourceRecordingMediaRetentionPolicy(t *testing.T) { userResource1, generateResourceRoles("genesyscloud_auth_role."+roleResource1+".id"), ) + - gcloud.GenerateUserWithCustomAttrs(userResource1, userEmail, userName) + + generateUserWithCustomAttrs(userResource1, userEmail, userName) + gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + diff --git a/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go b/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go index e0c4c1b96..fca2f6c32 100644 --- a/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go +++ b/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go @@ -958,7 +958,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { userResource1, GenerateResourceRoles("genesyscloud_auth_role."+roleResource1+".id"), ) + - gcloud.GenerateUserWithCustomAttrs(userResource1, userEmail, userName) + + generateUserWithCustomAttrs(userResource1, userEmail, userName) + gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + @@ -1065,7 +1065,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { userResource1, GenerateResourceRoles("genesyscloud_auth_role."+roleResource1+".id"), ) + - gcloud.GenerateUserWithCustomAttrs(userResource1, userEmail, userName) + + generateUserWithCustomAttrs(userResource1, userEmail, userName) + gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + @@ -1172,7 +1172,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { userResource1, GenerateResourceRoles("genesyscloud_auth_role."+roleResource1+".id"), ) + - gcloud.GenerateUserWithCustomAttrs(userResource1, userEmail, userName) + + generateUserWithCustomAttrs(userResource1, userEmail, userName) + gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + @@ -1279,7 +1279,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { userResource1, GenerateResourceRoles("genesyscloud_auth_role."+roleResource1+".id"), ) + - gcloud.GenerateUserWithCustomAttrs(userResource1, userEmail, userName) + + generateUserWithCustomAttrs(userResource1, userEmail, userName) + gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + @@ -2394,3 +2394,13 @@ func GenerateResourceRoles(skillID string, divisionIds ...string) string { } `, skillID, divAttr) } + +// TODO Duplicating this code within the function to not break a cyclid dependency +func generateUserWithCustomAttrs(resourceID string, email string, name string, attrs ...string) string { + return fmt.Sprintf(`resource "genesyscloud_user" "%s" { + email = "%s" + name = "%s" + %s + } + `, resourceID, email, name, strings.Join(attrs, "\n")) +} diff --git a/genesyscloud/resource_genesyscloud_group.go b/genesyscloud/resource_genesyscloud_group.go deleted file mode 100644 index 9c3d1d971..000000000 --- a/genesyscloud/resource_genesyscloud_group.go +++ /dev/null @@ -1,582 +0,0 @@ -package genesyscloud - -import ( - "context" - "fmt" - "log" - "strings" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "terraform-provider-genesyscloud/genesyscloud/validators" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - - "terraform-provider-genesyscloud/genesyscloud/consistency_checker" - "terraform-provider-genesyscloud/genesyscloud/util/chunks" - lists "terraform-provider-genesyscloud/genesyscloud/util/lists" - "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" - - resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2" -) - -var ( - groupPhoneType = "PHONE" - groupAddressResource = &schema.Resource{ - Schema: map[string]*schema.Schema{ - "number": { - Description: "Phone number for this contact type. Must be in an E.164 number format.", - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validators.ValidatePhoneNumber, - }, - "extension": { - Description: "Phone extension.", - Type: schema.TypeString, - Optional: true, - }, - "type": { - Description: "Contact type of the address. (GROUPRING | GROUPPHONE)", - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"GROUPRING", "GROUPPHONE"}, false), - }, - }, - } -) - -func GetAllGroups(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { - resources := make(resourceExporter.ResourceIDMetaMap) - groupsAPI := platformclientv2.NewGroupsApiWithConfig(clientConfig) - - for pageNum := 1; ; pageNum++ { - const pageSize = 100 - groups, _, getErr := groupsAPI.GetGroups(pageSize, pageNum, nil, nil, "") - if getErr != nil { - return nil, diag.Errorf("Failed to get page of groups: %v", getErr) - } - - if groups.Entities == nil || len(*groups.Entities) == 0 { - break - } - - for _, group := range *groups.Entities { - resources[*group.Id] = &resourceExporter.ResourceMeta{Name: *group.Name} - } - } - - return resources, nil -} - -func GroupExporter() *resourceExporter.ResourceExporter { - return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(GetAllGroups), - RefAttrs: map[string]*resourceExporter.RefAttrSettings{ - "owner_ids": {RefType: "genesyscloud_user"}, - "member_ids": {RefType: "genesyscloud_user"}, - }, - CustomValidateExports: map[string][]string{ - "E164": {"addresses.number"}, - }, - } -} - -func ResourceGroup() *schema.Resource { - return &schema.Resource{ - Description: "Genesys Cloud Directory Group", - - CreateContext: provider.CreateWithPooledClient(createGroup), - ReadContext: provider.ReadWithPooledClient(readGroup), - UpdateContext: provider.UpdateWithPooledClient(updateGroup), - DeleteContext: provider.DeleteWithPooledClient(deleteGroup), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - SchemaVersion: 1, - Schema: map[string]*schema.Schema{ - "name": { - Description: "Group name.", - Type: schema.TypeString, - Required: true, - }, - "description": { - Description: "Group description.", - Type: schema.TypeString, - Optional: true, - }, - "type": { - Description: "Group type (official | social). This cannot be modified. Changing type attribute will cause the existing genesys_group object to dropped and recreated with a new ID.", - Type: schema.TypeString, - Optional: true, - Default: "official", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"official", "social"}, false), - }, - "visibility": { - Description: "Who can view this group (public | owners | members).", - Type: schema.TypeString, - Optional: true, - Default: "public", - ValidateFunc: validation.StringInSlice([]string{"public", "owners", "members"}, false), - }, - "rules_visible": { - Description: "Are membership rules visible to the person requesting to view the group.", - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "addresses": { - Description: "Contact numbers for this group.", - Type: schema.TypeList, - Optional: true, - Elem: groupAddressResource, - }, - "owner_ids": { - Description: "IDs of owners of the group.", - Type: schema.TypeList, - Required: true, - Elem: &schema.Schema{Type: schema.TypeString}, - MinItems: 1, - }, - "member_ids": { - Description: "IDs of members assigned to the group. If not set, this resource will not manage group members.", - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - } -} - -func createGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - description := d.Get("description").(string) - groupType := d.Get("type").(string) - visibility := d.Get("visibility").(string) - rulesVisible := d.Get("rules_visible").(bool) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - groupsAPI := platformclientv2.NewGroupsApiWithConfig(sdkConfig) - - addresses, err := buildSdkGroupAddresses(d) - if err != nil { - return diag.Errorf("%v", err) - } - - log.Printf("Creating group %s", name) - group, _, err := groupsAPI.PostGroups(platformclientv2.Groupcreate{ - Name: &name, - VarType: &groupType, - Visibility: &visibility, - RulesVisible: &rulesVisible, - Addresses: addresses, - OwnerIds: lists.BuildSdkStringListFromInterfaceArray(d, "owner_ids"), - }) - if err != nil { - return diag.Errorf("Failed to create group %s: %s", name, err) - } - - d.SetId(*group.Id) - - // Description can only be set in a PUT. This is a bug with the API and has been reported - if description != "" { - diagErr := updateGroup(ctx, d, meta) - if diagErr != nil { - return diagErr - } - } - - diagErr := updateGroupMembers(d, groupsAPI) - if diagErr != nil { - return diagErr - } - - log.Printf("Created group %s %s", name, *group.Id) - return readGroup(ctx, d, meta) -} - -func readGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - groupsAPI := platformclientv2.NewGroupsApiWithConfig(sdkConfig) - - log.Printf("Reading group %s", d.Id()) - - return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { - - group, resp, getErr := groupsAPI.GetGroup(d.Id()) - if getErr != nil { - if util.IsStatus404(resp) { - return retry.RetryableError(fmt.Errorf("Failed to read group %s: %s", d.Id(), getErr)) - } - return retry.NonRetryableError(fmt.Errorf("Failed to read group %s: %s", d.Id(), getErr)) - } - - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceGroup()) - - resourcedata.SetNillableValue(d, "name", group.Name) - resourcedata.SetNillableValue(d, "type", group.VarType) - resourcedata.SetNillableValue(d, "visibility", group.Visibility) - resourcedata.SetNillableValue(d, "rules_visible", group.RulesVisible) - resourcedata.SetNillableValue(d, "description", group.Description) - - resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "owner_ids", group.Owners, flattenGroupOwners) - - if group.Addresses != nil { - d.Set("addresses", flattenGroupAddresses(d, group.Addresses)) - } else { - d.Set("addresses", nil) - } - - members, err := readGroupMembers(d.Id(), groupsAPI) - if err != nil { - return retry.NonRetryableError(fmt.Errorf("%v", err)) - } - d.Set("member_ids", members) - - log.Printf("Read group %s %s", d.Id(), *group.Name) - return cc.CheckState() - }) -} - -func updateGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - description := d.Get("description").(string) - visibility := d.Get("visibility").(string) - rulesVisible := d.Get("rules_visible").(bool) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - groupsAPI := platformclientv2.NewGroupsApiWithConfig(sdkConfig) - - diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { - // Get current group version - group, resp, getErr := groupsAPI.GetGroup(d.Id()) - if getErr != nil { - return resp, diag.Errorf("Failed to read group %s: %s", d.Id(), getErr) - } - - addresses, err := buildSdkGroupAddresses(d) - if err != nil { - return nil, diag.Errorf("%v", err) - } - - log.Printf("Updating group %s", name) - _, resp, putErr := groupsAPI.PutGroup(d.Id(), platformclientv2.Groupupdate{ - Version: group.Version, - Name: &name, - Description: &description, - Visibility: &visibility, - RulesVisible: &rulesVisible, - Addresses: addresses, - OwnerIds: lists.BuildSdkStringListFromInterfaceArray(d, "owner_ids"), - }) - if putErr != nil { - return resp, diag.Errorf("Failed to update group %s: %s", d.Id(), putErr) - } - - return resp, nil - }) - if diagErr != nil { - return diagErr - } - - diagErr = updateGroupMembers(d, groupsAPI) - if diagErr != nil { - return diagErr - } - - log.Printf("Updated group %s", name) - return readGroup(ctx, d, meta) -} - -func deleteGroup(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - groupsAPI := platformclientv2.NewGroupsApiWithConfig(sdkConfig) - - util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { - // Directory occasionally returns version errors on deletes if an object was updated at the same time. - log.Printf("Deleting group %s", name) - resp, err := groupsAPI.DeleteGroup(d.Id()) - if err != nil { - return resp, diag.Errorf("Failed to delete group %s: %s", name, err) - } - return nil, nil - }) - - return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError { - group, resp, err := groupsAPI.GetGroup(d.Id()) - if err != nil { - if util.IsStatus404(resp) { - log.Printf("Group %s deleted", name) - return nil - } - return retry.NonRetryableError(fmt.Errorf("Error deleting group %s: %s", d.Id(), err)) - } - - if group.State != nil && *group.State == "deleted" { - log.Printf("Group %s deleted", name) - return nil - } - - return retry.RetryableError(fmt.Errorf("Group %s still exists", d.Id())) - }) -} - -func buildSdkGroupAddresses(d *schema.ResourceData) (*[]platformclientv2.Groupcontact, error) { - if addressSlice, ok := d.Get("addresses").([]interface{}); ok && len(addressSlice) > 0 { - sdkContacts := make([]platformclientv2.Groupcontact, len(addressSlice)) - for i, configPhone := range addressSlice { - phoneMap := configPhone.(map[string]interface{}) - phoneType := phoneMap["type"].(string) - contact := platformclientv2.Groupcontact{ - VarType: &phoneType, - MediaType: &groupPhoneType, // Only option is PHONE - } - - if err := validateAddressesMap(phoneMap); err != nil { - return nil, err - } - - if phoneNum, ok := phoneMap["number"].(string); ok && phoneNum != "" { - contact.Address = &phoneNum - } - - if phoneExt := phoneMap["extension"].(string); ok && phoneExt != "" { - contact.Extension = &phoneExt - } - - sdkContacts[i] = contact - } - return &sdkContacts, nil - } - return nil, nil -} - -// 'number' and 'extension' conflict with eachother. However, one must be set. -// This function validates that the user has satisfied these conditions -func validateAddressesMap(m map[string]interface{}) error { - number, _ := m["number"].(string) - extension, _ := m["extension"].(string) - - if (number != "" && extension != "") || - (number == "" && extension == "") { - return fmt.Errorf("Either 'number' or 'extension' must be set inside addresses, but both cannot be set.") - } - - return nil -} - -func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2.Groupcontact) []interface{} { - addressSlice := make([]interface{}, 0) - for _, address := range *addresses { - if address.MediaType != nil { - if *address.MediaType == groupPhoneType { - phoneNumber := make(map[string]interface{}) - - // Strip off any parentheses from phone numbers - if address.Address != nil { - phoneNumber["number"] = strings.Trim(*address.Address, "()") - } - - resourcedata.SetMapValueIfNotNil(phoneNumber, "extension", address.Extension) - resourcedata.SetMapValueIfNotNil(phoneNumber, "type", address.VarType) - - // Sometimes the number or extension is only returned in Display - if address.Address == nil && - address.Extension == nil && - address.Display != nil { - setExtensionOrNumberBasedOnDisplay(d, phoneNumber, &address) - } - - addressSlice = append(addressSlice, phoneNumber) - } else { - log.Printf("Unknown address media type %s", *address.MediaType) - } - } - } - return addressSlice -} - -/** -* The api can sometimes return only the display which holds the value -* that was stored in either `address` or `extension` -* This function establishes which field was set in the schema data (`extension` or `address`) -* and then sets that field in the map to the value that came back in `display` - */ -func setExtensionOrNumberBasedOnDisplay(d *schema.ResourceData, addressMap map[string]interface{}, address *platformclientv2.Groupcontact) { - display := strings.Trim(*address.Display, "()") - schemaAddresses := d.Get("addresses").([]interface{}) - for _, a := range schemaAddresses { - currentAddress, ok := a.(map[string]interface{}) - if !ok { - continue - } - addressType, _ := currentAddress["type"].(string) - if addressType != *address.VarType { - continue - } - if ext, _ := currentAddress["extension"].(string); ext != "" { - addressMap["extension"] = display - } else if number, _ := currentAddress["number"].(string); number != "" { - addressMap["number"] = display - } - } -} - -func flattenGroupOwners(owners *[]platformclientv2.User) []interface{} { - interfaceList := make([]interface{}, len(*owners)) - for i, v := range *owners { - interfaceList[i] = *v.Id - } - return interfaceList -} - -func updateGroupMembers(d *schema.ResourceData, groupsAPI *platformclientv2.GroupsApi) diag.Diagnostics { - if d.HasChange("member_ids") { - if membersConfig := d.Get("member_ids"); membersConfig != nil { - configMemberIds := *lists.SetToStringList(membersConfig.(*schema.Set)) - existingMemberIds, err := getGroupMemberIds(d, groupsAPI) - if err != nil { - return err - } - - maxMembersPerRequest := 50 - membersToRemoveList := lists.SliceDifference(existingMemberIds, configMemberIds) - chunkedMemberIdsDelete := chunks.ChunkBy(membersToRemoveList, maxMembersPerRequest) - - chunkProcessor := func(membersToRemove []string) diag.Diagnostics { - if len(membersToRemove) > 0 { - if diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { - _, resp, err := groupsAPI.DeleteGroupMembers(d.Id(), strings.Join(membersToRemove, ",")) - if err != nil { - return resp, diag.Errorf("Failed to remove members from group %s: %s", d.Id(), err) - } - return resp, nil - }); diagErr != nil { - return diagErr - } - } - return nil - } - - if err := chunks.ProcessChunks(chunkedMemberIdsDelete, chunkProcessor); err != nil { - return err - } - - membersToAdd := lists.SliceDifference(configMemberIds, existingMemberIds) - if len(membersToAdd) < 1 { - return nil - } - - chunkedMemberIds := lists.ChunkStringSlice(membersToAdd, maxMembersPerRequest) - for _, chunk := range chunkedMemberIds { - if err := addGroupMembers(d, chunk, groupsAPI); err != nil { - return err - } - } - } - } - return nil -} - -func readGroupMembers(groupID string, groupsAPI *platformclientv2.GroupsApi) (*schema.Set, diag.Diagnostics) { - members, _, err := groupsAPI.GetGroupIndividuals(groupID) - if err != nil { - return nil, diag.Errorf("Failed to read members for group %s: %s", groupID, err) - } - - if members.Entities != nil { - interfaceList := make([]interface{}, len(*members.Entities)) - for i, v := range *members.Entities { - interfaceList[i] = *v.Id - } - return schema.NewSet(schema.HashString, interfaceList), nil - } - return nil, nil -} - -func getGroupMemberIds(d *schema.ResourceData, groupsAPI *platformclientv2.GroupsApi) ([]string, diag.Diagnostics) { - members, _, err := groupsAPI.GetGroupIndividuals(d.Id()) - if err != nil { - return nil, diag.FromErr(err) - } - - var existingMembers []string - if members.Entities != nil { - for _, member := range *members.Entities { - existingMembers = append(existingMembers, *member.Id) - } - } - return existingMembers, nil -} - -func addGroupMembers(d *schema.ResourceData, membersToAdd []string, groupsAPI *platformclientv2.GroupsApi) diag.Diagnostics { - if diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { - // Need the current group version to add members - groupInfo, _, getErr := groupsAPI.GetGroup(d.Id()) - if getErr != nil { - return nil, diag.Errorf("Failed to read group %s: %s", d.Id(), getErr) - } - - _, resp, postErr := groupsAPI.PostGroupMembers(d.Id(), platformclientv2.Groupmembersupdate{ - MemberIds: &membersToAdd, - Version: groupInfo.Version, - }) - if postErr != nil { - return resp, diag.Errorf("Failed to add group members %s: %s", d.Id(), postErr) - } - return resp, nil - }); diagErr != nil { - return diagErr - } - return nil -} - -func GenerateBasicGroupResource(resourceID string, name string, nestedBlocks ...string) string { - return generateGroupResource(resourceID, name, util.NullValue, util.NullValue, util.NullValue, util.TrueValue, nestedBlocks...) -} - -func generateGroupResource( - resourceID string, - name string, - desc string, - groupType string, - visibility string, - rulesVisible string, - nestedBlocks ...string) string { - return fmt.Sprintf(`resource "genesyscloud_group" "%s" { - name = "%s" - description = %s - type = %s - visibility = %s - rules_visible = %s - %s - } - `, resourceID, name, desc, groupType, visibility, rulesVisible, strings.Join(nestedBlocks, "\n")) -} - -func generateGroupAddress(number string, phoneType string, extension string) string { - return fmt.Sprintf(`addresses { - number = %s - type = "%s" - extension = %s - } - `, number, phoneType, extension) -} - -func GenerateGroupOwners(userIDs ...string) string { - return fmt.Sprintf(`owner_ids = [%s] - `, strings.Join(userIDs, ",")) -} - -func generateGroupMembers(userIDs ...string) string { - return fmt.Sprintf(`member_ids = [%s] - `, strings.Join(userIDs, ",")) -} diff --git a/genesyscloud/resource_genesyscloud_init.go b/genesyscloud/resource_genesyscloud_init.go index b4ce31115..b01cdab8d 100644 --- a/genesyscloud/resource_genesyscloud_init.go +++ b/genesyscloud/resource_genesyscloud_init.go @@ -21,7 +21,6 @@ func registerDataSources(l registrar.Registrar) { l.RegisterDataSource("genesyscloud_architect_user_prompt", dataSourceUserPrompt()) l.RegisterDataSource("genesyscloud_auth_division", dataSourceAuthDivision()) l.RegisterDataSource("genesyscloud_auth_division_home", DataSourceAuthDivisionHome()) - l.RegisterDataSource("genesyscloud_group", DataSourceGroup()) l.RegisterDataSource("genesyscloud_journey_action_map", dataSourceJourneyActionMap()) l.RegisterDataSource("genesyscloud_journey_action_template", dataSourceJourneyActionTemplate()) l.RegisterDataSource("genesyscloud_journey_outcome", dataSourceJourneyOutcome()) @@ -52,7 +51,6 @@ func registerResources(l registrar.Registrar) { l.RegisterResource("genesyscloud_architect_schedules", ResourceArchitectSchedules()) l.RegisterResource("genesyscloud_architect_user_prompt", ResourceArchitectUserPrompt()) l.RegisterResource("genesyscloud_auth_division", ResourceAuthDivision()) - l.RegisterResource("genesyscloud_group", ResourceGroup()) l.RegisterResource("genesyscloud_idp_adfs", ResourceIdpAdfs()) l.RegisterResource("genesyscloud_idp_generic", ResourceIdpGeneric()) l.RegisterResource("genesyscloud_idp_gsuite", ResourceIdpGsuite()) @@ -93,7 +91,6 @@ func registerExporters(l registrar.Registrar) { l.RegisterExporter("genesyscloud_architect_schedules", ArchitectSchedulesExporter()) l.RegisterExporter("genesyscloud_architect_user_prompt", ArchitectUserPromptExporter()) l.RegisterExporter("genesyscloud_auth_division", AuthDivisionExporter()) - l.RegisterExporter("genesyscloud_group", GroupExporter()) l.RegisterExporter("genesyscloud_idp_adfs", IdpAdfsExporter()) l.RegisterExporter("genesyscloud_idp_generic", IdpGenericExporter()) l.RegisterExporter("genesyscloud_idp_gsuite", IdpGsuiteExporter()) diff --git a/genesyscloud/resource_genesyscloud_init_test.go b/genesyscloud/resource_genesyscloud_init_test.go index 58ce07876..4facb06ba 100644 --- a/genesyscloud/resource_genesyscloud_init_test.go +++ b/genesyscloud/resource_genesyscloud_init_test.go @@ -5,6 +5,7 @@ import ( "sync" "terraform-provider-genesyscloud/genesyscloud/architect_flow" archScheduleGroup "terraform-provider-genesyscloud/genesyscloud/architect_schedulegroups" + "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" "testing" @@ -31,11 +32,11 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_flow"] = architect_flow.ResourceArchitectFlow() providerResources["genesyscloud_routing_queue"] = ResourceRoutingQueue() + providerResources["genesyscloud_group"] = group.ResourceGroup() providerResources["genesyscloud_location"] = ResourceLocation() providerResources["genesyscloud_architect_schedules"] = ResourceArchitectSchedules() providerResources["genesyscloud_architect_user_prompt"] = ResourceArchitectUserPrompt() providerResources["genesyscloud_auth_division"] = ResourceAuthDivision() - providerResources["genesyscloud_group"] = ResourceGroup() providerResources["genesyscloud_idp_adfs"] = ResourceIdpAdfs() providerResources["genesyscloud_idp_generic"] = ResourceIdpGeneric() providerResources["genesyscloud_idp_gsuite"] = ResourceIdpGsuite() @@ -76,6 +77,7 @@ func (r *registerTestInstance) registerTestDataSources() { defer r.datasourceMapMutex.Unlock() providerDataSources["genesyscloud_flow"] = architect_flow.DataSourceArchitectFlow() + providerDataSources["genesyscloud_group"] = group.DataSourceGroup() providerDataSources["genesyscloud_routing_wrapupcode"] = DataSourceRoutingWrapupcode() providerDataSources["genesyscloud_routing_queue"] = DataSourceRoutingQueue() providerDataSources["genesyscloud_location"] = DataSourceLocation() @@ -84,7 +86,6 @@ func (r *registerTestInstance) registerTestDataSources() { providerDataSources["genesyscloud_architect_user_prompt"] = dataSourceUserPrompt() providerDataSources["genesyscloud_auth_division"] = dataSourceAuthDivision() providerDataSources["genesyscloud_auth_division_home"] = DataSourceAuthDivisionHome() - providerDataSources["genesyscloud_group"] = DataSourceGroup() providerDataSources["genesyscloud_journey_action_map"] = dataSourceJourneyActionMap() providerDataSources["genesyscloud_journey_action_template"] = dataSourceJourneyActionTemplate() providerDataSources["genesyscloud_journey_outcome"] = dataSourceJourneyOutcome() diff --git a/genesyscloud/resource_genesyscloud_orgauthorization_pairing_test.go b/genesyscloud/resource_genesyscloud_orgauthorization_pairing_test.go index b844e2b8c..4b4193127 100644 --- a/genesyscloud/resource_genesyscloud_orgauthorization_pairing_test.go +++ b/genesyscloud/resource_genesyscloud_orgauthorization_pairing_test.go @@ -2,6 +2,7 @@ package genesyscloud import ( "fmt" + "strings" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -35,14 +36,14 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { Steps: []resource.TestStep{ // 1 user and 1 group { - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicUserResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicUserResource( userResource1, email1, userName1, - ) + GenerateBasicGroupResource( + ) + generateBasicGroupResource( groupResource1, groupName1, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + fmt.Sprintf(`resource "genesyscloud_orgauthorization_pairing" "%s" { user_ids = [genesyscloud_user.%s.id] group_ids = [genesyscloud_group.%s.id] @@ -54,7 +55,7 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { }, // 2 users and 2 groups { - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicUserResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicUserResource( userResource1, email1, userName1, @@ -62,14 +63,14 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { userResource2, email2, userName2, - ) + GenerateBasicGroupResource( + ) + generateBasicGroupResource( groupResource1, groupName1, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), - ) + GenerateBasicGroupResource( + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), + ) + generateBasicGroupResource( groupResource2, groupName2, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + fmt.Sprintf(`resource "genesyscloud_orgauthorization_pairing" "%s" { user_ids = [genesyscloud_user.%s.id, genesyscloud_user.%s.id] group_ids = [genesyscloud_group.%s.id, genesyscloud_group.%s.id] @@ -123,10 +124,10 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { }, // 1 group { - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateBasicGroupResource( groupResource1, groupName1, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + fmt.Sprintf(`resource "genesyscloud_orgauthorization_pairing" "%s" { group_ids = [genesyscloud_group.%s.id] }`, orgAuthorizationPairingResource, groupResource1), @@ -136,14 +137,14 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { }, // 2 groups { - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateBasicGroupResource( groupResource1, groupName1, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), - ) + GenerateBasicGroupResource( + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), + ) + generateBasicGroupResource( groupResource2, groupName2, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + generateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + fmt.Sprintf(`resource "genesyscloud_orgauthorization_pairing" "%s" { group_ids = [genesyscloud_group.%s.id, genesyscloud_group.%s.id] }`, orgAuthorizationPairingResource, groupResource1, groupResource2), @@ -164,3 +165,41 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { }, }) } + +func generateBasicGroupResource(resourceID string, name string, nestedBlocks ...string) string { + return generateGroupResource(resourceID, name, util.NullValue, util.NullValue, util.NullValue, util.TrueValue, nestedBlocks...) +} + +func generateGroupResource( + resourceID string, + name string, + desc string, + groupType string, + visibility string, + rulesVisible string, + nestedBlocks ...string) string { + return fmt.Sprintf(`resource "genesyscloud_group" "%s" { + name = "%s" + description = %s + type = %s + visibility = %s + rules_visible = %s + %s + } + `, resourceID, name, desc, groupType, visibility, rulesVisible, strings.Join(nestedBlocks, "\n")) +} + +func generateGroupOwners(userIDs ...string) string { + return fmt.Sprintf(`owner_ids = [%s] + `, strings.Join(userIDs, ",")) +} + +// TODO: Duplicating this code within the function to not break a cyclic dependency +func generateUserWithCustomAttrs(resourceID string, email string, name string, attrs ...string) string { + return fmt.Sprintf(`resource "genesyscloud_user" "%s" { + email = "%s" + name = "%s" + %s + } + `, resourceID, email, name, strings.Join(attrs, "\n")) +} diff --git a/genesyscloud/resource_genesyscloud_routing_queue_test.go b/genesyscloud/resource_genesyscloud_routing_queue_test.go index 4ea0f6ee5..7fbe78237 100644 --- a/genesyscloud/resource_genesyscloud_routing_queue_test.go +++ b/genesyscloud/resource_genesyscloud_routing_queue_test.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud/architect_flow" + "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -56,15 +57,15 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateRoutingSkillResource(queueSkillResource, queueSkillName) + + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateRoutingSkillResource(queueSkillResource, queueSkillName) + generateGroupResource( bullseyeMemberGroupName, - "MySeries6Group", + "MySeries6Groupv2", strconv.Quote("TestGroupForSeries6"), util.NullValue, // Default type util.NullValue, // Default visibility util.NullValue, // Default rules_visible - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + GenerateRoutingQueueResource( queueResource1, queueName1, @@ -269,10 +270,10 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { }, { // Update - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + group.GenerateBasicGroupResource( groupResourceId, groupName, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + generateRoutingQueueResourceBasic( queueResource2, @@ -1575,9 +1576,9 @@ func TestAccResourceRoutingQueueSkillGroups(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: GenerateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateRoutingSkillGroupResourceBasic(skillGroupResource, skillGroupName, skillGroupDescription) + - GenerateBasicGroupResource(groupResource, groupName, - GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateRoutingSkillGroupResourceBasic(skillGroupResource, skillGroupName, skillGroupDescription) + + group.GenerateBasicGroupResource(groupResource, groupName, + group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ) + GenerateRoutingQueueResourceBasicWithDepends( queueResource, diff --git a/genesyscloud/resource_genesyscloud_user.go b/genesyscloud/resource_genesyscloud_user.go index e6c1c690e..948bad7b4 100644 --- a/genesyscloud/resource_genesyscloud_user.go +++ b/genesyscloud/resource_genesyscloud_user.go @@ -1430,12 +1430,3 @@ func GenerateUserResource( } `, resourceID, email, name, state, title, department, manager, acdAutoAnswer, profileSkills, certifications) } - -func GenerateUserWithCustomAttrs(resourceID string, email string, name string, attrs ...string) string { - return fmt.Sprintf(`resource "genesyscloud_user" "%s" { - email = "%s" - name = "%s" - %s - } - `, resourceID, email, name, strings.Join(attrs, "\n")) -} diff --git a/genesyscloud/resource_genesyscloud_user_test.go b/genesyscloud/resource_genesyscloud_user_test.go index b64b6f615..6519a2eb2 100644 --- a/genesyscloud/resource_genesyscloud_user_test.go +++ b/genesyscloud/resource_genesyscloud_user_test.go @@ -188,7 +188,7 @@ func TestAccResourceUserAddresses(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, addrUserName, @@ -223,7 +223,7 @@ func TestAccResourceUserAddresses(t *testing.T) { }, { // Update phone number and other email attributes - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, addrUserName, @@ -275,7 +275,7 @@ func TestAccResourceUserPhone(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, addrUserName, @@ -297,7 +297,7 @@ func TestAccResourceUserPhone(t *testing.T) { ), }, { - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, addrUserName, @@ -319,7 +319,7 @@ func TestAccResourceUserPhone(t *testing.T) { ), }, { - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, addrUserName, @@ -341,7 +341,7 @@ func TestAccResourceUserPhone(t *testing.T) { ), }, { - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, addrUserName, @@ -388,7 +388,7 @@ func TestAccResourceUserSkills(t *testing.T) { Steps: []resource.TestStep{ { // Create user with 1 skill - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -400,7 +400,7 @@ func TestAccResourceUserSkills(t *testing.T) { }, { // Create another skill and add to the user - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -420,7 +420,7 @@ func TestAccResourceUserSkills(t *testing.T) { }, { // Remove a skill from the user and modify proficiency - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -435,7 +435,7 @@ func TestAccResourceUserSkills(t *testing.T) { }, { // Remove all skills from the user - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -470,7 +470,7 @@ func TestAccResourceUserLanguages(t *testing.T) { Steps: []resource.TestStep{ { // Create user with 1 language - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -482,7 +482,7 @@ func TestAccResourceUserLanguages(t *testing.T) { }, { // Create another language and add to the user - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -502,7 +502,7 @@ func TestAccResourceUserLanguages(t *testing.T) { }, { // Remove a language from the user and modify proficiency - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -517,7 +517,7 @@ func TestAccResourceUserLanguages(t *testing.T) { }, { // Remove all languages from the user - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName1, @@ -552,7 +552,7 @@ func TestAccResourceUserLocations(t *testing.T) { Steps: []resource.TestStep{ { // Create user with a location - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email, userName, @@ -569,7 +569,7 @@ func TestAccResourceUserLocations(t *testing.T) { }, { // Update with a new location - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email, userName, @@ -611,7 +611,7 @@ func TestAccResourceUserEmployerInfo(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -633,7 +633,7 @@ func TestAccResourceUserEmployerInfo(t *testing.T) { }, { // Update with other attributes - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -655,7 +655,7 @@ func TestAccResourceUserEmployerInfo(t *testing.T) { }, { // Update all attributes - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -677,7 +677,7 @@ func TestAccResourceUserEmployerInfo(t *testing.T) { }, { // Remove all employer info attributes - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -713,7 +713,7 @@ func TestAccResourceUserRoutingUtil(t *testing.T) { Steps: []resource.TestStep{ { // Create with utilization settings - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -746,7 +746,7 @@ func TestAccResourceUserRoutingUtil(t *testing.T) { }, { // Update utilization settings and set different org-level settings - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -779,7 +779,7 @@ func TestAccResourceUserRoutingUtil(t *testing.T) { }, { // Ensure max capacity can be set to 0 - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -812,7 +812,7 @@ func TestAccResourceUserRoutingUtil(t *testing.T) { }, { // Reset to org-level settings by specifying empty routing utilization attribute - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, @@ -862,7 +862,7 @@ func TestAccResourceUserRoutingUtilWithLabels(t *testing.T) { Config: GenerateRoutingUtilizationLabelResource(redLabelResource, redLabelName, "") + GenerateRoutingUtilizationLabelResource(blueLabelResource, blueLabelName, redLabelResource) + GenerateRoutingUtilizationLabelResource(greenLabelResource, greenLabelName, blueLabelResource) + - GenerateUserWithCustomAttrs( + generateUserWithCustomAttrs( userResource1, email1, userName, @@ -904,7 +904,7 @@ func TestAccResourceUserRoutingUtilWithLabels(t *testing.T) { Config: GenerateRoutingUtilizationLabelResource(redLabelResource, redLabelName, "") + GenerateRoutingUtilizationLabelResource(blueLabelResource, blueLabelName, redLabelResource) + GenerateRoutingUtilizationLabelResource(greenLabelResource, greenLabelName, blueLabelResource) + - GenerateUserWithCustomAttrs( + generateUserWithCustomAttrs( userResource1, email1, userName, @@ -946,7 +946,7 @@ func TestAccResourceUserRoutingUtilWithLabels(t *testing.T) { Config: GenerateRoutingUtilizationLabelResource(redLabelResource, redLabelName, "") + GenerateRoutingUtilizationLabelResource(blueLabelResource, blueLabelName, redLabelResource) + GenerateRoutingUtilizationLabelResource(greenLabelResource, greenLabelName, blueLabelResource) + - GenerateUserWithCustomAttrs( + generateUserWithCustomAttrs( userResource1, email1, userName, @@ -985,7 +985,7 @@ func TestAccResourceUserRoutingUtilWithLabels(t *testing.T) { }, { // Reset to org-level settings by specifying empty routing utilization attribute - Config: GenerateUserWithCustomAttrs( + Config: generateUserWithCustomAttrs( userResource1, email1, userName, diff --git a/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings.go b/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings.go index 256ac1563..95a9c0f41 100644 --- a/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings.go +++ b/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings.go @@ -194,6 +194,7 @@ func updateTrunkBaseSettings(ctx context.Context, d *schema.ResourceData, meta i } return resp, nil }) + if diagErr != nil { return diagErr } diff --git a/genesyscloud/tfexporter/tf_exporter_resource_test.go b/genesyscloud/tfexporter/tf_exporter_resource_test.go index 615c531e2..a40096f67 100644 --- a/genesyscloud/tfexporter/tf_exporter_resource_test.go +++ b/genesyscloud/tfexporter/tf_exporter_resource_test.go @@ -6,6 +6,7 @@ import ( "terraform-provider-genesyscloud/genesyscloud/architect_datatable_row" emergencyGroup "terraform-provider-genesyscloud/genesyscloud/architect_emergencygroup" flow "terraform-provider-genesyscloud/genesyscloud/architect_flow" + "terraform-provider-genesyscloud/genesyscloud/group" grammar "terraform-provider-genesyscloud/genesyscloud/architect_grammar" grammarLanguage "terraform-provider-genesyscloud/genesyscloud/architect_grammar_language" @@ -97,7 +98,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_auth_role"] = authRole.ResourceAuthRole() providerResources["genesyscloud_auth_division"] = gcloud.ResourceAuthDivision() providerResources["genesyscloud_employeeperformance_externalmetrics_definitions"] = employeeperformanceExternalmetricsDefinition.ResourceEmployeeperformanceExternalmetricsDefinition() - providerResources["genesyscloud_group"] = gcloud.ResourceGroup() + providerResources["genesyscloud_group"] = group.ResourceGroup() providerResources["genesyscloud_group_roles"] = groupRoles.ResourceGroupRoles() providerResources["genesyscloud_idp_adfs"] = gcloud.ResourceIdpAdfs() providerResources["genesyscloud_idp_generic"] = gcloud.ResourceIdpGeneric() @@ -190,7 +191,7 @@ func (r *registerTestInstance) registerTestExporters() { RegisterExporter("genesyscloud_flow", flow.ArchitectFlowExporter()) RegisterExporter("genesyscloud_flow_milestone", flowMilestone.FlowMilestoneExporter()) RegisterExporter("genesyscloud_flow_outcome", flowOutcome.FlowOutcomeExporter()) - RegisterExporter("genesyscloud_group", gcloud.GroupExporter()) + RegisterExporter("genesyscloud_group", group.GroupExporter()) RegisterExporter("genesyscloud_group_roles", groupRoles.GroupRolesExporter()) RegisterExporter("genesyscloud_idp_adfs", gcloud.IdpAdfsExporter()) RegisterExporter("genesyscloud_idp_generic", gcloud.IdpGenericExporter()) diff --git a/go.sum b/go.sum index 89ff76046..9b618433d 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -24,6 +25,7 @@ github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= @@ -45,6 +47,7 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= @@ -55,24 +58,30 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -85,6 +94,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -194,7 +204,9 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -202,16 +214,20 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d h1:2puqoOQwi3Ai1oznMOsFIbifm6kIfJaLLyYzWD4IzTs= github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d/go.mod h1:hO90vCP2x3exaSH58BIAowSKvV+0OsY21TtzuFGHON4= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= @@ -266,6 +282,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -287,16 +304,19 @@ github.com/rjNemo/underscore v0.6.1/go.mod h1:PwVP2XGRgIpWUkPbb8huhJ9xNWk+0xv9gM github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -338,6 +358,7 @@ github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21 github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= @@ -413,6 +434,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -519,6 +541,7 @@ gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index f3911796d..c5cdabb03 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( externalContacts "terraform-provider-genesyscloud/genesyscloud/external_contacts" flowMilestone "terraform-provider-genesyscloud/genesyscloud/flow_milestone" flowOutcome "terraform-provider-genesyscloud/genesyscloud/flow_outcome" + "terraform-provider-genesyscloud/genesyscloud/group" groupRoles "terraform-provider-genesyscloud/genesyscloud/group_roles" "terraform-provider-genesyscloud/genesyscloud/integration" integrationAction "terraform-provider-genesyscloud/genesyscloud/integration_action" @@ -191,6 +192,7 @@ func registerResources() { responsemanagementResponse.SetRegistrar(regInstance) //Registering responsemanagement responses responsemanagementResponseasset.SetRegistrar(regInstance) //Registering responsemanagement response asset respmanagementLibrary.SetRegistrar(regInstance) //Registering responsemanagement library + group.SetRegistrar(regInstance) //Registering group // setting resources for Use cases like TF export where provider is used in resource classes. tfexp.SetRegistrar(regInstance) //Registering tf exporter