diff --git a/docs/data-sources/routing_email_route.md b/docs/data-sources/routing_email_route.md new file mode 100644 index 000000000..4d3cd4fca --- /dev/null +++ b/docs/data-sources/routing_email_route.md @@ -0,0 +1,25 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "genesyscloud_routing_email_route Data Source - terraform-provider-genesyscloud" +subcategory: "" +description: |- + Data source for Genesys Cloud Routing Email Route. Select a routing email route by pattern and domain ID. +--- + +# genesyscloud_routing_email_route (Data Source) + +Data source for Genesys Cloud Routing Email Route. Select a routing email route by pattern and domain ID. + + + + +## Schema + +### Required + +- `domain_id` (String) Domain of the route. +- `pattern` (String) Routing pattern. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md index 424ecef22..af9225f66 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/user.md @@ -3,12 +3,12 @@ page_title: "genesyscloud_user Data Source - terraform-provider-genesyscloud" subcategory: "" description: |- - Data source for Genesys Cloud Users. Select a user by email or name. + Data source for Genesys Cloud Users. Select a user by email or name. If both email & name are specified, the name won't be used for user lookup --- # genesyscloud_user (Data Source) -Data source for Genesys Cloud Users. Select a user by email or name. +Data source for Genesys Cloud Users. Select a user by email or name. If both email & name are specified, the name won't be used for user lookup ## Example Usage diff --git a/docs/resources/idp_adfs.md b/docs/resources/idp_adfs.md index a732ac83f..ee0c86fd6 100644 --- a/docs/resources/idp_adfs.md +++ b/docs/resources/idp_adfs.md @@ -37,7 +37,10 @@ resource "genesyscloud_idp_adfs" "adfs" { ### Optional - `disabled` (Boolean) True if ADFS is disabled. Defaults to `false`. +- `name` (String) IDP ADFS resource name - `relying_party_identifier` (String) String used to identify Genesys Cloud to ADFS. +- `slo_binding` (String) Valid values: HTTP Redirect, HTTP Post +- `slo_uri` (String) Provided by ADSF on app creation - `target_uri` (String) Target URI provided by ADFS. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) diff --git a/docs/resources/idp_okta.md b/docs/resources/idp_okta.md index 5f355fa3d..2d407422f 100644 --- a/docs/resources/idp_okta.md +++ b/docs/resources/idp_okta.md @@ -35,7 +35,11 @@ resource "genesyscloud_idp_okta" "okta" { ### Optional -- `disabled` (Boolean) True if Okta is disabled. Defaults to `false`. +- `disabled` (Boolean) True if Okta is disabled. +- `name` (String) IDP Okta name +- `relying_party_identifier` (String) String used to identify Genesys Cloud to Okta. +- `slo_binding` (String) Valid values: HTTP Redirect, HTTP Post +- `slo_uri` (String) Provided by Okta on app creation. - `target_uri` (String) Target URI provided by Okta. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) diff --git a/docs/resources/journey_segment.md b/docs/resources/journey_segment.md index de55fcbeb..66173abf6 100644 --- a/docs/resources/journey_segment.md +++ b/docs/resources/journey_segment.md @@ -64,10 +64,8 @@ resource "genesyscloud_journey_segment" "example_journey_segment_resource" { ### Optional -- `assignment_expiration_days` (Number) Time, in days, from when the segment is assigned until it is automatically unassigned. - `context` (Block Set, Max: 1) The context of the segment. (see [below for nested schema](#nestedblock--context)) - `description` (String) A description of the segment. -- `external_segment` (Block Set, Max: 1) Details of an entity corresponding to this segment in an external system. (see [below for nested schema](#nestedblock--external_segment)) - `is_active` (Boolean) Whether or not the segment is active. Defaults to `true`. - `journey` (Block Set, Max: 1) The pattern of rules defining the segment. (see [below for nested schema](#nestedblock--journey)) - `should_display_to_agent` (Boolean) Whether or not the segment should be displayed to agent/supervisor users. @@ -107,16 +105,6 @@ Optional: - -### Nested Schema for `external_segment` - -Required: - -- `id` (String) Identifier for the external segment in the system where it originates from. Changing the id attribute will cause the journey_segment resource to be dropped and recreated with a new ID. -- `name` (String) Name for the external segment in the system where it originates from. -- `source` (String) The external system where the segment originates from.Valid values: AdobeExperiencePlatform, Custom. Changing the source attribute will cause the journey_segment resource to be dropped and recreated with a new ID. - - ### Nested Schema for `journey` diff --git a/docs/resources/journey_views.md b/docs/resources/journey_views.md new file mode 100644 index 000000000..f91e9cf28 --- /dev/null +++ b/docs/resources/journey_views.md @@ -0,0 +1,147 @@ +--- +page_title: "genesyscloud_journey_views Resource - terraform-provider-genesyscloud" +subcategory: "" +description: |- + Genesys Cloud Directory JourneyView +--- +# genesyscloud_journey_views (Resource) + +Genesys Cloud Directory JourneyView + +## API Usage +The following Genesys Cloud APIs are used by this resource. Ensure your OAuth Client has been granted the necessary scopes and permissions to perform these operations: + +* [POST /api/v2/journey/views](https://developer.genesys.cloud/platform/preview-apis#post-api-v2-journey-views) +* [POST /api/v2/journey/views/{viewId}/versions](https://developer.genesys.cloud/platform/preview-apis#post-api-v2-journey-views--viewId--versions) +* [GET /api/v2/journey/views/{viewId}](https://developer.genesys.cloud/platform/preview-apis#get-api-v2-journey-views--viewId-) +* [DELETE /api/v2/journey/views/{viewId}](https://developer.genesys.cloud/platform/preview-apis#delete-api-v2-journey-views--viewId-) + +## Example Usage + +```terraform +resource "genesyscloud_journey_views" "journey_view" { + duration = "P1Y" + name = "Sample Journey 1" + elements { + id = "ac6c61b5-1cd4-4c6e-a8a5-edb74d9117eb" + name = "Wrap Up" + attributes { + type = "Event" + id = "a416328b-167c-0365-d0e1-f072cd5d4ded" + source = "Voice" + } + filter { + type = "And" + predicates { + dimension = "mediaType" + values = ["VOICE"] + operator = "Matches" + no_value = false + } + } + } +} +``` + + +## Schema + +### Required + +- `name` (String) JourneyView name. + +### Optional + +- `description` (String) A description of the journey view. +- `duration` (String) A relative timeframe for the journey view, expressed as an ISO 8601 duration. Only one of interval or duration must be specified. Periods are represented as an ISO-8601 string. For example: P1D or P1DT12H. +- `elements` (Block List) The elements within the journey view. (see [below for nested schema](#nestedblock--elements)) +- `interval` (String) An absolute timeframe for the journey view, expressed as an ISO 8601 interval. Only one of interval or duration must be specified. Intervals are represented as an ISO-8601 string. For example: YYYY-MM-DDThh:mm:ss/YYYY-MM-DDThh:mm:ss. + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `elements` + +Required: + +- `attributes` (Block List, Min: 1, Max: 1) Attributes on an element in a journey view. (see [below for nested schema](#nestedblock--elements--attributes)) +- `id` (String) The unique identifier of the element within the elements list. +- `name` (String) The unique name of the element within the view. + +Optional: + +- `filter` (Block List, Max: 1) A set of filters on an element within a journey view. (see [below for nested schema](#nestedblock--elements--filter)) +- `followed_by` (Block List) A list of JourneyViewLink objects, listing the elements downstream of this element. (see [below for nested schema](#nestedblock--elements--followed_by)) + + +### Nested Schema for `elements.attributes` + +Required: + +- `type` (String) The type of the element (e.g. Event).Valid values: Event. + +Optional: + +- `id` (String) The identifier for the element based on its type. +- `source` (String) The source for the element (e.g. IVR, Voice, Chat). Used for informational purposes only. + + + +### Nested Schema for `elements.filter` + +Required: + +- `type` (String) Boolean operation to apply to the provided predicates and clauses. Valid values: And. + +Optional: + +- `predicates` (Block List) A filter on an element within a journey view. (see [below for nested schema](#nestedblock--elements--filter--predicates)) + + +### Nested Schema for `elements.filter.predicates` + +Required: + +- `dimension` (String) The element's attribute being filtered on +- `values` (List of String) The identifier for the element based on its type. + +Optional: + +- `no_value` (Boolean) set this to true if no specific value to be considered. +- `operator` (String) Optional operator, default is Matches. Valid values: Matches.Valid values: Matches, NotMatches. Defaults to `Matches`. + + + + +### Nested Schema for `elements.followed_by` + +Required: + +- `id` (String) The identifier of the element downstream. + +Optional: + +- `constraint_after` (Block List, Max: 1) A time constraint on this link, which requires a customer must complete the downstream element after this amount of time to be counted.. (see [below for nested schema](#nestedblock--elements--followed_by--constraint_after)) +- `constraint_within` (Block List, Max: 1) A time constraint on this link, which requires a customer to complete the downstream element within this amount of time to be counted. (see [below for nested schema](#nestedblock--elements--followed_by--constraint_within)) +- `event_count_type` (String) The type of events that will be counted. Note: Concurrent will override any JourneyViewLinkTimeConstraint. Default is Sequential.Valid values: All, Concurrent, Sequential. +- `join_attributes` (List of String) Other (secondary) attributes on which this link should join the customers being counted. + + +### Nested Schema for `elements.followed_by.constraint_after` + +Optional: + +- `unit` (String) The unit for the link's time constraint.Valid values: Seconds, Minutes, Hours, Days, Weeks, Months. +- `value` (Number) The value for the link's time constraint. + + + +### Nested Schema for `elements.followed_by.constraint_within` + +Optional: + +- `unit` (String) The unit for the link's time constraint.Valid values: Seconds, Minutes, Hours, Days, Weeks, Months. +- `value` (Number) The value for the link's time constraint. + diff --git a/docs/resources/recording_media_retention_policy.md b/docs/resources/recording_media_retention_policy.md index 910f16037..d1da60df2 100644 --- a/docs/resources/recording_media_retention_policy.md +++ b/docs/resources/recording_media_retention_policy.md @@ -11,11 +11,11 @@ Genesys Cloud Media Retention Policies ## API Usage The following Genesys Cloud APIs are used by this resource. Ensure your OAuth Client has been granted the necessary scopes and permissions to perform these operations: -* [GET /api/v2/recording/crossplatform/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/analyticsdatamanagement/recording/#get-api-v2-recording-crossplatform-mediaretentionpolicies--policyId-) -* [GET /api/v2/recording/crossplatform/mediaretentionpolicies](https://developer.genesys.cloud/analyticsdatamanagement/recording/#get-api-v2-recording-crossplatform-mediaretentionpolicies) -* [PUT /api/v2/recording/crossplatform/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/analyticsdatamanagement/recording/#put-api-v2-recording-crossplatform-mediaretentionpolicies--policyId-) -* [POST /api/v2/recording/crossplatform/mediaretentionpolicies](https://developer.genesys.cloud/analyticsdatamanagement/recording/#post-api-v2-recording-crossplatform-mediaretentionpolicies) -* [DELETE /api/v2/recording/crossplatform/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/analyticsdatamanagement/recording/#delete-api-v2-recording-crossplatform-mediaretentionpolicies--policyId-) +* [GET /api/v2/recording/mediaretentionpolicies](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-recording-mediaretentionpolicies) +* [POST /api/v2/recording/mediaretentionpolicies](https://developer.genesys.cloud/devapps/api-explorer#post-api-v2-recording-mediaretentionpolicies) +* [GET /api/v2/recording/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-recording-mediaretentionpolicies--policyId-) +* [PUT /api/v2/recording/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/devapps/api-explorer#put-api-v2-recording-mediaretentionpolicies--policyId-) +* [DELETE /api/v2/recording/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/devapps/api-explorer#delete-api-v2-recording-mediaretentionpolicies--policyId-) * [GET /api/v2/quality/forms/evaluations/{formId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-quality-forms-evaluations--formId-) * [GET /api/v2/quality/forms/evaluations/{formId}/versions](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-quality-forms-evaluations--formId--versions) * [GET /api/v2/quality/forms/surveys](https://developer.genesys.cloud/api/rest/v2/quality/#get-api-v2-quality-forms-surveys) diff --git a/examples/resources/genesyscloud_journey_views/apis.md b/examples/resources/genesyscloud_journey_views/apis.md new file mode 100644 index 000000000..d304e6b6c --- /dev/null +++ b/examples/resources/genesyscloud_journey_views/apis.md @@ -0,0 +1,4 @@ +* [POST /api/v2/journey/views](https://developer.genesys.cloud/platform/preview-apis#post-api-v2-journey-views) +* [POST /api/v2/journey/views/{viewId}/versions](https://developer.genesys.cloud/platform/preview-apis#post-api-v2-journey-views--viewId--versions) +* [GET /api/v2/journey/views/{viewId}](https://developer.genesys.cloud/platform/preview-apis#get-api-v2-journey-views--viewId-) +* [DELETE /api/v2/journey/views/{viewId}](https://developer.genesys.cloud/platform/preview-apis#delete-api-v2-journey-views--viewId-) \ No newline at end of file diff --git a/examples/resources/genesyscloud_journey_views/resource.tf b/examples/resources/genesyscloud_journey_views/resource.tf new file mode 100644 index 000000000..c523a5f2e --- /dev/null +++ b/examples/resources/genesyscloud_journey_views/resource.tf @@ -0,0 +1,22 @@ +resource "genesyscloud_journey_views" "journey_view" { + duration = "P1Y" + name = "Sample Journey 1" + elements { + id = "ac6c61b5-1cd4-4c6e-a8a5-edb74d9117eb" + name = "Wrap Up" + attributes { + type = "Event" + id = "a416328b-167c-0365-d0e1-f072cd5d4ded" + source = "Voice" + } + filter { + type = "And" + predicates { + dimension = "mediaType" + values = ["VOICE"] + operator = "Matches" + no_value = false + } + } + } +} \ No newline at end of file diff --git a/examples/resources/genesyscloud_recording_media_retention_policy/apis.md b/examples/resources/genesyscloud_recording_media_retention_policy/apis.md index 88fb9c61c..728b3a773 100644 --- a/examples/resources/genesyscloud_recording_media_retention_policy/apis.md +++ b/examples/resources/genesyscloud_recording_media_retention_policy/apis.md @@ -1,8 +1,8 @@ -* [GET /api/v2/recording/crossplatform/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/analyticsdatamanagement/recording/#get-api-v2-recording-crossplatform-mediaretentionpolicies--policyId-) -* [GET /api/v2/recording/crossplatform/mediaretentionpolicies](https://developer.genesys.cloud/analyticsdatamanagement/recording/#get-api-v2-recording-crossplatform-mediaretentionpolicies) -* [PUT /api/v2/recording/crossplatform/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/analyticsdatamanagement/recording/#put-api-v2-recording-crossplatform-mediaretentionpolicies--policyId-) -* [POST /api/v2/recording/crossplatform/mediaretentionpolicies](https://developer.genesys.cloud/analyticsdatamanagement/recording/#post-api-v2-recording-crossplatform-mediaretentionpolicies) -* [DELETE /api/v2/recording/crossplatform/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/analyticsdatamanagement/recording/#delete-api-v2-recording-crossplatform-mediaretentionpolicies--policyId-) +* [GET /api/v2/recording/mediaretentionpolicies](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-recording-mediaretentionpolicies) +* [POST /api/v2/recording/mediaretentionpolicies](https://developer.genesys.cloud/devapps/api-explorer#post-api-v2-recording-mediaretentionpolicies) +* [GET /api/v2/recording/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-recording-mediaretentionpolicies--policyId-) +* [PUT /api/v2/recording/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/devapps/api-explorer#put-api-v2-recording-mediaretentionpolicies--policyId-) +* [DELETE /api/v2/recording/mediaretentionpolicies/{policyId}](https://developer.genesys.cloud/devapps/api-explorer#delete-api-v2-recording-mediaretentionpolicies--policyId-) * [GET /api/v2/quality/forms/evaluations/{formId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-quality-forms-evaluations--formId-) * [GET /api/v2/quality/forms/evaluations/{formId}/versions](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-quality-forms-evaluations--formId--versions) * [GET /api/v2/quality/forms/surveys](https://developer.genesys.cloud/api/rest/v2/quality/#get-api-v2-quality-forms-surveys) diff --git a/genesyscloud/architect_schedules/resource_genesyscloud_architect_schedules.go b/genesyscloud/architect_schedules/resource_genesyscloud_architect_schedules.go index 9feb24117..b040cd8a9 100644 --- a/genesyscloud/architect_schedules/resource_genesyscloud_architect_schedules.go +++ b/genesyscloud/architect_schedules/resource_genesyscloud_architect_schedules.go @@ -88,7 +88,7 @@ func createArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta msg = "\nYou must have all divisions and future divisions selected in your OAuth client role" } - return util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to create schedule %s | Error: %s MSG: %s", *scheduleResponse.Name, err, msg), proxyResponse) + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create schedule %s | Error: %s. %s", name, err, msg), proxyResponse) } d.SetId(*scheduleResponse.Id) @@ -169,7 +169,7 @@ func updateArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta scheduleResponse, proxyResponse, err := proxy.getArchitectSchedulesById(ctx, d.Id()) if err != nil { - return proxyResponse, util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to read schedule %s error: %s", d.Id(), err), proxyResponse) + return proxyResponse, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read schedule %s error: %s", d.Id(), err), proxyResponse) } log.Printf("Updating schedule %s", name) @@ -188,7 +188,7 @@ func updateArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta msg = "\nYou must have all divisions and future divisions selected in your OAuth client role" } - return proxyUpdResponse, util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to update schedule %s | Error: %s MSG: %s", *scheduleResponse.Name, putErr, msg), proxyUpdResponse) + return proxyUpdResponse, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update schedule %s | Error: %s. %s", name, putErr, msg), proxyUpdResponse) } return proxyUpdResponse, nil }) @@ -211,7 +211,7 @@ func deleteArchitectSchedules(ctx context.Context, d *schema.ResourceData, meta log.Printf("Deleting schedule %s", d.Id()) proxyDelResponse, err := proxy.deleteArchitectSchedules(ctx, d.Id()) if err != nil { - return proxyDelResponse, util.BuildAPIDiagnosticError("genesyscloud_archiect_schedules", fmt.Sprintf("Failed to delete schedule %s error: %s", d.Id(), err), proxyDelResponse) + return proxyDelResponse, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete schedule %s error: %s", d.Id(), err), proxyDelResponse) } return proxyDelResponse, nil }) diff --git a/genesyscloud/data_source_genesyscloud_auth_division_test.go b/genesyscloud/data_source_genesyscloud_auth_division_test.go index 7936299d0..2fbde2513 100644 --- a/genesyscloud/data_source_genesyscloud_auth_division_test.go +++ b/genesyscloud/data_source_genesyscloud_auth_division_test.go @@ -14,9 +14,9 @@ import ( func TestAccDataSourceAuthDivision(t *testing.T) { var ( - divResource = "auth-div" + divResource = "auth-division" divDataSource = "auth-div-data" - divName = "Terraform Division-" + uuid.NewString() + divName = "Terraform Divisions-" + uuid.NewString() ) resource.Test(t, resource.TestCase{ @@ -24,6 +24,9 @@ func TestAccDataSourceAuthDivision(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { + PreConfig: func() { + time.Sleep(30 * time.Second) + }, Config: GenerateAuthDivisionResource( divResource, divName, diff --git a/genesyscloud/data_source_genesyscloud_journey_action_map_test.go b/genesyscloud/data_source_genesyscloud_journey_action_map_test.go index 47d334cf6..586a97ae7 100644 --- a/genesyscloud/data_source_genesyscloud_journey_action_map_test.go +++ b/genesyscloud/data_source_genesyscloud_journey_action_map_test.go @@ -11,7 +11,6 @@ import ( ) func TestAccDataSourceJourneyActionMap(t *testing.T) { - t.Skip("Customer segment not implemented") runDataJourneyActionMapTestCase(t, "find_by_name") } diff --git a/genesyscloud/data_source_genesyscloud_journey_segment_test.go b/genesyscloud/data_source_genesyscloud_journey_segment_test.go index 0f6ded016..a440b600d 100644 --- a/genesyscloud/data_source_genesyscloud_journey_segment_test.go +++ b/genesyscloud/data_source_genesyscloud_journey_segment_test.go @@ -11,7 +11,6 @@ import ( ) func TestAccDataSourceJourneySegment(t *testing.T) { - t.Skip("Customer segment not implemented") runDataJourneySegmentTestCase(t, "find_by_name") } diff --git a/genesyscloud/data_source_genesyscloud_routing_skill.go b/genesyscloud/data_source_genesyscloud_routing_skill.go index bd9d0a7cb..b211fde38 100644 --- a/genesyscloud/data_source_genesyscloud_routing_skill.go +++ b/genesyscloud/data_source_genesyscloud_routing_skill.go @@ -3,7 +3,9 @@ package genesyscloud import ( "context" "fmt" + "log" "terraform-provider-genesyscloud/genesyscloud/provider" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" "terraform-provider-genesyscloud/genesyscloud/util" "time" @@ -14,10 +16,12 @@ import ( "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" ) +// The context is now added without Timeout , +// since the warming up of cache will take place for the first Datasource registered during a Terraform Apply. func dataSourceRoutingSkill() *schema.Resource { return &schema.Resource{ - Description: "Data source for Genesys Cloud Routing Skills. Select a skill by name.", - ReadContext: provider.ReadWithPooledClient(dataSourceRoutingSkillRead), + Description: "Data source for Genesys Cloud Routing Skills. Select a skill by name.", + ReadWithoutTimeout: provider.ReadWithPooledClient(dataSourceRoutingSkillRead), Schema: map[string]*schema.Schema{ "name": { Description: "Skill name.", @@ -28,24 +32,81 @@ func dataSourceRoutingSkill() *schema.Resource { } } -func dataSourceRoutingSkillRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - const pageSize = 100 - var pageCount int +var ( + dataSourceRoutingSkillCache *rc.DataSourceCache +) +func dataSourceRoutingSkillRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sdkConfig := m.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - name := d.Get("name").(string) - skills, resp, getErr := routingAPI.GetRoutingSkills(pageSize, 1, name, nil) + key := d.Get("name").(string) + + if dataSourceRoutingSkillCache == nil { + dataSourceRoutingSkillCache = rc.NewDataSourceCache(sdkConfig, hydrateRoutingSkillCacheFn, getSkillByNameFn) + } + + queueId, err := rc.RetrieveId(dataSourceRoutingSkillCache, "genesyscloud_routing_skill", key, ctx) + + if err != nil { + return err + } + + d.SetId(queueId) + return nil +} + +func hydrateRoutingSkillCacheFn(c *rc.DataSourceCache) error { + log.Printf("hydrating cache for data source genesyscloud_routing_skill") + + routingApi := platformclientv2.NewRoutingApiWithConfig(c.ClientConfig) + const pageSize = 100 + skills, _, getErr := routingApi.GetRoutingSkills(pageSize, 1, "", nil) + if getErr != nil { - return util.BuildAPIDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("Error requesting skills error: %s", getErr), resp) + return fmt.Errorf("failed to get page of skills: %v", getErr) + } + + if skills.Entities == nil || len(*skills.Entities) == 0 { + return nil } - pageCount = *skills.PageCount + + for _, skill := range *skills.Entities { + c.Cache[*skill.Name] = *skill.Id + } + + for pageNum := 2; pageNum <= *skills.PageCount; pageNum++ { + + log.Printf("calling cache for data source genesyscloud_routing_skill") + + skills, _, getErr := routingApi.GetRoutingSkills(pageSize, pageNum, "", nil) + log.Printf("calling cache for data source genesyscloud_routing_skill %v", pageNum) + if getErr != nil { + return fmt.Errorf("failed to get page of skills: %v", getErr) + } + + if skills.Entities == nil || len(*skills.Entities) == 0 { + break + } + + // Add ids to cache + for _, skill := range *skills.Entities { + c.Cache[*skill.Name] = *skill.Id + } + } + + log.Printf("cache hydration completed for data source genesyscloud_routing_skill") + + return nil +} + +func getSkillByNameFn(c *rc.DataSourceCache, name string, ctx context.Context) (string, diag.Diagnostics) { + const pageSize = 100 + skillId := "" + routingAPI := platformclientv2.NewRoutingApiWithConfig(c.ClientConfig) // Find first non-deleted skill by name. Retry in case new skill is not yet indexed by search - return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - for pageNum := 1; pageNum <= pageCount; pageNum++ { - const pageSize = 100 + diag := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + for pageNum := 1; ; pageNum++ { skills, resp, getErr := routingAPI.GetRoutingSkills(pageSize, pageNum, name, nil) if getErr != nil { return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("error requesting skill %s | error: %s", name, getErr), resp)) @@ -58,11 +119,11 @@ func dataSourceRoutingSkillRead(ctx context.Context, d *schema.ResourceData, m i for _, skill := range *skills.Entities { if skill.Name != nil && *skill.Name == name && skill.State != nil && *skill.State != "deleted" { - d.SetId(*skill.Id) + skillId = *skill.Id return nil } } } - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_skill", fmt.Sprintf("no routing skills found with name %s", name), resp)) }) + return skillId, diag } diff --git a/genesyscloud/data_source_genesyscloud_user.go b/genesyscloud/data_source_genesyscloud_user.go index e0a79d275..b74a5b487 100644 --- a/genesyscloud/data_source_genesyscloud_user.go +++ b/genesyscloud/data_source_genesyscloud_user.go @@ -3,7 +3,10 @@ package genesyscloud import ( "context" "fmt" + "log" + "net/mail" "terraform-provider-genesyscloud/genesyscloud/provider" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" "terraform-provider-genesyscloud/genesyscloud/util" "time" @@ -16,8 +19,8 @@ import ( func DataSourceUser() *schema.Resource { return &schema.Resource{ - Description: "Data source for Genesys Cloud Users. Select a user by email or name.", - ReadContext: provider.ReadWithPooledClient(DataSourceUserRead), + Description: "Data source for Genesys Cloud Users. Select a user by email or name. If both email & name are specified, the name won't be used for user lookup", + ReadWithoutTimeout: provider.ReadWithPooledClient(DataSourceUserRead), Schema: map[string]*schema.Schema{ "email": { Description: "User email.", @@ -33,32 +36,99 @@ func DataSourceUser() *schema.Resource { } } +var ( + dataSourceUserCache *rc.DataSourceCache +) + func DataSourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sdkConfig := m.(*provider.ProviderMeta).ClientConfig - usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + key := "" + + if email, ok := d.GetOk("email"); ok { + key = email.(string) + + } + if name, ok := d.GetOk("name"); ok { + key = name.(string) + + } + if d.Get("name").(string) == "" && d.Get("email").(string) == "" { + return util.BuildDiagnosticError("genesyscloud_user", "no user search field specified", nil) + } + + if dataSourceUserCache == nil { + dataSourceUserCache = rc.NewDataSourceCache(sdkConfig, hydrateUserCacheFn, getUserByNameFn) + } + + userId, err := rc.RetrieveId(dataSourceUserCache, "genesyscloud_user", key, ctx) + if err != nil { + return err + } + + d.SetId(userId) + return nil +} + +func hydrateUserCacheFn(c *rc.DataSourceCache) error { + log.Printf("hydrating cache for data source genesyscloud_user") + const pageSize = 100 + usersAPI := platformclientv2.NewUsersApiWithConfig(c.ClientConfig) + + users, response, err := usersAPI.GetUsers(pageSize, 1, nil, nil, "", nil, "", "") + + if err != nil { + return fmt.Errorf("failed to get first page of users: %v %v", err, response) + } + + if users.Entities == nil || len(*users.Entities) == 0 { + return nil + } + for _, user := range *users.Entities { + c.Cache[*user.Name] = *user.Id + c.Cache[*user.Email] = *user.Id + + } + + for pageNum := 2; pageNum <= *users.PageCount; pageNum++ { + + users, response, err := usersAPI.GetUsers(pageSize, pageNum, nil, nil, "", nil, "", "") + + log.Printf("hydrating cache for data source genesyscloud_user with page number: %v", pageNum) + if err != nil { + return fmt.Errorf("failed to get page of users: %v %v", err, response) + } + if users.Entities == nil || len(*users.Entities) == 0 { + break + } + // Add ids to cache + for _, user := range *users.Entities { + c.Cache[*user.Name] = *user.Id + c.Cache[*user.Email] = *user.Id + + } + } + + log.Printf("cache hydration completed for data source genesyscloud_user") + + return nil +} + +func getUserByNameFn(c *rc.DataSourceCache, searchField string, ctx context.Context) (string, diag.Diagnostics) { + userId := "" + usersAPI := platformclientv2.NewUsersApiWithConfig(c.ClientConfig) exactSearchType := "EXACT" sortOrderAsc := "ASC" emailField := "email" - nameField := "name" searchCriteria := platformclientv2.Usersearchcriteria{ VarType: &exactSearchType, } - if email, ok := d.GetOk("email"); ok { - emailStr := email.(string) - searchCriteria.Fields = &[]string{emailField} - searchCriteria.Value = &emailStr - } else if name, ok := d.GetOk("name"); ok { - nameStr := name.(string) - searchCriteria.Fields = &[]string{nameField} - searchCriteria.Value = &nameStr - } else { - return util.BuildDiagnosticError("genesyscloud_user", fmt.Sprintf("No user search field specified"), fmt.Errorf("no user search field specified")) - } + searchFieldValue, searchFieldType := emailorNameDisambiguation(searchField) + searchCriteria.Fields = &[]string{searchFieldType} + searchCriteria.Value = &searchFieldValue - // Retry in case user is not yet indexed - return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + diag := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { users, resp, getErr := usersAPI.PostUsersSearch(platformclientv2.Usersearchrequest{ SortBy: &emailField, SortOrder: &sortOrderAsc, @@ -73,8 +143,19 @@ func DataSourceUserRead(ctx context.Context, d *schema.ResourceData, m interface } // Select first user in the list - user := (*users.Results)[0] - d.SetId(*user.Id) + userId = *(*users.Results)[0].Id return nil }) + return userId, diag + +} + +func emailorNameDisambiguation(searchField string) (string, string) { + emailField := "email" + nameField := "name" + _, err := mail.ParseAddress(searchField) + if err == nil { + return searchField, emailField + } + return searchField, nameField } diff --git a/genesyscloud/data_source_genesyscloud_user_test.go b/genesyscloud/data_source_genesyscloud_user_test.go index 310215d33..f5c639ac1 100644 --- a/genesyscloud/data_source_genesyscloud_user_test.go +++ b/genesyscloud/data_source_genesyscloud_user_test.go @@ -14,8 +14,9 @@ func TestAccDataSourceUser(t *testing.T) { var ( userResource = "test-user" userDataSource = "test-user-data" - userEmail = "terraform-" + uuid.NewString() + "@example.com" - userName = "John Data-" + uuid.NewString() + randomString = uuid.NewString() + userEmail = "John_Doe" + randomString + "@example.com" + userName = "John_Doe" + randomString ) resource.Test(t, resource.TestCase{ diff --git a/genesyscloud/group/data_source_genesyscloud_group_test.go b/genesyscloud/group/data_source_genesyscloud_group_test.go index dc5257a94..f6bbb0f6b 100644 --- a/genesyscloud/group/data_source_genesyscloud_group_test.go +++ b/genesyscloud/group/data_source_genesyscloud_group_test.go @@ -2,17 +2,25 @@ package group import ( "fmt" + "log" + "sync" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" "time" "github.com/google/uuid" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) +var ( + sdkConfig *platformclientv2.Configuration + mu sync.Mutex +) + func TestAccDataSourceGroup(t *testing.T) { var ( groupResource = "test-group-members" @@ -20,7 +28,8 @@ func TestAccDataSourceGroup(t *testing.T) { groupName = "test group" + uuid.NewString() testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@examplegroup.com" + userID string ) resource.Test(t, resource.TestCase{ @@ -44,11 +53,24 @@ func TestAccDataSourceGroup(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair("data.genesyscloud_group."+groupDataSource, "id", "genesyscloud_group."+groupResource, "id"), func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for resources to get deleted properly + rs, ok := s.RootModule().Resources["genesyscloud_user."+testUserResource] + if !ok { + return fmt.Errorf("not found: %s", "genesyscloud_user."+testUserResource) + } + userID = rs.Primary.ID + log.Printf("User ID: %s\n", userID) // Print user ID return nil }, ), }, + { + ResourceName: "genesyscloud_user." + testUserResource, + ImportState: true, + ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + checkUserDeleted(userID), + ), + }, }, }) } @@ -65,3 +87,45 @@ func generateGroupDataSource( } `, resourceID, name, dependsOnResource) } + +func checkUserDeleted(id string) resource.TestCheckFunc { + log.Printf("Fetching user with ID: %s\n", id) + return func(s *terraform.State) error { + maxAttempts := 18 + for i := 0; i < maxAttempts; i++ { + + deleted, err := isUserDeleted(id) + if err != nil { + return err + } + if deleted { + return nil + } + time.Sleep(10 * time.Second) + } + return fmt.Errorf("user %s was not deleted properly", id) + } +} + +func isUserDeleted(id string) (bool, error) { + mu.Lock() + defer mu.Unlock() + + usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + // Attempt to get the user + _, response, err := usersAPI.GetUser(id, nil, "", "") + + // Check if the user is not found (deleted) + if response != nil && response.StatusCode == 404 { + return true, nil // User is deleted + } + + // Handle other errors + if err != nil { + log.Printf("Error fetching user: %v", err) + return false, err + } + + // If user is found, it means the user is not deleted + return false, nil +} diff --git a/genesyscloud/group/resource_genesyscloud_group_test.go b/genesyscloud/group/resource_genesyscloud_group_test.go index 727d08c23..930dc3d9a 100644 --- a/genesyscloud/group/resource_genesyscloud_group_test.go +++ b/genesyscloud/group/resource_genesyscloud_group_test.go @@ -2,6 +2,7 @@ package group import ( "fmt" + "log" "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud/provider" @@ -26,7 +27,7 @@ func TestAccResourceGroupBasic(t *testing.T) { visMembers = "members" testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@group.com" ) resource.Test(t, resource.TestCase{ @@ -99,7 +100,7 @@ func TestAccResourceGroupAddresses(t *testing.T) { typeGroupPhone = "GROUPPHONE" testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@groupadd.com" ) resource.Test(t, resource.TestCase{ @@ -201,13 +202,14 @@ func TestAccResourceGroupMembers(t *testing.T) { groupName = "Terraform Test Group-" + uuid.NewString() userResource1 = "group-user1" userResource2 = "group-user2" - userEmail1 = "terraform1-" + uuid.NewString() + "@example.com" - userEmail2 = "terraform2-" + uuid.NewString() + "@example.com" + userEmail1 = "terraform1-" + uuid.NewString() + "@groupmem.com" + userEmail2 = "terraform2-" + uuid.NewString() + "@groupmem.com" userName1 = "Johnny Terraform" userName2 = "Ryan Terraform" testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@groupmem.com" + userID string ) resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, @@ -297,16 +299,23 @@ func TestAccResourceGroupMembers(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckNoResourceAttr("genesyscloud_group."+groupResource, "member_ids.%"), func(s *terraform.State) error { - time.Sleep(45 * time.Second) // Wait for 30 seconds for resources to get deleted properly + rs, ok := s.RootModule().Resources["genesyscloud_user."+testUserResource] + if !ok { + return fmt.Errorf("not found: %s", "genesyscloud_user."+testUserResource) + } + userID = rs.Primary.ID + log.Printf("User ID: %s\n", userID) // Print user ID return nil }, ), }, { - // Import/Read - ResourceName: "genesyscloud_group." + groupResource, + ResourceName: "genesyscloud_user." + testUserResource, ImportState: true, ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + checkUserDeleted(userID), + ), }, }, CheckDestroy: testVerifyGroupsDestroyed, diff --git a/genesyscloud/idp_adfs/genesyscloud_idp_adfs_proxy.go b/genesyscloud/idp_adfs/genesyscloud_idp_adfs_proxy.go new file mode 100644 index 000000000..23910c006 --- /dev/null +++ b/genesyscloud/idp_adfs/genesyscloud_idp_adfs_proxy.go @@ -0,0 +1,96 @@ +package idp_adfs + +import ( + "context" + + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" +) + +/* +The genesyscloud_idp_adfs_proxy.go file contains the proxy structures and methods that interact +with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed +out during testing. +*/ + +// internalProxy holds a proxy instance that can be used throughout the package +var internalProxy *idpAdfsProxy + +// Type definitions for each func on our proxy so we can easily mock them out later +type getAllIdpAdfsFunc func(ctx context.Context, p *idpAdfsProxy) (*platformclientv2.Adfs, *platformclientv2.APIResponse, error) +type updateIdpAdfsFunc func(ctx context.Context, p *idpAdfsProxy, id string, aDFS *platformclientv2.Adfs) (resp *platformclientv2.APIResponse, err error) +type deleteIdpAdfsFunc func(ctx context.Context, p *idpAdfsProxy, id string) (resp *platformclientv2.APIResponse, err error) + +// idpAdfsProxy contains all of the methods that call genesys cloud APIs. +type idpAdfsProxy struct { + clientConfig *platformclientv2.Configuration + identityProviderApi *platformclientv2.IdentityProviderApi + getAllIdpAdfsAttr getAllIdpAdfsFunc + updateIdpAdfsAttr updateIdpAdfsFunc + deleteIdpAdfsAttr deleteIdpAdfsFunc +} + +// newIdpAdfsProxy initializes the idp adfs proxy with all of the data needed to communicate with Genesys Cloud +func newIdpAdfsProxy(clientConfig *platformclientv2.Configuration) *idpAdfsProxy { + api := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig) + return &idpAdfsProxy{ + clientConfig: clientConfig, + identityProviderApi: api, + getAllIdpAdfsAttr: getAllIdpAdfsFn, + updateIdpAdfsAttr: updateIdpAdfsFn, + deleteIdpAdfsAttr: deleteIdpAdfsFn, + } +} + +// getIdpAdfsProxy acts as a singleton to for the internalProxy. It also ensures +// that we can still proxy our tests by directly setting internalProxy package variable +func getIdpAdfsProxy(clientConfig *platformclientv2.Configuration) *idpAdfsProxy { + if internalProxy == nil { + internalProxy = newIdpAdfsProxy(clientConfig) + } + + return internalProxy +} + +// getIdpAdfs retrieves all Genesys Cloud idp adfs +func (p *idpAdfsProxy) getIdpAdfs(ctx context.Context) (*platformclientv2.Adfs, *platformclientv2.APIResponse, error) { + return p.getAllIdpAdfsAttr(ctx, p) +} + +// updateIdpAdfs updates a Genesys Cloud idp adfs +func (p *idpAdfsProxy) updateIdpAdfs(ctx context.Context, id string, idpAdfs *platformclientv2.Adfs) (resp *platformclientv2.APIResponse, err error) { + return p.updateIdpAdfsAttr(ctx, p, id, idpAdfs) +} + +// deleteIdpAdfs deletes a Genesys Cloud idp adfs by Id +func (p *idpAdfsProxy) deleteIdpAdfs(ctx context.Context, id string) (resp *platformclientv2.APIResponse, err error) { + return p.deleteIdpAdfsAttr(ctx, p, id) +} + +// getAllIdpAdfsFn is the implementation for retrieving all idp adfs in Genesys Cloud +func getAllIdpAdfsFn(ctx context.Context, p *idpAdfsProxy) (*platformclientv2.Adfs, *platformclientv2.APIResponse, error) { + adfs, resp, err := p.identityProviderApi.GetIdentityprovidersAdfs() + if err != nil { + return nil, resp, err + } + + return adfs, resp, nil +} + +// updateIdpAdfsFn is an implementation of the function to update a Genesys Cloud idp adfs +func updateIdpAdfsFn(ctx context.Context, p *idpAdfsProxy, id string, idpAdfs *platformclientv2.Adfs) (statusCode *platformclientv2.APIResponse, err error) { + _, resp, err := p.identityProviderApi.PutIdentityprovidersAdfs(*idpAdfs) + if err != nil { + return resp, err + } + return resp, nil +} + +// deleteIdpAdfsFn is an implementation function for deleting a Genesys Cloud idp adfs +func deleteIdpAdfsFn(ctx context.Context, p *idpAdfsProxy, id string) (response *platformclientv2.APIResponse, err error) { + _, resp, err := p.identityProviderApi.DeleteIdentityprovidersAdfs() + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs.go b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs.go new file mode 100644 index 000000000..1367be58b --- /dev/null +++ b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs.go @@ -0,0 +1,145 @@ +package idp_adfs + +import ( + "context" + "fmt" + "log" + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + + "terraform-provider-genesyscloud/genesyscloud/consistency_checker" + + "terraform-provider-genesyscloud/genesyscloud/util/constants" + "terraform-provider-genesyscloud/genesyscloud/util/lists" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +/* +The resource_genesyscloud_idp_adfs.go contains all of the methods that perform the core logic for a resource. +*/ + +// getAllAuthIdpAdfss retrieves all of the idp adfs via Terraform in the Genesys Cloud and is used for the exporter +func getAllAuthIdpAdfss(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + proxy := getIdpAdfsProxy(clientConfig) + resources := make(resourceExporter.ResourceIDMetaMap) + + _, resp, err := proxy.getIdpAdfs(ctx) + if err != nil { + if util.IsStatus404(resp) { + // Don't export if config doesn't exist + return resources, nil + } + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get IDP ADFS error: %s", err), resp) + } + resources["0"] = &resourceExporter.ResourceMeta{Name: "adfs"} + return resources, nil +} + +// createIdpAdfs is used by the idp_adfs resource to create Genesys cloud idp adfs +func createIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + log.Printf("Creating IDP ADFS") + d.SetId("adfs") + return updateIdpAdfs(ctx, d, meta) +} + +// readIdpAdfs is used by the idp_adfs resource to read an idp adfs from genesys cloud +func readIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getIdpAdfsProxy(sdkConfig) + + log.Printf("Reading idp adfs %s", d.Id()) + + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpAdfs(), constants.DefaultConsistencyChecks, resourceName) + + return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError { + aDFS, resp, getErr := proxy.getIdpAdfs(ctx) + if getErr != nil { + if util.IsStatus404(resp) { + createIdpAdfs(ctx, d, meta) + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read IDP ADFS: %s", getErr), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read IDP ADFS: %s", getErr), resp)) + } + + resourcedata.SetNillableValue(d, "disabled", aDFS.Disabled) + resourcedata.SetNillableValue(d, "issuer_uri", aDFS.IssuerURI) + resourcedata.SetNillableValue(d, "target_uri", aDFS.SsoTargetURI) + resourcedata.SetNillableValue(d, "relying_party_identifier", aDFS.RelyingPartyIdentifier) + + if aDFS.Certificate != nil { + d.Set("certificates", lists.StringListToInterfaceList([]string{*aDFS.Certificate})) + } else if aDFS.Certificates != nil { + d.Set("certificates", lists.StringListToInterfaceList(*aDFS.Certificates)) + } else { + d.Set("certificates", nil) + } + + log.Printf("Read idp adfs") + return cc.CheckState(d) + }) +} + +// updateIdpAdfs is used by the idp_adfs resource to update an idp adfs in Genesys Cloud +func updateIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getIdpAdfsProxy(sdkConfig) + + idpAdfs := getIdpAdfsFromResourceData(d) + certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates") + if certificates != nil { + if len(*certificates) == 1 { + idpAdfs.Certificate = &(*certificates)[0] + } + idpAdfs.Certificates = certificates + } + log.Printf("Updating idp adfs") + resp, err := proxy.updateIdpAdfs(ctx, d.Id(), &idpAdfs) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update IDP ADFS %s error: %s", d.Id(), err), resp) + } + + log.Printf("Updated idp adfs") + return readIdpAdfs(ctx, d, meta) +} + +// deleteIdpAdfs is used by the idp_adfs resource to delete an idp adfs from Genesys cloud +func deleteIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getIdpAdfsProxy(sdkConfig) + + resp, err := proxy.deleteIdpAdfs(ctx, d.Id()) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete idp adfs %s error: %s", d.Id(), err), resp) + } + + return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError { + _, resp, err := proxy.getIdpAdfs(ctx) + if err != nil { + if util.IsStatus404(resp) { + // IDP ADFS deleted + log.Printf("Deleted IDP ADFS") + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting IDP ADFS: %s", err), resp)) + } + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("IDP ADFS still exists"), resp)) + }) +} + +// getIdpAdfsFromResourceData maps data from schema ResourceData object to a platformclientv2.Adfs +func getIdpAdfsFromResourceData(d *schema.ResourceData) platformclientv2.Adfs { + return platformclientv2.Adfs{ + Disabled: platformclientv2.Bool(d.Get("disabled").(bool)), + IssuerURI: platformclientv2.String(d.Get("issuer_uri").(string)), + SsoTargetURI: platformclientv2.String(d.Get("target_uri").(string)), + RelyingPartyIdentifier: platformclientv2.String(d.Get("relying_party_identifier").(string)), + } +} diff --git a/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_init_test.go b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_init_test.go new file mode 100644 index 000000000..dc753b58e --- /dev/null +++ b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_init_test.go @@ -0,0 +1,51 @@ +package idp_adfs + +import ( + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* + The genesyscloud_idp_adfs_init_test.go file is used to initialize the data sources and resources + used in testing the idp_adfs resource. +*/ + +// providerDataSources holds a map of all registered datasources +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["genesyscloud_idp_adfs"] = ResourceIdpAdfs() +} + +// initTestResources initializes all test_data resources and data sources. +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() +} + +// TestMain is a "setup" function called by the testing framework when run the test_data +func TestMain(m *testing.M) { + // Run setup function before starting the test_data suite for the idp_adfs package + initTestResources() + + // Run the test_data suite for the idp_adfs package + m.Run() +} diff --git a/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_schema.go b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_schema.go new file mode 100644 index 000000000..beb08620b --- /dev/null +++ b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_schema.go @@ -0,0 +1,103 @@ +package idp_adfs + +import ( + "time" + + "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" +) + +/* +resource_genesycloud_idp_adfs_schema.go holds four functions within it: + +1. The registration code that registers the Datasource, Resource and Exporter for the package. +2. The resource schema definitions for the idp_adfs resource. +3. The datasource schema definitions for the idp_adfs datasource. +4. The resource exporter configuration for the idp_adfs exporter. +*/ +const resourceName = "genesyscloud_idp_adfs" + +// SetRegistrar registers all of the resources, datasources and exporters in the package +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceIdpAdfs()) + regInstance.RegisterExporter(resourceName, IdpAdfsExporter()) +} + +// ResourceIdpAdfs registers the genesyscloud_idp_adfs resource with Terraform +func ResourceIdpAdfs() *schema.Resource { + return &schema.Resource{ + Description: `Genesys Cloud Single Sign-on ADFS Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-microsoft-adfs-single-sign-provider/`, + + CreateContext: provider.CreateWithPooledClient(createIdpAdfs), + ReadContext: provider.ReadWithPooledClient(readIdpAdfs), + UpdateContext: provider.UpdateWithPooledClient(updateIdpAdfs), + DeleteContext: provider.DeleteWithPooledClient(deleteIdpAdfs), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Timeouts: &schema.ResourceTimeout{ + Update: schema.DefaultTimeout(8 * time.Minute), + Read: schema.DefaultTimeout(8 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + `name`: { + Description: `IDP ADFS resource name`, + Optional: true, + Type: schema.TypeString, + }, + `disabled`: { + Description: `True if ADFS is disabled.`, + Optional: true, + Type: schema.TypeBool, + Default: false, + }, + `issuer_uri`: { + Description: `Issuer URI provided by ADFS.`, + Required: true, + Type: schema.TypeString, + }, + `target_uri`: { + Description: `Target URI provided by ADFS.`, + Optional: true, + Type: schema.TypeString, + }, + `slo_uri`: { + Description: `Provided by ADSF on app creation`, + Optional: true, + Type: schema.TypeString, + }, + `slo_binding`: { + Description: `Valid values: HTTP Redirect, HTTP Post`, + Optional: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{`HTTP Redirect`, `HTTP Post`}, false), + }, + `relying_party_identifier`: { + Description: `String used to identify Genesys Cloud to ADFS.`, + Optional: true, + Type: schema.TypeString, + }, + `certificates`: { + Description: `PEM or DER encoded public X.509 certificates for SAML signature validation.`, + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +// IdpAdfsExporter returns the resourceExporter object used to hold the genesyscloud_idp_adfs exporter's config +func IdpAdfsExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthIdpAdfss), + RefAttrs: map[string]*resourceExporter.RefAttrSettings{ + // TODO: Add any reference attributes here + }, + } +} diff --git a/genesyscloud/resource_genesyscloud_idp_adfs_test.go b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_test.go similarity index 99% rename from genesyscloud/resource_genesyscloud_idp_adfs_test.go rename to genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_test.go index 6310bab6e..5b8cc3391 100644 --- a/genesyscloud/resource_genesyscloud_idp_adfs_test.go +++ b/genesyscloud/idp_adfs/resource_genesyscloud_idp_adfs_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package idp_adfs import ( "fmt" diff --git a/genesyscloud/idp_okta/genesyscloud_idp_okta_proxy.go b/genesyscloud/idp_okta/genesyscloud_idp_okta_proxy.go new file mode 100644 index 000000000..d40d622d0 --- /dev/null +++ b/genesyscloud/idp_okta/genesyscloud_idp_okta_proxy.go @@ -0,0 +1,88 @@ +package idp_okta + +import ( + "context" + "fmt" + + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" +) + +/* +The genesyscloud_idp_okta_proxy.go file contains the proxy structures and methods that interact +with the Genesys Cloud SDK. We use composition here for each function on the proxy so individual functions can be stubbed +out during testing. +*/ + +// internalProxy holds a proxy instance that can be used throughout the package +var internalProxy *idpOktaProxy + +// Type definitions for each func on our proxy so we can easily mock them out later +type getIdpOktaFunc func(ctx context.Context, p *idpOktaProxy) (*platformclientv2.Okta, *platformclientv2.APIResponse, error) +type updateIdpOktaFunc func(ctx context.Context, p *idpOktaProxy, id string, okta *platformclientv2.Okta) (*platformclientv2.Identityprovider, *platformclientv2.APIResponse, error) +type deleteIdpOktaFunc func(ctx context.Context, p *idpOktaProxy, id string) (response *platformclientv2.APIResponse, err error) + +// idpOktaProxy contains all of the methods that call genesys cloud APIs. +type idpOktaProxy struct { + clientConfig *platformclientv2.Configuration + identityProviderApi *platformclientv2.IdentityProviderApi + getIdpOktaAttr getIdpOktaFunc + updateIdpOktaAttr updateIdpOktaFunc + deleteIdpOktaAttr deleteIdpOktaFunc +} + +// newIdpOktaProxy initializes the idp okta proxy with all of the data needed to communicate with Genesys Cloud +func newIdpOktaProxy(clientConfig *platformclientv2.Configuration) *idpOktaProxy { + api := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig) + return &idpOktaProxy{ + clientConfig: clientConfig, + identityProviderApi: api, + getIdpOktaAttr: getIdpOktaFn, + updateIdpOktaAttr: updateIdpOktaFn, + deleteIdpOktaAttr: deleteIdpOktaFn, + } +} + +// getIdpOktaProxy acts as a singleton to for the internalProxy. It also ensures +// that we can still proxy our tests by directly setting internalProxy package variable +func getIdpOktaProxy(clientConfig *platformclientv2.Configuration) *idpOktaProxy { + if internalProxy == nil { + internalProxy = newIdpOktaProxy(clientConfig) + } + + return internalProxy +} + +// getIdpOkta retrieves all Genesys Cloud idp okta +func (p *idpOktaProxy) getIdpOkta(ctx context.Context) (*platformclientv2.Okta, *platformclientv2.APIResponse, error) { + return p.getIdpOktaAttr(ctx, p) +} + +// updateIdpOkta updates a Genesys Cloud idp okta +func (p *idpOktaProxy) updateIdpOkta(ctx context.Context, id string, idpOkta *platformclientv2.Okta) (*platformclientv2.Identityprovider, *platformclientv2.APIResponse, error) { + return p.updateIdpOktaAttr(ctx, p, id, idpOkta) +} + +// deleteIdpOkta deletes a Genesys Cloud idp okta by Id +func (p *idpOktaProxy) deleteIdpOkta(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) { + return p.deleteIdpOktaAttr(ctx, p, id) +} + +// getIdpOktaFn is the implementation for retrieving all idp okta in Genesys Cloud +func getIdpOktaFn(ctx context.Context, p *idpOktaProxy) (*platformclientv2.Okta, *platformclientv2.APIResponse, error) { + return p.identityProviderApi.GetIdentityprovidersOkta() +} + +// updateIdpOktaFn is an implementation of the function to update a Genesys Cloud idp okta +func updateIdpOktaFn(ctx context.Context, p *idpOktaProxy, id string, idpOkta *platformclientv2.Okta) (*platformclientv2.Identityprovider, *platformclientv2.APIResponse, error) { + return p.identityProviderApi.PutIdentityprovidersOkta(*idpOkta) +} + +// deleteIdpOktaFn is an implementation function for deleting a Genesys Cloud idp okta +func deleteIdpOktaFn(ctx context.Context, p *idpOktaProxy, id string) (response *platformclientv2.APIResponse, err error) { + _, resp, err := p.identityProviderApi.DeleteIdentityprovidersOkta() + if err != nil { + return resp, fmt.Errorf("Failed to delete idp okta: %s", err) + } + + return resp, nil +} diff --git a/genesyscloud/idp_okta/resource_genesyscloud_idp_okta.go b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta.go new file mode 100644 index 000000000..2925dbc85 --- /dev/null +++ b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta.go @@ -0,0 +1,147 @@ +package idp_okta + +import ( + "context" + "fmt" + "log" + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "terraform-provider-genesyscloud/genesyscloud/consistency_checker" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + + "terraform-provider-genesyscloud/genesyscloud/util/constants" + "terraform-provider-genesyscloud/genesyscloud/util/lists" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +/* +The resource_genesyscloud_idp_okta.go contains all of the methods that perform the core logic for a resource. +*/ + +// getAllAuthIdpOkta retrieves all of the idp okta via Terraform in the Genesys Cloud and is used for the exporter +func getAllAuthIdpOktas(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + proxy := getIdpOktaProxy(clientConfig) + resources := make(resourceExporter.ResourceIDMetaMap) + + _, resp, err := proxy.getIdpOkta(ctx) + if err != nil { + if util.IsStatus404(resp) { + // Don't export if config doesn't exist + return resources, nil + } + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get IDP okta error: %s", err), resp) + } + + resources["0"] = &resourceExporter.ResourceMeta{Name: "okta"} + return resources, nil +} + +// createIdpOkta is used by the idp_okta resource to create Genesys cloud idp okta +func createIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + log.Printf("Creating IDP Okta") + d.SetId("okta") + return updateIdpOkta(ctx, d, meta) +} + +// readIdpOkta is used by the idp_okta resource to read an idp okta from genesys cloud +func readIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getIdpOktaProxy(sdkConfig) + + log.Printf("Reading idp okta %s", d.Id()) + + return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError { + okta, resp, getErr := proxy.getIdpOkta(ctx) + if getErr != nil { + if util.IsStatus404(resp) { + createIdpOkta(ctx, d, meta) + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read IDP Okta: %s", getErr), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read IDP Okta: %s", getErr), resp)) + } + + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpOkta(), constants.DefaultConsistencyChecks, "genesyscloud_idp_okta") + + resourcedata.SetNillableValue(d, "disabled", okta.Disabled) + resourcedata.SetNillableValue(d, "issuer_uri", okta.IssuerURI) + resourcedata.SetNillableValue(d, "target_uri", okta.SsoTargetURI) + + if okta.Certificate != nil { + d.Set("certificates", lists.StringListToInterfaceList([]string{*okta.Certificate})) + } else if okta.Certificates != nil { + d.Set("certificates", lists.StringListToInterfaceList(*okta.Certificates)) + } else { + d.Set("certificates", nil) + } + + log.Printf("Read idp okta") + return cc.CheckState(d) + }) +} + +// updateIdpOkta is used by the idp_okta resource to update an idp okta in Genesys Cloud +func updateIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getIdpOktaProxy(sdkConfig) + + idpOkta := getIdpOktaFromResourceData(d) + + certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates") + if certificates != nil { + if len(*certificates) == 1 { + idpOkta.Certificate = &(*certificates)[0] + } + idpOkta.Certificates = certificates + } + + log.Printf("Updating idp okta") + _, resp, err := proxy.updateIdpOkta(ctx, d.Id(), &idpOkta) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update idp okta: %s", err), resp) + } + + log.Printf("Updated idp okta") + return readIdpOkta(ctx, d, meta) +} + +// deleteIdpOkta is used by the idp_okta resource to delete an idp okta from Genesys cloud +func deleteIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getIdpOktaProxy(sdkConfig) + + resp, err := proxy.deleteIdpOkta(ctx, d.Id()) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete idp okta: %s", err), resp) + } + + return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError { + _, resp, err := proxy.getIdpOkta(ctx) + + if err != nil { + if util.IsStatus404(resp) { + // IDP Okta deleted + log.Printf("Deleted IDP Okta") + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting IDP Okta: %s", err), resp)) + } + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("IDP Okta still exists"), resp)) + }) +} + +// getIdpOktaFromResourceData maps data from schema ResourceData object to a platformclientv2.Okta +func getIdpOktaFromResourceData(d *schema.ResourceData) platformclientv2.Okta { + return platformclientv2.Okta{ + Disabled: platformclientv2.Bool(d.Get("disabled").(bool)), + IssuerURI: platformclientv2.String(d.Get("issuer_uri").(string)), + SsoTargetURI: platformclientv2.String(d.Get("target_uri").(string)), + } +} diff --git a/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_init_test.go b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_init_test.go new file mode 100644 index 000000000..2cd8b06c8 --- /dev/null +++ b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_init_test.go @@ -0,0 +1,51 @@ +package idp_okta + +import ( + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* + The genesyscloud_idp_okta_init_test.go file is used to initialize the data sources and resources + used in testing the idp_okta resource. +*/ + +// providerDataSources holds a map of all registered datasources +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["genesyscloud_idp_okta"] = ResourceIdpOkta() +} + +// initTestResources initializes all test_data resources and data sources. +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() +} + +// TestMain is a "setup" function called by the testing framework when run the test_data +func TestMain(m *testing.M) { + // Run setup function before starting the test_data suite for the idp_okta package + initTestResources() + + // Run the test_data suite for the idp_okta package + m.Run() +} diff --git a/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_schema.go b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_schema.go new file mode 100644 index 000000000..3a234a1b3 --- /dev/null +++ b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_schema.go @@ -0,0 +1,103 @@ +package idp_okta + +import ( + "time" + + "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" +) + +/* +resource_genesycloud_idp_okta_schema.go holds four functions within it: + +1. The registration code that registers the Datasource, Resource and Exporter for the package. +2. The resource schema definitions for the idp_okta resource. +3. The datasource schema definitions for the idp_okta datasource. +4. The resource exporter configuration for the idp_okta exporter. +*/ +const resourceName = "genesyscloud_idp_okta" + +// SetRegistrar registers all of the resources, datasources and exporters in the package +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceIdpOkta()) + regInstance.RegisterExporter(resourceName, IdpOktaExporter()) +} + +// ResourceIdpOkta registers the genesyscloud_idp_okta resource with Terraform +func ResourceIdpOkta() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud Single Sign-on Okta Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-okta-as-a-single-sign-on-provider/", + + CreateContext: provider.CreateWithPooledClient(createIdpOkta), + ReadContext: provider.ReadWithPooledClient(readIdpOkta), + UpdateContext: provider.UpdateWithPooledClient(updateIdpOkta), + DeleteContext: provider.DeleteWithPooledClient(deleteIdpOkta), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Timeouts: &schema.ResourceTimeout{ + Update: schema.DefaultTimeout(8 * time.Minute), + Read: schema.DefaultTimeout(8 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + `name`: { + Description: `IDP Okta name`, + Optional: true, + Type: schema.TypeString, + }, + `disabled`: { + Description: `True if Okta is disabled.`, + Optional: true, + Type: schema.TypeBool, + }, + `issuer_uri`: { + Description: `Issuer URI provided by Okta.`, + Required: true, + Type: schema.TypeString, + }, + `target_uri`: { + Description: `Target URI provided by Okta.`, + Optional: true, + Type: schema.TypeString, + }, + `slo_uri`: { + Description: `Provided by Okta on app creation.`, + Optional: true, + Type: schema.TypeString, + }, + `slo_binding`: { + Description: `Valid values: HTTP Redirect, HTTP Post`, + Optional: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{`HTTP Redirect`, `HTTP Post`}, false), + }, + `relying_party_identifier`: { + Description: `String used to identify Genesys Cloud to Okta.`, + Optional: true, + Type: schema.TypeString, + }, + `certificates`: { + Description: `PEM or DER encoded public X.509 certificates for SAML signature validation.`, + Required: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + }, + }, + } +} + +// IdpOktaExporter returns the resourceExporter object used to hold the genesyscloud_idp_okta exporter's config +func IdpOktaExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthIdpOktas), + RefAttrs: map[string]*resourceExporter.RefAttrSettings{ + // TODO: Add any reference attributes here + }, + } +} diff --git a/genesyscloud/resource_genesyscloud_idp_okta_test.go b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_test.go similarity index 99% rename from genesyscloud/resource_genesyscloud_idp_okta_test.go rename to genesyscloud/idp_okta/resource_genesyscloud_idp_okta_test.go index 22564b4f4..547eee8b3 100644 --- a/genesyscloud/resource_genesyscloud_idp_okta_test.go +++ b/genesyscloud/idp_okta/resource_genesyscloud_idp_okta_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package idp_okta import ( "fmt" diff --git a/genesyscloud/integration_custom_auth_action/data_source_genesyscloud_integration_custom_auth_action_test.go b/genesyscloud/integration_custom_auth_action/data_source_genesyscloud_integration_custom_auth_action_test.go index c73484d74..c305ebb95 100644 --- a/genesyscloud/integration_custom_auth_action/data_source_genesyscloud_integration_custom_auth_action_test.go +++ b/genesyscloud/integration_custom_auth_action/data_source_genesyscloud_integration_custom_auth_action_test.go @@ -6,6 +6,7 @@ import ( "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" + "time" integration "terraform-provider-genesyscloud/genesyscloud/integration" integrationCred "terraform-provider-genesyscloud/genesyscloud/integration_credential" @@ -66,6 +67,10 @@ func TestAccDataSourceIntegrationCustomAuthAction(t *testing.T) { Config: config, Check: resource.ComposeTestCheckFunc( testCheckCustomAuthId("data.genesyscloud_integration_custom_auth_action."+customAuthSource, "genesyscloud_integration."+integResource1), + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion + return nil + }, ), }, }, diff --git a/genesyscloud/integration_custom_auth_action/resource_genesyscloud_integration_custom_auth_action_test.go b/genesyscloud/integration_custom_auth_action/resource_genesyscloud_integration_custom_auth_action_test.go index 37e1f4c4b..193ef3266 100644 --- a/genesyscloud/integration_custom_auth_action/resource_genesyscloud_integration_custom_auth_action_test.go +++ b/genesyscloud/integration_custom_auth_action/resource_genesyscloud_integration_custom_auth_action_test.go @@ -7,6 +7,7 @@ import ( "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" + "time" "terraform-provider-genesyscloud/genesyscloud/integration" integrationCred "terraform-provider-genesyscloud/genesyscloud/integration_credential" @@ -180,6 +181,12 @@ func TestAccResourceIntegrationCustomAuthAction(t *testing.T) { ResourceName: "genesyscloud_integration_custom_auth_action." + actionResource1, ImportState: true, ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion + return nil + }, + ), }, }, CheckDestroy: testVerifyIntegrationActionDestroyed, diff --git a/genesyscloud/journey_views/genesyscloud_journey_views_init_test.go b/genesyscloud/journey_views/genesyscloud_journey_views_init_test.go new file mode 100644 index 000000000..cc684a6f1 --- /dev/null +++ b/genesyscloud/journey_views/genesyscloud_journey_views_init_test.go @@ -0,0 +1,56 @@ +package journey_views + +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 +} + +// registerTestResources registers all resources used in the tests +func (r *registerTestInstance) registerTestResources() { + r.resourceMapMutex.Lock() + defer r.resourceMapMutex.Unlock() + + providerResources[resourceName] = ResourceJourneyViews() + providerResources["genesyscloud_user"] = genesyscloud.ResourceUser() + +} + +// registerTestDataSources registers all data sources used in the tests. +/* TODO: +func (r *registerTestInstance) registerTestDataSources() { + r.datasourceMapMutex.Lock() + defer r.datasourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceGroup() +}*/ + +// initTestResources initializes all test resources and data sources. +func initTestResources() { + //TODO: providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() + //TODO: 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 journey_views package + initTestResources() + + // Run the test suite for the journey_views package + m.Run() +} diff --git a/genesyscloud/journey_views/genesyscloud_journey_views_proxy.go b/genesyscloud/journey_views/genesyscloud_journey_views_proxy.go new file mode 100644 index 000000000..929ddd59d --- /dev/null +++ b/genesyscloud/journey_views/genesyscloud_journey_views_proxy.go @@ -0,0 +1,77 @@ +package journey_views + +import ( + "context" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" +) + +var internalProxy *journeyViewsProxy + +type getJourneyViewByViewIdFunc func(ctx context.Context, p *journeyViewsProxy, viewId string) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) +type createJourneyViewFunc func(ctx context.Context, p *journeyViewsProxy, journeyView *platformclientv2.Journeyview) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) +type updateJourneyViewFunc func(ctx context.Context, p *journeyViewsProxy, viewId string, journeyView *platformclientv2.Journeyview) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) +type deleteJourneyViewFunc func(ctx context.Context, p *journeyViewsProxy, viewId string) (*platformclientv2.APIResponse, error) + +type journeyViewsProxy struct { + clientConfig *platformclientv2.Configuration + journeyViewsApi *platformclientv2.JourneyApi + getJourneyViewAttr getJourneyViewByViewIdFunc + createJourneyViewAttr createJourneyViewFunc + updateJourneyViewAttr updateJourneyViewFunc + deleteJourneyViewAttr deleteJourneyViewFunc + journeyViewCache rc.CacheInterface[platformclientv2.Journeyview] +} + +func newJourneyViewsProxy(clientConfig *platformclientv2.Configuration) *journeyViewsProxy { + api := platformclientv2.NewJourneyApiWithConfig(clientConfig) + journeyViewCache := rc.NewResourceCache[platformclientv2.Journeyview]() + return &journeyViewsProxy{ + clientConfig: clientConfig, + journeyViewsApi: api, + getJourneyViewAttr: getJourneyViewByViewIdFn, + createJourneyViewAttr: createJourneyViewFn, + updateJourneyViewAttr: updateJourneyViewFn, + deleteJourneyViewAttr: deleteJourneyViewFn, + journeyViewCache: journeyViewCache, + } +} + +func getJourneyViewProxy(clientConfig *platformclientv2.Configuration) *journeyViewsProxy { + if internalProxy == nil { + internalProxy = newJourneyViewsProxy(clientConfig) + } + return internalProxy +} + +func (p *journeyViewsProxy) getJourneyViewById(ctx context.Context, viewId string) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) { + return p.getJourneyViewAttr(ctx, p, viewId) +} + +func (p *journeyViewsProxy) createJourneyView(ctx context.Context, journeyView *platformclientv2.Journeyview) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) { + return p.createJourneyViewAttr(ctx, p, journeyView) +} + +func (p *journeyViewsProxy) updateJourneyView(ctx context.Context, viewId string, journeyView *platformclientv2.Journeyview) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) { + return p.updateJourneyViewAttr(ctx, p, viewId, journeyView) +} + +func (p *journeyViewsProxy) deleteJourneyView(ctx context.Context, viewId string) (*platformclientv2.APIResponse, error) { + return p.deleteJourneyViewAttr(ctx, p, viewId) +} + +func getJourneyViewByViewIdFn(_ context.Context, p *journeyViewsProxy, viewId string) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) { + return p.journeyViewsApi.GetJourneyView(viewId) +} + +func createJourneyViewFn(_ context.Context, p *journeyViewsProxy, journeyView *platformclientv2.Journeyview) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) { + return p.journeyViewsApi.PostJourneyViews(*journeyView) +} + +func updateJourneyViewFn(_ context.Context, p *journeyViewsProxy, viewId string, journeyView *platformclientv2.Journeyview) (*platformclientv2.Journeyview, *platformclientv2.APIResponse, error) { + return p.journeyViewsApi.PostJourneyViewVersions(viewId, *journeyView) +} + +func deleteJourneyViewFn(_ context.Context, p *journeyViewsProxy, viewId string) (*platformclientv2.APIResponse, error) { + return p.journeyViewsApi.DeleteJourneyView(viewId) +} diff --git a/genesyscloud/journey_views/resource_genesyscloud_journey_views.go b/genesyscloud/journey_views/resource_genesyscloud_journey_views.go new file mode 100644 index 000000000..525f8b15f --- /dev/null +++ b/genesyscloud/journey_views/resource_genesyscloud_journey_views.go @@ -0,0 +1,124 @@ +package journey_views + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + "log" + "terraform-provider-genesyscloud/genesyscloud/consistency_checker" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "terraform-provider-genesyscloud/genesyscloud/util/constants" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + "time" +) + +func createJourneyView(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getJourneyViewProxy(sdkConfig) + + journeyView := makeJourneyViewFromSchema(d) + log.Printf("Creating journeyView %s", name) + journeyView, resp, err := gp.createJourneyView(ctx, journeyView) + + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create journeyView %s: %s", name, err), resp) + } + d.SetId(*journeyView.Id) + log.Printf("Created journeyView with viewId: %s", d.Id()) + return readJourneyView(ctx, d, meta) +} + +func updateJourneyView(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getJourneyViewProxy(sdkConfig) + + journeyView := makeJourneyViewFromSchema(d) + log.Printf("Updating journeyView %s", d.Id()) + journeyView, resp, err := gp.updateJourneyView(ctx, d.Id(), journeyView) + + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create journeyView %s: %s", name, err), resp) + } + log.Printf("Updated journeyView %s", d.Id()) + return readJourneyView(ctx, d, meta) +} + +func readJourneyView(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + viewId := d.Id() + + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceJourneyViews(), constants.DefaultConsistencyChecks, resourceName) + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getJourneyViewProxy(sdkConfig) + log.Printf("Getting journeyView with viewId: %s", viewId) + + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + journeyView, resp, err := gp.getJourneyViewById(ctx, viewId) + if err != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get journeyView with viewId %s | error: %s", viewId, err), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to get journeyView with viewId %s | error: %s", viewId, err), resp)) + } + + resourcedata.SetNillableValue(d, "name", journeyView.Name) + resourcedata.SetNillableValue(d, "description", journeyView.Description) + resourcedata.SetNillableValue(d, "interval", journeyView.Interval) + resourcedata.SetNillableValue(d, "duration", journeyView.Duration) + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "elements", journeyView.Elements, flattenElements) + + return cc.CheckState(d) + }) +} + +func deleteJourneyView(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + viewId := d.Id() + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + gp := getJourneyViewProxy(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 journeyView with viewId %s", viewId) + resp, err := gp.deleteJourneyView(ctx, viewId) + if err != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete journeyView with viewId %s: %s", viewId, err), resp) + } + return nil, nil + }) + + return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError { + _, resp, err := gp.getJourneyViewById(ctx, viewId) + if err != nil { + if util.IsStatus404(resp) { + log.Printf("JourneyView %s deleted", viewId) + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting joruneyView with viewId %s | error: %s", viewId, err), resp)) + } + + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("JourneyView with viewId %s still exists", viewId), resp)) + }) +} + +func makeJourneyViewFromSchema(d *schema.ResourceData) *platformclientv2.Journeyview { + name := d.Get("name").(string) + description := d.Get("description").(string) + interval := d.Get("interval").(string) + duration := d.Get("duration").(string) + elements, _ := buildElements(d) + + journeyView := &platformclientv2.Journeyview{ + Name: &name, + Description: &description, + Interval: &interval, + Duration: &duration, + Elements: elements, + } + return journeyView +} diff --git a/genesyscloud/journey_views/resource_genesyscloud_journey_views_schema.go b/genesyscloud/journey_views/resource_genesyscloud_journey_views_schema.go new file mode 100644 index 000000000..0263d5ecc --- /dev/null +++ b/genesyscloud/journey_views/resource_genesyscloud_journey_views_schema.go @@ -0,0 +1,212 @@ +package journey_views + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "terraform-provider-genesyscloud/genesyscloud/provider" + registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" +) + +const resourceName = "genesyscloud_journey_views" + +var ( + constraintResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "unit": { + Description: "The unit for the link's time constraint.Valid values: Seconds, Minutes, Hours, Days, Weeks, Months.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"Seconds", "Minutes", "Hours", "Days", "Weeks", "Months"}, false), + }, + "value": { + Description: "The value for the link's time constraint.", + Type: schema.TypeInt, + Optional: true, + }, + }, + } + followedByResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The identifier of the element downstream.", + Type: schema.TypeString, + Required: true, + }, + "constraint_within": { + Description: "A time constraint on this link, which requires a customer to complete the downstream element within this amount of time to be counted.", + Type: schema.TypeList, + Elem: constraintResource, + MaxItems: 1, + Optional: true, + }, + "constraint_after": { + Description: "A time constraint on this link, which requires a customer must complete the downstream element after this amount of time to be counted..", + Type: schema.TypeList, + Elem: constraintResource, + MaxItems: 1, + Optional: true, + }, + "event_count_type": { + Description: "The type of events that will be counted. Note: Concurrent will override any JourneyViewLinkTimeConstraint. Default is Sequential.Valid values: All, Concurrent, Sequential.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"Sequential", "All", "Concurrent"}, false), + }, + "join_attributes": { + Description: "Other (secondary) attributes on which this link should join the customers being counted.", + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + }, + } + predicatesResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "dimension": { + Description: "The element's attribute being filtered on", + Type: schema.TypeString, + Required: true, + }, + "values": { + Description: "The identifier for the element based on its type.", + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + }, + "operator": { + Description: "Optional operator, default is Matches. Valid values: Matches.Valid values: Matches, NotMatches.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"Matches", "NotMatches"}, false), + Default: "Matches", + }, + "no_value": { + Description: "set this to true if no specific value to be considered.", + Type: schema.TypeBool, + Optional: true, + }, + }, + } + attributesResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Description: "The type of the element (e.g. Event).Valid values: Event.", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"Event"}, false), + }, + "id": { + Description: "The identifier for the element based on its type.", + Type: schema.TypeString, + Optional: true, + }, + "source": { + Description: "The source for the element (e.g. IVR, Voice, Chat). Used for informational purposes only.", + Type: schema.TypeString, + Optional: true, + }, + }, + } + filtersResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Description: "Boolean operation to apply to the provided predicates and clauses. Valid values: And.", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"And"}, false), + }, + "predicates": { + Description: "A filter on an element within a journey view.", + Type: schema.TypeList, + Elem: predicatesResource, + Optional: true, + }, + }, + } + elementsResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The unique identifier of the element within the elements list.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "The unique name of the element within the view.", + Type: schema.TypeString, + Required: true, + }, + "attributes": { + Description: "Attributes on an element in a journey view.", + Type: schema.TypeList, + Required: true, + Elem: attributesResource, + MaxItems: 1, + }, + "filter": { + Description: "A set of filters on an element within a journey view.", + Type: schema.TypeList, + Optional: true, + Elem: filtersResource, + MaxItems: 1, + }, + "followed_by": { + Description: "A list of JourneyViewLink objects, listing the elements downstream of this element.", + Type: schema.TypeList, + Optional: true, + Elem: followedByResource, + }, + }, + } +) + +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceJourneyViews()) + /*** + TODO: Add DataSource and Exporter once we are done with https://inindca.atlassian.net/browse/JM-109 + regInstance.RegisterDataSource(resourceName, DataSourceGroup()) + regInstance.RegisterExporter(resourceName, JourneyViewExporter()) + ***/ +} + +func ResourceJourneyViews() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud Directory JourneyView", + + CreateContext: provider.CreateWithPooledClient(createJourneyView), + ReadContext: provider.ReadWithPooledClient(readJourneyView), + UpdateContext: provider.UpdateWithPooledClient(updateJourneyView), + DeleteContext: provider.DeleteWithPooledClient(deleteJourneyView), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "name": { + Description: "JourneyView name.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "A description of the journey view.", + Type: schema.TypeString, + Optional: true, + }, + "interval": { + Description: "An absolute timeframe for the journey view, expressed as an ISO 8601 interval. Only one of interval or duration must be specified. Intervals are represented as an ISO-8601 string. For example: YYYY-MM-DDThh:mm:ss/YYYY-MM-DDThh:mm:ss.", + Type: schema.TypeString, + Optional: true, + }, + "duration": { + Description: "A relative timeframe for the journey view, expressed as an ISO 8601 duration. Only one of interval or duration must be specified. Periods are represented as an ISO-8601 string. For example: P1D or P1DT12H.", + Type: schema.TypeString, + Optional: true, + }, + "elements": { + Description: "The elements within the journey view.", + Type: schema.TypeList, + Optional: true, + Elem: elementsResource, + }, + }, + } +} diff --git a/genesyscloud/journey_views/resource_genesyscloud_journey_views_test.go b/genesyscloud/journey_views/resource_genesyscloud_journey_views_test.go new file mode 100644 index 000000000..ca39fd4c8 --- /dev/null +++ b/genesyscloud/journey_views/resource_genesyscloud_journey_views_test.go @@ -0,0 +1,164 @@ +package journey_views + +import ( + "fmt" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + "strings" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "testing" +) + +func TestAccResourceJourneyViewsBasic(t *testing.T) { + var ( + name = "test journey from tf 1" + duration = "P1Y" + elementsId = "ac6c61b5-1cd4-4c6e-a8a5-edb74d9117eb" + elementsName = "Wrap Up" + attributeType = "Event" + attributeId = "a416328b-167c-0365-d0e1-f072cd5d4ded" + attributeSource = "Voice" + filterType = "And" + predicatesDimension = "mediaType" + predicatesValues = "VOICE" + predicatesOperator = "Matches" + predicatesNoValue = false + testUserResource = "user_resource1" + testUserName = "nameUser1" + uuid.NewString() + testUserEmail = uuid.NewString() + "@example.com" + journeyResource = "journey_resource1" + emptyElementBlock = "" + ) + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, nil), + Steps: []resource.TestStep{ + { + //Create + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateJourneyView(journeyResource, name, duration, emptyElementBlock), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "name", name), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "duration", duration), + ), + }, + { + //Update + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + generateJourneyView(journeyResource, name, duration, generateElements( + elementsId, + elementsName, + generateAttributes(attributeType, attributeId, attributeSource), + generateFilter(filterType, generatePredicates(predicatesDimension, predicatesValues, predicatesOperator, predicatesNoValue)), + )), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "name", name), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "duration", duration), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.id", elementsId), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.name", elementsName), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.attributes.#", "1"), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.attributes.0.type", attributeType), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.attributes.0.id", attributeId), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.attributes.0.source", attributeSource), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.#", "1"), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.type", "And"), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.predicates.#", "1"), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.predicates.0.dimension", predicatesDimension), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.predicates.0.values.#", "1"), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.predicates.0.values.0", predicatesValues), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.predicates.0.operator", predicatesOperator), + resource.TestCheckResourceAttr("genesyscloud_journey_views."+journeyResource, "elements.0.filter.0.predicates.0.no_value", fmt.Sprintf("%t", predicatesNoValue)), + ), + }, + { + // Import/Read + ResourceName: "genesyscloud_journey_views." + journeyResource, + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testVerifyJourneyViewsDestroyed, + }) +} + +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")) +} + +func generateJourneyView(journeyResource string, name string, duration string, elementsBlock string) string { + return fmt.Sprintf(`resource "genesyscloud_journey_views" "%s" { + duration = "%s" + name = "%s" + %s +}`, journeyResource, duration, name, func() string { + if elementsBlock != "" { + return elementsBlock + } + return "" + }()) +} + +func generateElements(id string, name string, attributesBlock string, filter string) string { + return fmt.Sprintf(` + elements { + id = "%s" + name = "%s" + %s + %s + }`, id, name, attributesBlock, filter) +} + +func generateFilter(filterType string, nestedBlocks ...string) string { + return fmt.Sprintf(` + filter { + type = "%s" + %s + }`, filterType, strings.Join(nestedBlocks, "\n")) +} + +func generateAttributes(attributeType string, attributeId string, attributeSource string) string { + return fmt.Sprintf(` + attributes { + type = "%s" + id = "%s" + source = "%s" + }`, attributeType, attributeId, attributeSource) +} + +func generatePredicates(dimension string, values string, operator string, noValue bool) string { + return fmt.Sprintf(` + predicates { + dimension = "%s" + values = ["%s"] + operator = "%s" + no_value = %v + }`, dimension, values, operator, noValue) +} + +func testVerifyJourneyViewsDestroyed(state *terraform.State) error { + journeyViewApi := platformclientv2.NewJourneyApi() + for _, rs := range state.RootModule().Resources { + if rs.Type != "genesyscloud_journey_views" { + continue + } + + journeyView, resp, err := journeyViewApi.GetJourneyView(rs.Primary.ID) + if journeyView != nil { + return fmt.Errorf("journeyView (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // JourneyView not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + // Success. All journeyView destroyed + return nil +} diff --git a/genesyscloud/journey_views/resource_genesyscloud_journey_views_utils.go b/genesyscloud/journey_views/resource_genesyscloud_journey_views_utils.go new file mode 100644 index 000000000..b057f62b0 --- /dev/null +++ b/genesyscloud/journey_views/resource_genesyscloud_journey_views_utils.go @@ -0,0 +1,259 @@ +package journey_views + +import ( + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" +) + +func buildElements(d *schema.ResourceData) (*[]platformclientv2.Journeyviewelement, error) { + elementsSlice := d.Get("elements").([]interface{}) + if len(elementsSlice) == 0 { + emptySlice := make([]platformclientv2.Journeyviewelement, 0) + return &emptySlice, nil + } + + var elements []platformclientv2.Journeyviewelement + + for _, elem := range elementsSlice { + elemMap, ok := elem.(map[string]interface{}) + if !ok { + return nil, errors.New("element is not a map[string]interface{}") + } + + var element platformclientv2.Journeyviewelement + element.Id = getStringPointerFromInterface(elemMap["id"]) + element.Name = getStringPointerFromInterface(elemMap["name"]) + + if attributesSlice, ok := elemMap["attributes"].([]interface{}); ok { + attributes := buildJourneyViewElementAttributes(attributesSlice) + element.Attributes = &attributes + } + + if filterSlice, ok := elemMap["filter"].([]interface{}); ok { + filter := buildJourneyViewElementFilter(filterSlice) + element.Filter = &filter + } + + if followedBySlice, ok := elemMap["followed_by"].([]interface{}); ok { + followedBy := make([]platformclientv2.Journeyviewlink, len(followedBySlice)) + for i, fb := range followedBySlice { + followedByMap, ok := fb.(map[string]interface{}) + if !ok { + return nil, errors.New("followedBy element is not a map[string]interface{}") + } + followedBy[i] = buildJourneyViewLink(followedByMap) + } + element.FollowedBy = &followedBy + } + elements = append(elements, element) + } + + return &elements, nil +} + +func buildJourneyViewElementAttributes(attributesSlice []interface{}) platformclientv2.Journeyviewelementattributes { + var attributes platformclientv2.Journeyviewelementattributes + for _, elem := range attributesSlice { + if attributesMap, ok := elem.(map[string]interface{}); ok { + attributes.VarType = getStringPointerFromInterface(attributesMap["type"]) + attributes.Id = getStringPointerFromInterface(attributesMap["id"]) + attributes.Source = getStringPointerFromInterface(attributesMap["source"]) + } + } + return attributes +} + +func buildJourneyViewElementFilter(filterSlice []interface{}) platformclientv2.Journeyviewelementfilter { + var filter platformclientv2.Journeyviewelementfilter + for _, elem := range filterSlice { + if filterMap, ok := elem.(map[string]interface{}); ok { + filter.VarType = getStringPointerFromInterface(filterMap["type"]) + if predicatesSlice, ok := filterMap["predicates"].([]interface{}); ok { + predicates := make([]platformclientv2.Journeyviewelementfilterpredicate, len(predicatesSlice)) + for i, predicate := range predicatesSlice { + predicateMap, ok := predicate.(map[string]interface{}) + if ok { + predicates[i] = buildJourneyviewelementfilterpredicate(predicateMap) + } + } + filter.Predicates = &predicates + } + } + } + return filter +} + +func buildJourneyviewelementfilterpredicate(predicateMap map[string]interface{}) platformclientv2.Journeyviewelementfilterpredicate { + var predicate platformclientv2.Journeyviewelementfilterpredicate + predicate.Dimension = getStringPointerFromInterface(predicateMap["dimension"]) + if valuesSlice, ok := predicateMap["values"].([]interface{}); ok { + values := make([]string, len(valuesSlice)) + for i, value := range valuesSlice { + if stringValue, ok := value.(string); ok { + values[i] = stringValue + } + } + predicate.Values = &values + } + predicate.Operator = getStringPointerFromInterface(predicateMap["operator"]) + predicate.NoValue = getBoolPointerFromInterface(predicateMap["no_value"]) + return predicate +} + +func buildJourneyViewLink(linkMap map[string]interface{}) platformclientv2.Journeyviewlink { + var link platformclientv2.Journeyviewlink + link.Id = getStringPointerFromInterface(linkMap["id"]) + if constraintWithinSlice, ok := linkMap["constraint_within"].([]interface{}); ok { + constraintWithin := buildJourneyViewLinkTimeConstraint(constraintWithinSlice) + if constraintWithin != nil { + link.ConstraintWithin = constraintWithin + } + } + if constraintAfterSlice, ok := linkMap["constraint_after"].([]interface{}); ok { + constraintAfter := buildJourneyViewLinkTimeConstraint(constraintAfterSlice) + if constraintAfter != nil { + link.ConstraintAfter = constraintAfter + } + } + link.EventCountType = getStringPointerFromInterface(linkMap["event_count_type"]) + if joinAttributesSlice, ok := linkMap["join_attributes"].([]interface{}); ok { + joinAttributes := make([]string, len(joinAttributesSlice)) + for i, attr := range joinAttributesSlice { + if stringValue, ok := attr.(string); ok { + joinAttributes[i] = stringValue + } + } + if len(joinAttributes) > 0 { + link.JoinAttributes = &joinAttributes + } + } + return link +} + +func buildJourneyViewLinkTimeConstraint(timeConstraintSlice []interface{}) *platformclientv2.Journeyviewlinktimeconstraint { + if timeConstraintSlice == nil || len(timeConstraintSlice) == 0 { + return nil + } + var timeConstraint platformclientv2.Journeyviewlinktimeconstraint + for _, elem := range timeConstraintSlice { + timeConstraintMap, ok := elem.(map[string]interface{}) + if ok { + timeConstraint.Unit = getStringPointerFromInterface(timeConstraintMap["unit"]) + timeConstraint.Value = getIntPointerFromInterface(timeConstraintMap["value"]) + } + } + return &timeConstraint +} + +func getStringPointerFromInterface(val interface{}) *string { + if valString, ok := val.(string); ok { + if valString == "" { + return nil + } + return &valString + } + return nil +} + +func getBoolPointerFromInterface(val interface{}) *bool { + if valBool, ok := val.(bool); ok { + return &valBool + } + return nil +} + +func getIntPointerFromInterface(val interface{}) *int { + if valInt, ok := val.(int); ok { + return &valInt + } + return nil +} + +func flattenElements(elements *[]platformclientv2.Journeyviewelement) []interface{} { + if len(*elements) == 0 { + return nil + } + var elementsList []interface{} + for _, element := range *elements { + elementsMap := make(map[string]interface{}) + resourcedata.SetMapValueIfNotNil(elementsMap, "id", element.Id) + resourcedata.SetMapValueIfNotNil(elementsMap, "name", element.Name) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(elementsMap, "attributes", element.Attributes, flattenAttributes) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(elementsMap, "filter", element.Filter, flattenFilters) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(elementsMap, "followed_by", element.FollowedBy, flattenJourneyViewLink) + elementsList = append(elementsList, elementsMap) + } + return elementsList +} + +func flattenAttributes(attribute *platformclientv2.Journeyviewelementattributes) []interface{} { + if attribute == nil { + return nil + } + var attributesList []interface{} + attributesMap := make(map[string]interface{}) + resourcedata.SetMapValueIfNotNil(attributesMap, "id", attribute.Id) + resourcedata.SetMapValueIfNotNil(attributesMap, "type", attribute.VarType) + resourcedata.SetMapValueIfNotNil(attributesMap, "source", attribute.Source) + attributesList = append(attributesList, attributesMap) + return attributesList +} + +func flattenFilters(filter *platformclientv2.Journeyviewelementfilter) []interface{} { + if filter == nil { + return nil + } + var filtersList []interface{} + filtersMap := make(map[string]interface{}) + resourcedata.SetMapValueIfNotNil(filtersMap, "type", filter.VarType) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(filtersMap, "predicates", filter.Predicates, flattenPredicates) + filtersList = append(filtersList, filtersMap) + return filtersList +} + +func flattenPredicates(predicates *[]platformclientv2.Journeyviewelementfilterpredicate) []interface{} { + if len(*predicates) == 0 { + return nil + } + var predicatesList []interface{} + for _, predicate := range *predicates { + predicatesMap := make(map[string]interface{}) + resourcedata.SetMapValueIfNotNil(predicatesMap, "dimension", predicate.Dimension) + resourcedata.SetMapValueIfNotNil(predicatesMap, "values", predicate.Values) + resourcedata.SetMapValueIfNotNil(predicatesMap, "operator", predicate.Operator) + resourcedata.SetMapValueIfNotNil(predicatesMap, "no_value", predicate.NoValue) + predicatesList = append(predicatesList, predicatesMap) + } + return predicatesList +} + +func flattenJourneyViewLink(journeyViewLinks *[]platformclientv2.Journeyviewlink) []interface{} { + if len(*journeyViewLinks) == 0 { + return nil + } + var journeyViewLinksList []interface{} + for _, journeyViewLink := range *journeyViewLinks { + journeyViewLinksMap := make(map[string]interface{}) + resourcedata.SetMapValueIfNotNil(journeyViewLinksMap, "id", journeyViewLink.Id) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(journeyViewLinksMap, "constraint_within", journeyViewLink.ConstraintWithin, flattenConstraints) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(journeyViewLinksMap, "constraint_after", journeyViewLink.ConstraintAfter, flattenConstraints) + resourcedata.SetMapValueIfNotNil(journeyViewLinksMap, "event_count_type", journeyViewLink.EventCountType) + resourcedata.SetMapValueIfNotNil(journeyViewLinksMap, "join_attributes", journeyViewLink.JoinAttributes) + journeyViewLinksList = append(journeyViewLinksList, journeyViewLinksMap) + } + return journeyViewLinksList +} + +func flattenConstraints(constraint *platformclientv2.Journeyviewlinktimeconstraint) []interface{} { + if constraint == nil { + return nil + } + var constraintsList []interface{} + constraintMap := make(map[string]interface{}) + resourcedata.SetMapValueIfNotNil(constraintMap, "unit", constraint.Unit) + resourcedata.SetMapValueIfNotNil(constraintMap, "value", constraint.Value) + constraintsList = append(constraintsList, constraintMap) + return constraintsList +} diff --git a/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go b/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go index a8f6543ba..10117e498 100644 --- a/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go +++ b/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go @@ -19,8 +19,8 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { orgAuthorizationPairingResource = "test-orgauthorization-pairing" userResource1 = "test-user-1" userResource2 = "test-user-2" - email1 = "terraform-1-" + uuid.NewString() + "@example.com" - email2 = "terraform-2-" + uuid.NewString() + "@example.com" + email1 = "terraform-1-" + uuid.NewString() + "@authpair.com" + email2 = "terraform-2-" + uuid.NewString() + "@authpair.com" userName1 = "test user " + uuid.NewString() userName2 = "test user " + uuid.NewString() groupResource1 = "test-group-1" @@ -29,7 +29,7 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { groupName2 = "TF Group" + uuid.NewString() testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@authpair.com" ) resource.Test(t, resource.TestCase{ @@ -171,7 +171,7 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { "genesyscloud_group."+groupResource2, "id"), resource.TestCheckResourceAttr("genesyscloud_orgauthorization_pairing."+orgAuthorizationPairingResource, "group_ids.#", "2"), func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for resources to get deleted properly + time.Sleep(45 * time.Second) // Wait for 45 seconds for resources to get deleted properly return nil }, ), diff --git a/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go b/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go index 472de4403..a5bfe82fa 100644 --- a/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go +++ b/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go @@ -2,9 +2,11 @@ package recording_media_retention_policy import ( "context" + "encoding/json" "fmt" - "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" + "net/http" + "net/url" ) /* @@ -135,21 +137,33 @@ func (p *policyProxy) getQualityFormsSurveyByName(ctx context.Context, surveyNam // getAllIntegrationCredsFn is the implementation for getting all media retention policy in Genesys Cloud func getAllPoliciesFn(ctx context.Context, p *policyProxy) (*[]platformclientv2.Policy, *platformclientv2.APIResponse, error) { var allPolicies []platformclientv2.Policy - var response *platformclientv2.APIResponse - for pageNum := 1; ; pageNum++ { - const pageSize = 100 - retentionPolicies, resp, err := p.recordingApi.GetRecordingMediaretentionpolicies(pageSize, pageNum, "", []string{}, "", "", "", true, false, false, 0) + const pageSize = 100 + + retentionPolicies, resp, err := callGetAllPoliciesApi(pageSize, 1, p.qualityApi.Configuration) + if err != nil { + return nil, resp, err + } + + if retentionPolicies.Entities == nil || len(*retentionPolicies.Entities) == 0 { + return &allPolicies, resp, nil + } + + allPolicies = append(allPolicies, *retentionPolicies.Entities...) + + for pageNum := 2; pageNum <= *retentionPolicies.PageCount; pageNum++ { + retentionPolicies, resp, err = callGetAllPoliciesApi(pageSize, pageNum, p.qualityApi.Configuration) if err != nil { return nil, resp, err } - response = resp + if retentionPolicies.Entities == nil || len(*retentionPolicies.Entities) == 0 { break } allPolicies = append(allPolicies, *retentionPolicies.Entities...) } - return &allPolicies, response, nil + + return &allPolicies, resp, nil } // createPolicyFn is the implementation for creating a media retention policy in Genesys Cloud @@ -172,18 +186,22 @@ func getPolicyByIdFn(ctx context.Context, p *policyProxy, policyId string) (poli // getPolicyByNameFn is the implementation for getting a media retention policy in Genesys Cloud by name func getPolicyByNameFn(ctx context.Context, p *policyProxy, policyName string) (policy *platformclientv2.Policy, retryable bool, response *platformclientv2.APIResponse, err error) { - const pageSize = 100 - const pageNum = 1 - policies, resp, err := p.recordingApi.GetRecordingMediaretentionpolicies(pageSize, pageNum, "", nil, "", "", policyName, true, false, false, 0) + policies, resp, err := getAllPoliciesFn(ctx, p) if err != nil { return nil, false, resp, err } - if policies.Entities == nil || len(*policies.Entities) == 0 { + if policies == nil || len(*policies) == 0 { return nil, true, resp, fmt.Errorf("no media retention policy found with name %s", policyName) } - policy = &(*policies.Entities)[0] - return policy, false, resp, nil + + for _, policy := range *policies { + if *policy.Name == policyName { + return &policy, false, resp, nil + } + } + + return nil, true, resp, fmt.Errorf("unable to find media retention policy with name %s", policyName) } // updatePolicyFn is the implementation for updating a media retention policy in Genesys Cloud @@ -239,3 +257,44 @@ func getQualityFormsSurveyByNameFn(ctx context.Context, p *policyProxy, surveyNa surveyFormReference := platformclientv2.Publishedsurveyformreference{Name: &surveyName, ContextId: (*forms.Entities)[0].ContextId} return &surveyFormReference, resp, nil } + +// We need to call /api/v2/recording/mediaretentionpolicies manually to avoid setting optional boolean and integer parameters than filter results +func callGetAllPoliciesApi(pageSize, pageNumber int, config *platformclientv2.Configuration) (*platformclientv2.Policyentitylisting, *platformclientv2.APIResponse, error) { + apiClient := &config.APIClient + + // create path and map variables + path := config.BasePath + "/api/v2/recording/mediaretentionpolicies" + + headerParams := make(map[string]string) + queryParams := make(map[string]string) + formParams := url.Values{} + var postBody interface{} + var postFileName string + var fileBytes []byte + + // oauth required + if config.AccessToken != "" { + headerParams["Authorization"] = "Bearer " + config.AccessToken + } + // add default headers if any + for key := range config.DefaultHeader { + headerParams[key] = config.DefaultHeader[key] + } + + queryParams["pageSize"] = apiClient.ParameterToString(pageSize, "") + queryParams["pageNumber"] = apiClient.ParameterToString(pageNumber, "") + + headerParams["Content-Type"] = "application/json" + headerParams["Accept"] = "application/json" + + var successPayload *platformclientv2.Policyentitylisting + response, err := apiClient.CallAPI(path, http.MethodGet, postBody, headerParams, queryParams, formParams, postFileName, fileBytes) + if err != nil { + // Nothing special to do here, but do avoid processing the response + } else if response.Error != nil { + err = fmt.Errorf(response.ErrorMessage) + } else { + err = json.Unmarshal(response.RawBody, &successPayload) + } + return successPayload, response, err +} diff --git a/genesyscloud/resource_cache/datasource_cache.go b/genesyscloud/resource_cache/datasource_cache.go new file mode 100644 index 000000000..e430e8731 --- /dev/null +++ b/genesyscloud/resource_cache/datasource_cache.go @@ -0,0 +1,116 @@ +package resource_cache + +import ( + "context" + "fmt" + "log" + "sync" + "terraform-provider-genesyscloud/genesyscloud/util" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" +) + +// Cache for Data Sources +type DataSourceCache struct { + Cache map[string]string + mutex sync.RWMutex + ClientConfig *platformclientv2.Configuration + HydrateCacheFunc func(*DataSourceCache) error + getApiFunc func(*DataSourceCache, string, context.Context) (string, diag.Diagnostics) +} + +// NewDataSourceCache creates a new data source cache +func NewDataSourceCache(clientConfig *platformclientv2.Configuration, hydrateFn func(*DataSourceCache) error, getFn func(*DataSourceCache, string, context.Context) (string, diag.Diagnostics)) *DataSourceCache { + + return &DataSourceCache{ + Cache: make(map[string]string), + ClientConfig: clientConfig, + HydrateCacheFunc: hydrateFn, + getApiFunc: getFn, + } +} + +func (c *DataSourceCache) HydrateCacheIfEmpty() error { + c.mutex.Lock() + defer c.mutex.Unlock() + if c.isEmpty() { + if err := c.hydrateCache(); err != nil { + return err + } + } + return nil +} + +// Hydrate the cache with updated values. +func (c *DataSourceCache) hydrateCache() error { + return c.HydrateCacheFunc(c) +} + +// Adds or updates a cache entry +func (c *DataSourceCache) UpdateCacheEntry(key string, val string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.Cache == nil { + return fmt.Errorf("cache is not initialized") + } + c.Cache[key] = val + + log.Printf("updated cache entry [%v] to value: %v", key, val) + + return nil +} + +// Returns true if the cache is empty +func (c *DataSourceCache) isEmpty() bool { + return len(c.Cache) <= 0 +} + +// Get value (resource id) from cache by key string +// If value is not found return empty string and `false` +func (c *DataSourceCache) Get(key string) (val string, isFound bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + if c.isEmpty() { + log.Printf("cache is empty. Hydrate it first with values") + return "", false + } + + id, ok := c.Cache[key] + if !ok { + log.Printf("cache miss. cannot find key %s", key) + return "", false + } + + log.Printf("cache hit. found key %v in cache with value %v", key, id) + return id, true +} + +func RetrieveId(cache *DataSourceCache, + resourceName, key string, ctx context.Context) (string, diag.Diagnostics) { + + if err := cache.HydrateCacheIfEmpty(); err != nil { + return "", diag.FromErr(err) + } + + // Get id from cache + id, ok := cache.Get(key) + if !ok { + // If not found in cache, try to obtain through SDK call + log.Printf("could not find the resource %v in cache. Will try API to find value", key) + idFromApi, diagErr := cache.getApiFunc(cache, key, ctx) + if diagErr != nil { + return "", diagErr + } + + if err := cache.UpdateCacheEntry(key, idFromApi); err != nil { + return "", util.BuildDiagnosticError(resourceName, fmt.Sprintf("error updating cache"), err) + } + // id gets reset to empty string at the updateCacheEntry method. + id = idFromApi + } + log.Printf(" id identified %v from cache", id) + return id, nil +} diff --git a/genesyscloud/resource_genesyscloud_auth_division_test.go b/genesyscloud/resource_genesyscloud_auth_division_test.go index b358e0c6d..033097f4c 100644 --- a/genesyscloud/resource_genesyscloud_auth_division_test.go +++ b/genesyscloud/resource_genesyscloud_auth_division_test.go @@ -75,6 +75,12 @@ func TestAccResourceAuthDivisionBasic(t *testing.T) { ResourceName: "genesyscloud_auth_division." + divResource1, ImportState: true, ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion + return nil + }, + ), }, }, CheckDestroy: testVerifyDivisionsDestroyed, @@ -149,6 +155,12 @@ func TestAccResourceAuthDivisionHome(t *testing.T) { ResourceName: "genesyscloud_auth_division." + divHomeRes, ImportState: true, ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion + return nil + }, + ), }, }, CheckDestroy: testVerifyDivisionsDestroyed, diff --git a/genesyscloud/resource_genesyscloud_idp_adfs.go b/genesyscloud/resource_genesyscloud_idp_adfs.go deleted file mode 100644 index 042d5a282..000000000 --- a/genesyscloud/resource_genesyscloud_idp_adfs.go +++ /dev/null @@ -1,211 +0,0 @@ -package genesyscloud - -import ( - "context" - "fmt" - "log" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "terraform-provider-genesyscloud/genesyscloud/util/constants" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - - "terraform-provider-genesyscloud/genesyscloud/consistency_checker" - - resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" - lists "terraform-provider-genesyscloud/genesyscloud/util/lists" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" -) - -func getAllIdpAdfs(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig) - resources := make(resourceExporter.ResourceIDMetaMap) - - _, resp, getErr := idpAPI.GetIdentityprovidersAdfs() - if getErr != nil { - if util.IsStatus404(resp) { - // Don't export if config doesn't exist - return resources, nil - } - return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to get IDP ADFS error: %s", getErr), resp) - } - - resources["0"] = &resourceExporter.ResourceMeta{Name: "adfs"} - return resources, nil -} - -func IdpAdfsExporter() *resourceExporter.ResourceExporter { - return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpAdfs), - RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references - } -} - -func ResourceIdpAdfs() *schema.Resource { - return &schema.Resource{ - Description: "Genesys Cloud Single Sign-on ADFS Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-microsoft-adfs-single-sign-provider/", - - CreateContext: provider.CreateWithPooledClient(createIdpAdfs), - ReadContext: provider.ReadWithPooledClient(readIdpAdfs), - UpdateContext: provider.UpdateWithPooledClient(updateIdpAdfs), - DeleteContext: provider.DeleteWithPooledClient(deleteIdpAdfs), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - SchemaVersion: 1, - Timeouts: &schema.ResourceTimeout{ - Update: schema.DefaultTimeout(8 * time.Minute), - Read: schema.DefaultTimeout(8 * time.Minute), - }, - Schema: map[string]*schema.Schema{ - "certificates": { - Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.", - Type: schema.TypeList, - Required: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "issuer_uri": { - Description: "Issuer URI provided by ADFS.", - Type: schema.TypeString, - Required: true, - }, - "target_uri": { - Description: "Target URI provided by ADFS.", - Type: schema.TypeString, - Optional: true, - }, - "relying_party_identifier": { - Description: "String used to identify Genesys Cloud to ADFS.", - Type: schema.TypeString, - Optional: true, - }, - "disabled": { - Description: "True if ADFS is disabled.", - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - }, - } -} - -func createIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - log.Printf("Creating IDP ADFS") - d.SetId("adfs") - return updateIdpAdfs(ctx, d, meta) -} - -func readIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig) - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpAdfs(), constants.DefaultConsistencyChecks, "genesyscloud_idp_adfs") - - log.Printf("Reading IDP ADFS") - return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError { - adfs, resp, getErr := idpAPI.GetIdentityprovidersAdfs() - if getErr != nil { - if util.IsStatus404(resp) { - createIdpAdfs(ctx, d, meta) - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to read IDP ADFS: %s", getErr), resp)) - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to read IDP ADFS: %s", getErr), resp)) - } - - if adfs.Certificate != nil { - d.Set("certificates", lists.StringListToInterfaceList([]string{*adfs.Certificate})) - } else if adfs.Certificates != nil { - d.Set("certificates", lists.StringListToInterfaceList(*adfs.Certificates)) - } else { - d.Set("certificates", nil) - } - - if adfs.IssuerURI != nil { - d.Set("issuer_uri", *adfs.IssuerURI) - } else { - d.Set("issuer_uri", nil) - } - - if adfs.SsoTargetURI != nil { - d.Set("target_uri", *adfs.SsoTargetURI) - } else { - d.Set("target_uri", nil) - } - - if adfs.RelyingPartyIdentifier != nil { - d.Set("relying_party_identifier", *adfs.RelyingPartyIdentifier) - } else { - d.Set("relying_party_identifier", nil) - } - - if adfs.Disabled != nil { - d.Set("disabled", *adfs.Disabled) - } else { - d.Set("disabled", nil) - } - - log.Printf("Read IDP ADFS") - return cc.CheckState(d) - }) -} - -func updateIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - issuerUri := d.Get("issuer_uri").(string) - targetUri := d.Get("target_uri").(string) - relyingPartyID := d.Get("relying_party_identifier").(string) - disabled := d.Get("disabled").(bool) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig) - - log.Printf("Updating IDP ADFS") - update := platformclientv2.Adfs{ - IssuerURI: &issuerUri, - SsoTargetURI: &targetUri, - RelyingPartyIdentifier: &relyingPartyID, - Disabled: &disabled, - } - - certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates") - if certificates != nil { - if len(*certificates) == 1 { - update.Certificate = &(*certificates)[0] - } - update.Certificates = certificates - } - - _, resp, err := idpAPI.PutIdentityprovidersAdfs(update) - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to update IDP ADFS %s error: %s", d.Id(), err), resp) - } - - log.Printf("Updated IDP ADFS") - return readIdpAdfs(ctx, d, meta) -} - -func deleteIdpAdfs(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig) - - log.Printf("Deleting IDP ADFS") - _, resp, err := idpAPI.DeleteIdentityprovidersAdfs() - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Failed to delete IDP ADFS %s error: %s", d.Id(), err), resp) - } - - return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError { - _, resp, err := idpAPI.GetIdentityprovidersAdfs() - if err != nil { - if util.IsStatus404(resp) { - // IDP ADFS deleted - log.Printf("Deleted IDP ADFS") - return nil - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("Error deleting IDP ADFS: %s", err), resp)) - } - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_adfs", fmt.Sprintf("IDP ADFS still exists"), resp)) - }) -} diff --git a/genesyscloud/resource_genesyscloud_idp_okta.go b/genesyscloud/resource_genesyscloud_idp_okta.go deleted file mode 100644 index f2262e0e1..000000000 --- a/genesyscloud/resource_genesyscloud_idp_okta.go +++ /dev/null @@ -1,199 +0,0 @@ -package genesyscloud - -import ( - "context" - "fmt" - "log" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "terraform-provider-genesyscloud/genesyscloud/util/constants" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - - "terraform-provider-genesyscloud/genesyscloud/consistency_checker" - - resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" - lists "terraform-provider-genesyscloud/genesyscloud/util/lists" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" -) - -func getAllIdpOkta(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(clientConfig) - resources := make(resourceExporter.ResourceIDMetaMap) - - _, resp, getErr := idpAPI.GetIdentityprovidersOkta() - if getErr != nil { - if util.IsStatus404(resp) { - // Don't export if config doesn't exist - return resources, nil - } - return nil, util.BuildAPIDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to get IDP okta error: %s", getErr), resp) - } - - resources["0"] = &resourceExporter.ResourceMeta{Name: "okta"} - return resources, nil -} - -func IdpOktaExporter() *resourceExporter.ResourceExporter { - return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(getAllIdpOkta), - RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references - } -} - -func ResourceIdpOkta() *schema.Resource { - return &schema.Resource{ - Description: "Genesys Cloud Single Sign-on Okta Identity Provider. See this page for detailed configuration instructions: https://help.mypurecloud.com/articles/add-okta-as-a-single-sign-on-provider/", - - CreateContext: provider.CreateWithPooledClient(createIdpOkta), - ReadContext: provider.ReadWithPooledClient(readIdpOkta), - UpdateContext: provider.UpdateWithPooledClient(updateIdpOkta), - DeleteContext: provider.DeleteWithPooledClient(deleteIdpOkta), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - SchemaVersion: 1, - Timeouts: &schema.ResourceTimeout{ - Update: schema.DefaultTimeout(8 * time.Minute), - Read: schema.DefaultTimeout(8 * time.Minute), - }, - Schema: map[string]*schema.Schema{ - "certificates": { - Description: "PEM or DER encoded public X.509 certificates for SAML signature validation.", - Type: schema.TypeList, - Required: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "issuer_uri": { - Description: "Issuer URI provided by Okta.", - Type: schema.TypeString, - Required: true, - }, - "target_uri": { - Description: "Target URI provided by Okta.", - Type: schema.TypeString, - Optional: true, - }, - "disabled": { - Description: "True if Okta is disabled.", - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - }, - } -} - -func createIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - log.Printf("Creating IDP Okta") - d.SetId("okta") - return updateIdpOkta(ctx, d, meta) -} - -func readIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig) - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceIdpOkta(), constants.DefaultConsistencyChecks, "genesyscloud_idp_okta") - - log.Printf("Reading IDP Okta") - - return util.WithRetriesForReadCustomTimeout(ctx, d.Timeout(schema.TimeoutRead), d, func() *retry.RetryError { - okta, resp, getErr := idpAPI.GetIdentityprovidersOkta() - if getErr != nil { - if util.IsStatus404(resp) { - createIdpOkta(ctx, d, meta) - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to read IDP Okta: %s", getErr), resp)) - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to read IDP Okta: %s", getErr), resp)) - } - - if okta.Certificate != nil { - d.Set("certificates", lists.StringListToInterfaceList([]string{*okta.Certificate})) - } else if okta.Certificates != nil { - d.Set("certificates", lists.StringListToInterfaceList(*okta.Certificates)) - } else { - d.Set("certificates", nil) - } - - if okta.IssuerURI != nil { - d.Set("issuer_uri", *okta.IssuerURI) - } else { - d.Set("issuer_uri", nil) - } - - if okta.SsoTargetURI != nil { - d.Set("target_uri", *okta.SsoTargetURI) - } else { - d.Set("target_uri", nil) - } - - if okta.Disabled != nil { - d.Set("disabled", *okta.Disabled) - } else { - d.Set("disabled", nil) - } - - log.Printf("Read IDP Okta") - return cc.CheckState(d) - }) -} - -func updateIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - issuerUri := d.Get("issuer_uri").(string) - targetUri := d.Get("target_uri").(string) - disabled := d.Get("disabled").(bool) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig) - - log.Printf("Updating IDP Okta") - update := platformclientv2.Okta{ - IssuerURI: &issuerUri, - SsoTargetURI: &targetUri, - Disabled: &disabled, - } - - certificates := lists.BuildSdkStringListFromInterfaceArray(d, "certificates") - if certificates != nil { - if len(*certificates) == 1 { - update.Certificate = &(*certificates)[0] - } - update.Certificates = certificates - } - - _, resp, err := idpAPI.PutIdentityprovidersOkta(update) - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to update IDP okta %s error: %s", d.Id(), err), resp) - } - - log.Printf("Updated IDP Okta") - return readIdpOkta(ctx, d, meta) -} - -func deleteIdpOkta(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - idpAPI := platformclientv2.NewIdentityProviderApiWithConfig(sdkConfig) - - log.Printf("Deleting IDP Okta") - _, resp, err := idpAPI.DeleteIdentityprovidersOkta() - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Failed to delete IDP okta %s error: %s", d.Id(), err), resp) - } - - return util.WithRetries(ctx, 60*time.Second, func() *retry.RetryError { - _, resp, err := idpAPI.GetIdentityprovidersOkta() - if err != nil { - if util.IsStatus404(resp) { - // IDP Okta deleted - log.Printf("Deleted IDP Okta") - return nil - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("Error deleting IDP Okta: %s", err), resp)) - } - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_okta", fmt.Sprintf("IDP Okta still exists"), resp)) - }) -} diff --git a/genesyscloud/resource_genesyscloud_idp_onelogin.go b/genesyscloud/resource_genesyscloud_idp_onelogin.go index 38ae47c59..08a92192f 100644 --- a/genesyscloud/resource_genesyscloud_idp_onelogin.go +++ b/genesyscloud/resource_genesyscloud_idp_onelogin.go @@ -105,7 +105,7 @@ func readIdpOnelogin(ctx context.Context, d *schema.ResourceData, meta interface onelogin, resp, getErr := idpAPI.GetIdentityprovidersOnelogin() if getErr != nil { if util.IsStatus404(resp) { - createIdpOkta(ctx, d, meta) + createIdpOnelogin(ctx, d, meta) return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to read IDP Onelogin: %s", getErr), resp)) } return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_idp_onelogin", fmt.Sprintf("Failed to read IDP Onelogin: %s", getErr), resp)) diff --git a/genesyscloud/resource_genesyscloud_init.go b/genesyscloud/resource_genesyscloud_init.go index f973b8fc9..8ca8476fa 100644 --- a/genesyscloud/resource_genesyscloud_init.go +++ b/genesyscloud/resource_genesyscloud_init.go @@ -44,10 +44,8 @@ func registerResources(l registrar.Registrar) { l.RegisterResource("genesyscloud_location", ResourceLocation()) l.RegisterResource("genesyscloud_auth_division", ResourceAuthDivision()) - l.RegisterResource("genesyscloud_idp_adfs", ResourceIdpAdfs()) l.RegisterResource("genesyscloud_idp_generic", ResourceIdpGeneric()) l.RegisterResource("genesyscloud_idp_gsuite", ResourceIdpGsuite()) - l.RegisterResource("genesyscloud_idp_okta", ResourceIdpOkta()) l.RegisterResource("genesyscloud_idp_onelogin", ResourceIdpOnelogin()) l.RegisterResource("genesyscloud_idp_ping", ResourceIdpPing()) l.RegisterResource("genesyscloud_journey_action_map", ResourceJourneyActionMap()) @@ -79,10 +77,8 @@ func registerResources(l registrar.Registrar) { func registerExporters(l registrar.Registrar) { l.RegisterExporter("genesyscloud_auth_division", AuthDivisionExporter()) - l.RegisterExporter("genesyscloud_idp_adfs", IdpAdfsExporter()) l.RegisterExporter("genesyscloud_idp_generic", IdpGenericExporter()) l.RegisterExporter("genesyscloud_idp_gsuite", IdpGsuiteExporter()) - l.RegisterExporter("genesyscloud_idp_okta", IdpOktaExporter()) l.RegisterExporter("genesyscloud_idp_onelogin", IdpOneloginExporter()) l.RegisterExporter("genesyscloud_idp_ping", IdpPingExporter()) l.RegisterExporter("genesyscloud_journey_action_map", JourneyActionMapExporter()) diff --git a/genesyscloud/resource_genesyscloud_init_test.go b/genesyscloud/resource_genesyscloud_init_test.go index 837e8c50d..0988af4f8 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" + architectSchedules "terraform-provider-genesyscloud/genesyscloud/architect_schedules" "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" @@ -19,6 +20,7 @@ var ( providerDataSources map[string]*schema.Resource providerResources map[string]*schema.Resource err error + mu sync.Mutex ) type registerTestInstance struct { @@ -36,10 +38,8 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() providerResources["genesyscloud_location"] = ResourceLocation() providerResources["genesyscloud_auth_division"] = ResourceAuthDivision() - providerResources["genesyscloud_idp_adfs"] = ResourceIdpAdfs() providerResources["genesyscloud_idp_generic"] = ResourceIdpGeneric() providerResources["genesyscloud_idp_gsuite"] = ResourceIdpGsuite() - providerResources["genesyscloud_idp_okta"] = ResourceIdpOkta() providerResources["genesyscloud_idp_onelogin"] = ResourceIdpOnelogin() providerResources["genesyscloud_idp_ping"] = ResourceIdpPing() providerResources["genesyscloud_journey_action_map"] = ResourceJourneyActionMap() @@ -65,6 +65,8 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_user"] = ResourceUser() providerResources["genesyscloud_widget_deployment"] = ResourceWidgetDeployment() providerResources["genesyscloud_architect_schedulegroups"] = archScheduleGroup.ResourceArchitectSchedulegroups() + providerResources["genesyscloud_architect_schedules"] = architectSchedules.ResourceArchitectSchedules() + } func (r *registerTestInstance) registerTestDataSources() { diff --git a/genesyscloud/resource_genesyscloud_journey_action_map_test.go b/genesyscloud/resource_genesyscloud_journey_action_map_test.go index 2f915b57a..f0753a13e 100644 --- a/genesyscloud/resource_genesyscloud_journey_action_map_test.go +++ b/genesyscloud/resource_genesyscloud_journey_action_map_test.go @@ -20,7 +20,6 @@ import ( const resourceName = "genesyscloud_journey_action_map" func TestAccResourceJourneyActionMapActionMediaTypes(t *testing.T) { - t.Skip("Customer segment not implemented") runJourneyActionMapTestCaseWithFileServer(t, "action_media_types", 8111) } @@ -29,17 +28,14 @@ func TestAccResourceJourneyActionMapActionMediaTypesWithTriggerConditions(t *tes } func TestAccResourceJourneyActionMapOptionalAttributes(t *testing.T) { - t.Skip("Customer segment not implemented") runJourneyActionMapTestCase(t, "basic_optional_attributes") } func TestAccResourceJourneyActionMapRequiredAttributes(t *testing.T) { - t.Skip("Customer segment not implemented") runJourneyActionMapTestCaseWithFileServer(t, "basic_required_attributes", 8112) } func TestAccResourceJourneyActionMapScheduleGroups(t *testing.T) { - t.Skip("Customer segment not implemented") runJourneyActionMapTestCase(t, "schedule_groups") } diff --git a/genesyscloud/resource_genesyscloud_journey_segment.go b/genesyscloud/resource_genesyscloud_journey_segment.go index 5b13b70c4..579f2c9ba 100644 --- a/genesyscloud/resource_genesyscloud_journey_segment.go +++ b/genesyscloud/resource_genesyscloud_journey_segment.go @@ -57,7 +57,7 @@ var ( Type: schema.TypeString, Required: true, ForceNew: true, // scope can be only set during creation - ValidateFunc: validation.StringInSlice([]string{"Session", "Customer"}, false), + ValidateFunc: validation.StringInSlice([]string{"Session"}, false), }, "should_display_to_agent": { Description: "Whether or not the segment should be displayed to agent/supervisor users.", @@ -79,19 +79,6 @@ var ( MaxItems: 1, Elem: journeyResource, }, - "external_segment": { - Description: "Details of an entity corresponding to this segment in an external system.", - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: externalSegmentResource, // can only be used with Customer scope - }, - "assignment_expiration_days": { - Description: "Time, in days, from when the segment is assigned until it is automatically unassigned.", - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(2, 60), - }, } contextResource = &schema.Resource{ @@ -230,29 +217,6 @@ var ( }, }, } - - externalSegmentResource = &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Description: "Identifier for the external segment in the system where it originates from. Changing the id attribute will cause the journey_segment resource to be dropped and recreated with a new ID.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "name": { - Description: "Name for the external segment in the system where it originates from.", - Type: schema.TypeString, - Required: true, - }, - "source": { - Description: "The external system where the segment originates from.Valid values: AdobeExperiencePlatform, Custom. Changing the source attribute will cause the journey_segment resource to be dropped and recreated with a new ID.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"AdobeExperiencePlatform", "Custom"}, false), - }, - }, - } ) func getAllJourneySegments(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { @@ -407,8 +371,6 @@ func flattenJourneySegment(d *schema.ResourceData, journeySegment *platformclien resourcedata.SetNillableValue(d, "should_display_to_agent", journeySegment.ShouldDisplayToAgent) resourcedata.SetNillableValue(d, "context", lists.FlattenAsList(journeySegment.Context, flattenContext)) resourcedata.SetNillableValue(d, "journey", lists.FlattenAsList(journeySegment.Journey, flattenJourney)) - resourcedata.SetNillableValue(d, "external_segment", lists.FlattenAsList(journeySegment.ExternalSegment, flattenExternalSegment)) - resourcedata.SetNillableValue(d, "assignment_expiration_days", journeySegment.AssignmentExpirationDays) } func buildSdkJourneySegment(journeySegment *schema.ResourceData) *platformclientv2.Journeysegmentrequest { @@ -420,20 +382,16 @@ func buildSdkJourneySegment(journeySegment *schema.ResourceData) *platformclient shouldDisplayToAgent := resourcedata.GetNillableBool(journeySegment, "should_display_to_agent") sdkContext := resourcedata.BuildSdkListFirstElement(journeySegment, "context", buildSdkRequestContext, false) journey := resourcedata.BuildSdkListFirstElement(journeySegment, "journey", buildSdkRequestJourney, false) - externalSegment := resourcedata.BuildSdkListFirstElement(journeySegment, "external_segment", buildSdkExternalSegment, true) - assignmentExpirationDays := resourcedata.GetNillableValue[int](journeySegment, "assignment_expiration_days") return &platformclientv2.Journeysegmentrequest{ - IsActive: &isActive, - DisplayName: &displayName, - Description: description, - Color: &color, - Scope: &scope, - ShouldDisplayToAgent: shouldDisplayToAgent, - Context: sdkContext, - Journey: journey, - ExternalSegment: externalSegment, - AssignmentExpirationDays: assignmentExpirationDays, + IsActive: &isActive, + DisplayName: &displayName, + Description: description, + Color: &color, + Scope: &scope, + ShouldDisplayToAgent: shouldDisplayToAgent, + Context: sdkContext, + Journey: journey, } } @@ -445,8 +403,6 @@ func buildSdkPatchSegment(journeySegment *schema.ResourceData) *platformclientv2 shouldDisplayToAgent := resourcedata.GetNillableBool(journeySegment, "should_display_to_agent") sdkContext := resourcedata.BuildSdkListFirstElement(journeySegment, "context", buildSdkPatchContext, false) journey := resourcedata.BuildSdkListFirstElement(journeySegment, "journey", buildSdkPatchJourney, false) - externalSegment := resourcedata.BuildSdkListFirstElement(journeySegment, "external_segment", buildSdkPatchExternalSegment, true) - assignmentExpirationDays := resourcedata.GetNillableValue[int](journeySegment, "assignment_expiration_days") sdkPatchSegment := platformclientv2.Patchsegment{} sdkPatchSegment.SetField("IsActive", &isActive) @@ -456,10 +412,6 @@ func buildSdkPatchSegment(journeySegment *schema.ResourceData) *platformclientv2 sdkPatchSegment.SetField("ShouldDisplayToAgent", shouldDisplayToAgent) sdkPatchSegment.SetField("Context", sdkContext) sdkPatchSegment.SetField("Journey", journey) - sdkPatchSegment.SetField("ExternalSegment", externalSegment) - if assignmentExpirationDays != nil { - sdkPatchSegment.SetField("AssignmentExpirationDays", assignmentExpirationDays) - } return &sdkPatchSegment } @@ -659,31 +611,3 @@ func buildSdkPatchCriteria(criteria map[string]interface{}) *platformclientv2.Pa Operator: &operator, } } - -func flattenExternalSegment(externalSegment *platformclientv2.Externalsegment) map[string]interface{} { - externalSegmentMap := make(map[string]interface{}) - externalSegmentMap["id"] = *externalSegment.Id - externalSegmentMap["name"] = *externalSegment.Name - externalSegmentMap["source"] = *externalSegment.Source - return externalSegmentMap -} - -func buildSdkExternalSegment(externalSegment map[string]interface{}) *platformclientv2.Requestexternalsegment { - id := externalSegment["id"].(string) - name := externalSegment["name"].(string) - source := externalSegment["source"].(string) - - return &platformclientv2.Requestexternalsegment{ - Id: &id, - Name: &name, - Source: &source, - } -} - -func buildSdkPatchExternalSegment(externalSegment map[string]interface{}) *platformclientv2.Patchexternalsegment { - name := externalSegment["name"].(string) - - return &platformclientv2.Patchexternalsegment{ - Name: &name, - } -} diff --git a/genesyscloud/resource_genesyscloud_journey_segment_test.go b/genesyscloud/resource_genesyscloud_journey_segment_test.go index 3a7c025b1..165aa683b 100644 --- a/genesyscloud/resource_genesyscloud_journey_segment_test.go +++ b/genesyscloud/resource_genesyscloud_journey_segment_test.go @@ -17,14 +17,10 @@ import ( ) func TestAccResourceJourneySegmentCustomer(t *testing.T) { - if supported, errorMessage := customerSegmentationIsSupported(t); !supported { - t.Skipf("Skipping because feature is not supported. Error message: %s", errorMessage) - } runResourceJourneySegmentTestCase(t, "basic_customer_attributes") } func TestAccResourceJourneySegmentSession(t *testing.T) { - t.Skip("Issue") runResourceJourneySegmentTestCase(t, "basic_session_attributes") } @@ -33,9 +29,6 @@ func TestAccResourceJourneySegmentContextOnly(t *testing.T) { } func TestAccResourceJourneySegmentOptionalAttributes(t *testing.T) { - if supported, errorMessage := customerSegmentationIsSupported(t); !supported { - t.Skipf("Skipping because feature is not supported. Error message: %s", errorMessage) - } runResourceJourneySegmentTestCase(t, "optional_attributes") } diff --git a/genesyscloud/resource_genesyscloud_routing_utilization_test.go b/genesyscloud/resource_genesyscloud_routing_utilization_test.go index efdfffac6..65f90323c 100644 --- a/genesyscloud/resource_genesyscloud_routing_utilization_test.go +++ b/genesyscloud/resource_genesyscloud_routing_utilization_test.go @@ -134,10 +134,6 @@ func TestAccResourceRoutingUtilizationWithLabels(t *testing.T) { generateLabelUtilization(blueLabelResource, maxCapacity1, redLabelResource), ), Check: resource.ComposeTestCheckFunc( - func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for resources to be updated - return nil - }, resource.TestCheckResourceAttr("genesyscloud_routing_utilization.routing-util", "call.0.maximum_capacity", maxCapacity1), resource.TestCheckResourceAttr("genesyscloud_routing_utilization.routing-util", "call.0.include_non_acd", util.FalseValue), resource.TestCheckNoResourceAttr("genesyscloud_routing_utilization.routing-util", "call.0.interruptible_media_types"), @@ -157,10 +153,6 @@ func TestAccResourceRoutingUtilizationWithLabels(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_routing_utilization.routing-util", "label_utilizations.0.maximum_capacity", maxCapacity1), resource.TestCheckResourceAttrSet("genesyscloud_routing_utilization.routing-util", "label_utilizations.1.label_id"), resource.TestCheckResourceAttr("genesyscloud_routing_utilization.routing-util", "label_utilizations.1.maximum_capacity", maxCapacity1), - func(s *terraform.State) error { - time.Sleep(30 * time.Second) - return nil - }, ), }, { @@ -197,12 +189,32 @@ func TestAccResourceRoutingUtilizationWithLabels(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_routing_utilization.routing-util", "label_utilizations.0.maximum_capacity", maxCapacity2), resource.TestCheckResourceAttrSet("genesyscloud_routing_utilization.routing-util", "label_utilizations.1.label_id"), resource.TestCheckResourceAttr("genesyscloud_routing_utilization.routing-util", "label_utilizations.1.maximum_capacity", maxCapacity2), - func(s *terraform.State) error { - time.Sleep(30 * time.Second) - return nil - }, ), }, + { //Delete one by one to avoid conflict + Config: GenerateRoutingUtilizationLabelResource(redLabelResource, redLabelName, "") + + GenerateRoutingUtilizationLabelResource(blueLabelResource, blueLabelName, redLabelResource) + + generateRoutingUtilizationResource( + generateRoutingUtilMediaType("call", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeEmail)), + generateRoutingUtilMediaType("callback", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateRoutingUtilMediaType("chat", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateRoutingUtilMediaType("email", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateRoutingUtilMediaType("message", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateLabelUtilization(redLabelResource, maxCapacity2), + generateLabelUtilization(blueLabelResource, maxCapacity2, redLabelResource), + ), + }, + { + Config: GenerateRoutingUtilizationLabelResource(redLabelResource, redLabelName, "") + + generateRoutingUtilizationResource( + generateRoutingUtilMediaType("call", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeEmail)), + generateRoutingUtilMediaType("callback", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateRoutingUtilMediaType("chat", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateRoutingUtilMediaType("email", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateRoutingUtilMediaType("message", maxCapacity2, util.TrueValue, strconv.Quote(utilTypeCall)), + generateLabelUtilization(redLabelResource, maxCapacity2), + ), + }, { // Import/Read ResourceName: "genesyscloud_routing_utilization.routing-util", @@ -244,6 +256,12 @@ func TestAccResourceRoutingUtilizationWithLabels(t *testing.T) { return nil }, + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + time.Sleep(30 * time.Second) + return nil + }, + ), }, }, }) diff --git a/genesyscloud/resource_genesyscloud_user_test.go b/genesyscloud/resource_genesyscloud_user_test.go index aaa140a3b..37c39f245 100644 --- a/genesyscloud/resource_genesyscloud_user_test.go +++ b/genesyscloud/resource_genesyscloud_user_test.go @@ -22,9 +22,9 @@ func TestAccResourceUserBasic(t *testing.T) { var ( userResource1 = "test-user1" userResource2 = "test-user2" - email1 = "terraform-" + uuid.NewString() + "@example.com" - email2 = "terraform-" + uuid.NewString() + "@example.com" - email3 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" + email2 = "terraform-" + uuid.NewString() + "@user.com" + email3 = "terraform-" + uuid.NewString() + "@user.com" userName1 = "John Terraform" userName2 = "Jim Terraform" stateActive = "active" @@ -179,9 +179,9 @@ func TestAccResourceUserAddresses(t *testing.T) { var ( addrUserResource1 = "test-user-addr" addrUserName = "Nancy Terraform" - addrEmail1 = "terraform-" + uuid.NewString() + "@example.com" - addrEmail2 = "terraform-" + uuid.NewString() + "@example.com" - addrEmail3 = "terraform-" + uuid.NewString() + "@example.com" + addrEmail1 = "terraform-" + uuid.NewString() + "@user.com" + addrEmail2 = "terraform-" + uuid.NewString() + "@user.com" + addrEmail3 = "terraform-" + uuid.NewString() + "@user.com" addrPhone1 = "+13174269078" addrPhone2 = "+441434634996" addrPhoneExt = "1234" @@ -270,7 +270,7 @@ func TestAccResourceUserPhone(t *testing.T) { var ( addrUserResource1 = "test-user-addr" addrUserName = "Nancy Terraform" - addrEmail1 = "terraform-" + uuid.NewString() + "@example.com" + addrEmail1 = "terraform-" + uuid.NewString() + "@user.com" addrPhone1 = "+13173271898" addrPhone2 = "+13173271899" addrExt1 = "353" @@ -381,7 +381,7 @@ func TestAccResourceUserSkills(t *testing.T) { t.Parallel() var ( userResource1 = "test-user" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" userName1 = "Skill Terraform" skillResource1 = "test-skill-1" skillResource2 = "test-skill-2" @@ -463,7 +463,7 @@ func TestAccResourceUserLanguages(t *testing.T) { t.Parallel() var ( userResource1 = "test-user" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" userName1 = "Lang Terraform" langResource1 = "test-lang-1" langResource2 = "test-lang-2" @@ -534,6 +534,10 @@ func TestAccResourceUserLanguages(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( resource.TestCheckNoResourceAttr("genesyscloud_user."+userResource1, "routing_languages.%"), + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion + return nil + }, ), }, }, @@ -545,7 +549,7 @@ func TestAccResourceUserLocations(t *testing.T) { t.Parallel() var ( userResource1 = "test-user-loc" - email = "terraform-" + uuid.NewString() + "@example.com" + email = "terraform-" + uuid.NewString() + "@user.com" userName = "Loki Terraform" locResource1 = "test-location1" locResource2 = "test-location2" @@ -603,7 +607,7 @@ func TestAccResourceUserEmployerInfo(t *testing.T) { var ( userResource1 = "test-user-info" userName = "Info Terraform" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" empTypeFull = "Full-time" empTypePart = "Part-time" hireDate1 = "2010-05-06" @@ -703,12 +707,12 @@ func TestAccResourceUserEmployerInfo(t *testing.T) { }) } -func TestAccResourceUserRoutingUtil(t *testing.T) { +func TestAccResourceUserroutingUtil(t *testing.T) { t.Parallel() var ( userResource1 = "test-user-util" userName = "Terraform Util" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" maxCapacity0 = "0" maxCapacity1 = "10" maxCapacity2 = "12" @@ -837,12 +841,12 @@ func TestAccResourceUserRoutingUtil(t *testing.T) { }) } -func TestAccResourceUserRoutingUtilWithLabels(t *testing.T) { +func TestAccResourceUserroutingUtilWithLabels(t *testing.T) { t.Parallel() var ( userResource1 = "test-user-util" userName = "Terraform Util" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" maxCapacity0 = "0" maxCapacity1 = "10" maxCapacity2 = "12" @@ -1014,7 +1018,7 @@ func TestAccResourceUserRestore(t *testing.T) { t.Parallel() var ( userResource1 = "test-user" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" userName1 = "Terraform Restore1" userName2 = "Terraform Restore2" ) @@ -1065,7 +1069,7 @@ func TestAccResourceUserCreateWhenDestroyed(t *testing.T) { t.Parallel() var ( userResource1 = "test-user" - email1 = "terraform-" + uuid.NewString() + "@example.com" + email1 = "terraform-" + uuid.NewString() + "@user.com" userName1 = "Terraform Existing" userName2 = "Terraform Create" stateActive = "active" diff --git a/genesyscloud/responsemanagement_responseasset/data_source_genesyscloud_responsemanagement_responseasset.go b/genesyscloud/responsemanagement_responseasset/data_source_genesyscloud_responsemanagement_responseasset.go index ae35acfd0..34614110b 100644 --- a/genesyscloud/responsemanagement_responseasset/data_source_genesyscloud_responsemanagement_responseasset.go +++ b/genesyscloud/responsemanagement_responseasset/data_source_genesyscloud_responsemanagement_responseasset.go @@ -3,12 +3,13 @@ package responsemanagement_responseasset import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceResponseManagementResponseAssetRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { diff --git a/genesyscloud/routing_email_route/data_source_genesyscloud_routing_email_route.go b/genesyscloud/routing_email_route/data_source_genesyscloud_routing_email_route.go new file mode 100644 index 000000000..717d98ab0 --- /dev/null +++ b/genesyscloud/routing_email_route/data_source_genesyscloud_routing_email_route.go @@ -0,0 +1,42 @@ +package routing_email_route + +import ( + "context" + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* + The data_source_genesyscloud_routing_email_route.go contains the data source implementation + for the resource. +*/ + +// dataSourceRoutingEmailRouteRead retrieves by pattern, domainId the id in question + +func dataSourceRoutingEmailRouteRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sdkConfig := m.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingEmailRouteProxy(sdkConfig) + + pattern := d.Get("pattern").(string) + domainId := d.Get("domain_id").(string) + + return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + responseId, retryable, resp, err := proxy.getRoutingEmailRouteIdByPattern(ctx, pattern, domainId) + + if err != nil && !retryable { + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting routing email route %s | error: %s", pattern, err), resp)) + } + if retryable { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("No routing email route found with pattern %s", pattern), resp)) + } + + d.SetId(responseId) + return nil + }) +} diff --git a/genesyscloud/routing_email_route/data_source_genesyscloud_routing_email_route_test.go b/genesyscloud/routing_email_route/data_source_genesyscloud_routing_email_route_test.go new file mode 100644 index 000000000..0b66ad296 --- /dev/null +++ b/genesyscloud/routing_email_route/data_source_genesyscloud_routing_email_route_test.go @@ -0,0 +1,75 @@ +package routing_email_route + +import ( + "fmt" + "strings" + gcloud "terraform-provider-genesyscloud/genesyscloud" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "testing" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceRoutingEmailRoute(t *testing.T) { + var ( + domainRes = "routing-domain1" + domainId = fmt.Sprintf("terraformroutes.%s.com", strings.Replace(uuid.NewString(), "-", "", -1)) + routeRes = "email-route1" + routePattern1 = "terraform1" + fromEmail1 = "terraform1@test.com" + fromName1 = "John Terraform" + bccEmail1 = "test1@" + domainId + ) + + // Standard acceptance tests + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + // Create email domain and basic route + Config: gcloud.GenerateRoutingEmailDomainResource( + domainRes, + domainId, + util.FalseValue, + util.NullValue, + ) + GenerateRoutingEmailRouteResource( + routeRes, + "genesyscloud_routing_email_domain."+domainRes+".id", + routePattern1, + fromName1, + fmt.Sprintf("from_email = \"%s\"", fromEmail1), + generateRoutingAutoBcc(fromName1, bccEmail1), + ) + generateRoutingEmailRouteDataSource( + routeRes, + routePattern1, + "genesyscloud_routing_email_domain."+domainRes+".id", + "genesyscloud_routing_email_route."+routeRes, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair( + "data.genesyscloud_routing_email_route."+routeRes, "id", + "genesyscloud_routing_email_route."+routeRes, "id", + ), + ), + }, + }, + CheckDestroy: testVerifyRoutingEmailRouteDestroyed, + }) +} + +func generateRoutingEmailRouteDataSource( + resourceID string, + pattern string, + domainId string, + dependsOn string) string { + return fmt.Sprintf(` + data "genesyscloud_routing_email_route" "%s" { + pattern = "%s" + domain_id = "%s" + depends_on=[%s] + } + `, resourceID, pattern, domainId, dependsOn) +} diff --git a/genesyscloud/routing_email_route/genesyscloud_routing_email_route_init_test.go b/genesyscloud/routing_email_route/genesyscloud_routing_email_route_init_test.go index 5a526bc5f..9037521ac 100644 --- a/genesyscloud/routing_email_route/genesyscloud_routing_email_route_init_test.go +++ b/genesyscloud/routing_email_route/genesyscloud_routing_email_route_init_test.go @@ -16,11 +16,15 @@ The genesyscloud_routing_email_route_init_test.go file is used to initialize the used in testing the routing_email_route resource. */ +// providerDataSources holds a map of all registered datasources +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 + resourceMapMutex sync.RWMutex + dataSourceMapMutex sync.RWMutex } // registerTestResources registers all resources used in the tests @@ -28,7 +32,7 @@ func (r *registerTestInstance) registerTestResources() { r.resourceMapMutex.Lock() defer r.resourceMapMutex.Unlock() - providerResources["genesyscloud_routing_email_route"] = ResourceRoutingEmailRoute() + providerResources[resourceName] = ResourceRoutingEmailRoute() providerResources["genesyscloud_routing_email_domain"] = genesyscloud.ResourceRoutingEmailDomain() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() providerResources["genesyscloud_routing_language"] = genesyscloud.ResourceRoutingLanguage() @@ -37,13 +41,23 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_routing_skill_group"] = genesyscloud.ResourceRoutingSkillGroup() } +// registerTestDataSources registers all data sources used in the tests. +func (r *registerTestInstance) registerTestDataSources() { + r.dataSourceMapMutex.Lock() + defer r.dataSourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceRoutingEmailRoute() +} + // 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 diff --git a/genesyscloud/routing_email_route/genesyscloud_routing_email_route_proxy.go b/genesyscloud/routing_email_route/genesyscloud_routing_email_route_proxy.go index f6f98a352..771340b62 100644 --- a/genesyscloud/routing_email_route/genesyscloud_routing_email_route_proxy.go +++ b/genesyscloud/routing_email_route/genesyscloud_routing_email_route_proxy.go @@ -3,8 +3,9 @@ package routing_email_route import ( "context" "fmt" - "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" "log" + + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" ) /* @@ -19,35 +20,35 @@ var internalProxy *routingEmailRouteProxy // Type definitions for each func on our proxy so we can easily mock them out later type createRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, inboundRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) type getAllRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, name string) (*map[string][]platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) -type getRoutingEmailRouteIdByNameFunc func(ctx context.Context, p *routingEmailRouteProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) +type getRoutingEmailRouteIdByPatternFunc func(ctx context.Context, p *routingEmailRouteProxy, pattern string, domainId string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) type getRoutingEmailRouteByIdFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, id string) (inboundRoute *platformclientv2.Inboundroute, response *platformclientv2.APIResponse, err error) type updateRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, id string, domainId string, inboundRoute *platformclientv2.Inboundroute) (*platformclientv2.Inboundroute, *platformclientv2.APIResponse, error) type deleteRoutingEmailRouteFunc func(ctx context.Context, p *routingEmailRouteProxy, domainId string, id string) (response *platformclientv2.APIResponse, err error) // routingEmailRouteProxy contains all of the methods that call genesys cloud APIs. type routingEmailRouteProxy struct { - clientConfig *platformclientv2.Configuration - routingApi *platformclientv2.RoutingApi - createRoutingEmailRouteAttr createRoutingEmailRouteFunc - getAllRoutingEmailRouteAttr getAllRoutingEmailRouteFunc - getRoutingEmailRouteIdByNameAttr getRoutingEmailRouteIdByNameFunc - getRoutingEmailRouteByIdAttr getRoutingEmailRouteByIdFunc - updateRoutingEmailRouteAttr updateRoutingEmailRouteFunc - deleteRoutingEmailRouteAttr deleteRoutingEmailRouteFunc + clientConfig *platformclientv2.Configuration + routingApi *platformclientv2.RoutingApi + createRoutingEmailRouteAttr createRoutingEmailRouteFunc + getAllRoutingEmailRouteAttr getAllRoutingEmailRouteFunc + getRoutingEmailRouteIdByPatternAttr getRoutingEmailRouteIdByPatternFunc + getRoutingEmailRouteByIdAttr getRoutingEmailRouteByIdFunc + updateRoutingEmailRouteAttr updateRoutingEmailRouteFunc + deleteRoutingEmailRouteAttr deleteRoutingEmailRouteFunc } // newRoutingEmailRouteProxy initializes the routing email route proxy with all of the data needed to communicate with Genesys Cloud func newRoutingEmailRouteProxy(clientConfig *platformclientv2.Configuration) *routingEmailRouteProxy { api := platformclientv2.NewRoutingApiWithConfig(clientConfig) return &routingEmailRouteProxy{ - clientConfig: clientConfig, - routingApi: api, - createRoutingEmailRouteAttr: createRoutingEmailRouteFn, - getAllRoutingEmailRouteAttr: getAllRoutingEmailRouteFn, - getRoutingEmailRouteIdByNameAttr: getRoutingEmailRouteIdByNameFn, - getRoutingEmailRouteByIdAttr: getRoutingEmailRouteByIdFn, - updateRoutingEmailRouteAttr: updateRoutingEmailRouteFn, - deleteRoutingEmailRouteAttr: deleteRoutingEmailRouteFn, + clientConfig: clientConfig, + routingApi: api, + createRoutingEmailRouteAttr: createRoutingEmailRouteFn, + getAllRoutingEmailRouteAttr: getAllRoutingEmailRouteFn, + getRoutingEmailRouteIdByPatternAttr: getRoutingEmailRouteIdByPatternFn, + getRoutingEmailRouteByIdAttr: getRoutingEmailRouteByIdFn, + updateRoutingEmailRouteAttr: updateRoutingEmailRouteFn, + deleteRoutingEmailRouteAttr: deleteRoutingEmailRouteFn, } } @@ -70,9 +71,9 @@ func (p *routingEmailRouteProxy) getAllRoutingEmailRoute(ctx context.Context, do return p.getAllRoutingEmailRouteAttr(ctx, p, domainId, name) } -// getRoutingEmailRouteIdByName returns a single Genesys Cloud routing email route by a name -func (p *routingEmailRouteProxy) getRoutingEmailRouteIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) { - return p.getRoutingEmailRouteIdByNameAttr(ctx, p, name) +// getRoutingEmailRouteIdByName returns a single Genesys Cloud routing email route by a pattern +func (p *routingEmailRouteProxy) getRoutingEmailRouteIdByPattern(ctx context.Context, pattern string, domainId string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) { + return p.getRoutingEmailRouteIdByPatternAttr(ctx, p, pattern, domainId) } // getRoutingEmailRouteById returns a single Genesys Cloud routing email route by Id @@ -194,24 +195,24 @@ func getRoutingEmailRouteByIdFn(ctx context.Context, p *routingEmailRouteProxy, } // getRoutingEmailRouteIdByNameFn is an implementation of the function to get a Genesys Cloud routing email route by name -func getRoutingEmailRouteIdByNameFn(ctx context.Context, p *routingEmailRouteProxy, name string) (string, bool, *platformclientv2.APIResponse, error) { - inboundRoutesMap, resp, err := getAllRoutingEmailRouteFn(ctx, p, "", name) +func getRoutingEmailRouteIdByPatternFn(ctx context.Context, p *routingEmailRouteProxy, pattern string, domainId string) (string, bool, *platformclientv2.APIResponse, error) { + inboundRoutesMap, resp, err := getAllRoutingEmailRouteFn(ctx, p, domainId, pattern) if err != nil { return "", false, resp, err } if inboundRoutesMap == nil || len(*inboundRoutesMap) == 0 { - return "", true, resp, fmt.Errorf("No routing email route found with name %s", name) + return "", true, resp, fmt.Errorf("No routing email route found with pattern %s", pattern) } for _, inboundRoutes := range *inboundRoutesMap { for _, inboundRoute := range inboundRoutes { - if *inboundRoute.Name == name { - log.Printf("Retrieved the routing email route id %s by name %s", *inboundRoute.Id, name) + if *inboundRoute.Pattern == pattern { + log.Printf("Retrieved the routing email route id %s by pattern %s", *inboundRoute.Id, pattern) return *inboundRoute.Id, false, resp, nil } } } - return "", true, resp, fmt.Errorf("Unable to find routing email route with name %s", name) + return "", true, resp, fmt.Errorf("Unable to find routing email route with name %s", pattern) } diff --git a/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_schema.go b/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_schema.go index 8abb10d5b..9fadd83da 100644 --- a/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_schema.go +++ b/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_schema.go @@ -40,6 +40,7 @@ var ( func SetRegistrar(regInstance registrar.Registrar) { regInstance.RegisterResource(resourceName, ResourceRoutingEmailRoute()) regInstance.RegisterExporter(resourceName, RoutingEmailRouteExporter()) + regInstance.RegisterDataSource(resourceName, DataSourceRoutingEmailRoute()) } func ResourceRoutingEmailRoute() *schema.Resource { @@ -151,6 +152,25 @@ func ResourceRoutingEmailRoute() *schema.Resource { } } +func DataSourceRoutingEmailRoute() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Genesys Cloud Routing Email Route. Select a routing email route by pattern and domain ID.", + ReadContext: provider.ReadWithPooledClient(dataSourceRoutingEmailRouteRead), + Schema: map[string]*schema.Schema{ + "pattern": { + Description: "Routing pattern.", + Type: schema.TypeString, + Required: true, + }, + "domain_id": { + Description: "Domain of the route.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} + // RoutingEmailRouteExporter returns the resourceExporter object used to hold the genesyscloud_routing_email_route exporter's config func RoutingEmailRouteExporter() *resourceExporter.ResourceExporter { return &resourceExporter.ResourceExporter{ diff --git a/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_test.go b/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_test.go index fb0d28226..941e6c4c8 100644 --- a/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_test.go +++ b/genesyscloud/routing_email_route/resource_genesyscloud_routing_email_route_test.go @@ -155,6 +155,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { CheckDestroy: testVerifyRoutingEmailRouteDestroyed, }) + domainId = fmt.Sprintf("terraformroute.%s.com", strings.Replace(uuid.NewString(), "-", "", -1)) // Standard acceptance tests resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, diff --git a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go index 74cbff611..67c47b23e 100644 --- a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go +++ b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "strings" - "sync" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "time" @@ -15,117 +14,81 @@ 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/v130/platformclientv2" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" ) -// Cache for Data Sources -type DataSourceCache struct { - cache map[string]string - mutex sync.RWMutex - clientConfig *platformclientv2.Configuration - - hydrateCacheFunc func(*DataSourceCache) error -} - var ( - dataSourceRoutingQueueCache *DataSourceCache + dataSourceRoutingQueueCache *rc.DataSourceCache ) func dataSourceRoutingQueueRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sdkConfig := m.(*provider.ProviderMeta).ClientConfig - routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - // Create a cache for the queues + key := d.Get("name").(string) + key = normalizeQueueName(key) + if dataSourceRoutingQueueCache == nil { - dataSourceRoutingQueueCache = NewDataSourceCache(sdkConfig, hydrateRoutingQueueCacheFn) + dataSourceRoutingQueueCache = rc.NewDataSourceCache(sdkConfig, hydrateRoutingQueueCacheFn, getQueueByNameFn) } - if err := dataSourceRoutingQueueCache.hydrateCacheIfEmpty(); err != nil { - return diag.FromErr(err) - } + queueId, err := rc.RetrieveId(dataSourceRoutingQueueCache, resourceName, key, ctx) - // Get id from cache - name := d.Get("name").(string) - queueId, ok := dataSourceRoutingQueueCache.get(normalizeQueueName(name)) - if !ok { - // If not found in cache, try to obtain through SDK call - log.Printf("could not find routing queue %v in cache. Will try API to find value", name) - queueId, diagErr := getQueueByName(ctx, routingApi, name) - if diagErr != nil { - return diagErr - } - - d.SetId(queueId) - if err := dataSourceRoutingQueueCache.updateCacheEntry(name, queueId); err != nil { - return util.BuildDiagnosticError(resourceName, fmt.Sprintf("error updating cache"), err) - } - return nil + if err != nil { + return err } - log.Printf("found queue %v from cache", name) d.SetId(queueId) return nil } -func (c *DataSourceCache) hydrateCacheIfEmpty() error { - c.mutex.Lock() - defer c.mutex.Unlock() - if c.isEmpty() { - if err := c.hydrateCache(); err != nil { - return err - } - } - return nil -} - // Normalize queue name for keys in the cache func normalizeQueueName(queueName string) string { return strings.ToLower(queueName) } -// NewDataSourceCache creates a new data source cache -func NewDataSourceCache(clientConfig *platformclientv2.Configuration, hydrateFn func(*DataSourceCache) error) *DataSourceCache { - return &DataSourceCache{ - cache: make(map[string]string), - clientConfig: clientConfig, - hydrateCacheFunc: hydrateFn, - } -} - // hydrateRoutingQueueCacheFn for hydrating the cache with Genesys Cloud routing queues using the SDK -func hydrateRoutingQueueCacheFn(c *DataSourceCache) error { +func hydrateRoutingQueueCacheFn(c *rc.DataSourceCache) error { log.Printf("hydrating cache for data source genesyscloud_routing_queues") + routingApi := platformclientv2.NewRoutingApiWithConfig(c.ClientConfig) + const pageSize = 100 + queues, _, getErr := routingApi.GetRoutingQueues(1, pageSize, "", "", nil, nil, nil, "", false) + + if getErr != nil { + return fmt.Errorf("failed to get page of skills: %v", getErr) + } + if queues.Entities == nil || len(*queues.Entities) == 0 { + return nil + } + for _, queue := range *queues.Entities { + c.Cache[normalizeQueueName(*queue.Name)] = *queue.Id + } - routingApi := platformclientv2.NewRoutingApiWithConfig(c.clientConfig) + for pageNum := 2; pageNum <= *queues.PageCount; pageNum++ { - for pageNum := 1; ; pageNum++ { - const pageSize = 100 queues, _, getErr := routingApi.GetRoutingQueues(pageNum, pageSize, "", "", nil, nil, nil, "", false) if getErr != nil { return fmt.Errorf("failed to get page of queues: %v", getErr) } - if queues.Entities == nil || len(*queues.Entities) == 0 { break } - // Add ids to cache for _, queue := range *queues.Entities { - c.cache[normalizeQueueName(*queue.Name)] = *queue.Id + c.Cache[normalizeQueueName(*queue.Name)] = *queue.Id } } - log.Printf("cache hydration completed for data source genesyscloud_routing_queues") - return nil } // Get queue by name. // Returns the queue id (blank if not found) and diag -func getQueueByName(ctx context.Context, routingApi *platformclientv2.RoutingApi, name string) (string, diag.Diagnostics) { +func getQueueByNameFn(c *rc.DataSourceCache, name string, ctx context.Context) (string, diag.Diagnostics) { + routingApi := platformclientv2.NewRoutingApiWithConfig(c.ClientConfig) queueId := "" + const pageSize = 100 diag := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { for pageNum := 1; ; pageNum++ { - const pageSize = 100 queues, resp, getErr := routingApi.GetRoutingQueues(pageNum, pageSize, "", name, nil, nil, nil, "", false) if getErr != nil { return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting queue %s | error %s", name, getErr), resp)) @@ -146,49 +109,3 @@ func getQueueByName(ctx context.Context, routingApi *platformclientv2.RoutingApi return queueId, diag } - -// Hydrate the cache with updated values. -func (c *DataSourceCache) hydrateCache() error { - return c.hydrateCacheFunc(c) -} - -// Adds or updates a cache entry -func (c *DataSourceCache) updateCacheEntry(key string, val string) error { - c.mutex.Lock() - defer c.mutex.Unlock() - - if c.cache == nil { - return fmt.Errorf("cache is not initialized") - } - c.cache[key] = val - - log.Printf("updated cache entry [%v] to value: %v", key, val) - - return nil -} - -// Returns true if the cache is empty -func (c *DataSourceCache) isEmpty() bool { - return len(c.cache) <= 0 -} - -// Get value (resource id) from cache by key string -// If value is not found return empty string and `false` -func (c *DataSourceCache) get(key string) (val string, isFound bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - - if c.isEmpty() { - log.Printf("cache is empty. Hydrate it first with values") - return "", false - } - - queueId, ok := c.cache[key] - if !ok { - log.Printf("cache miss. cannot find key %s", key) - return "", false - } - - log.Printf("cache hit. found key %v in cache with value %v", key, queueId) - return queueId, true -} diff --git a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go index ad3f419fd..bd8e42ba9 100644 --- a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go +++ b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go @@ -6,6 +6,7 @@ import ( "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" + "time" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -74,6 +75,9 @@ func TestAccDataSourceRoutingQueueCaching(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { + PreConfig: func() { + time.Sleep(45 * time.Second) + }, Config: generateRoutingQueueResourceBasic( // queue resource queue1ResourceId, queueName1, diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go index bfb860edc..896ea6e8a 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go @@ -533,8 +533,8 @@ func RoutingQueueExporter() *resourceExporter.ResourceExporter { func DataSourceRoutingQueue() *schema.Resource { return &schema.Resource{ - Description: "Data source for Genesys Cloud Routing Queues. Select a queue by name.", - ReadContext: provider.ReadWithPooledClient(dataSourceRoutingQueueRead), + Description: "Data source for Genesys Cloud Routing Queues. Select a queue by name.", + ReadWithoutTimeout: provider.ReadWithPooledClient(dataSourceRoutingQueueRead), Schema: map[string]*schema.Schema{ "name": { Description: "Queue name.", diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go index 97839f657..d4a8c70c1 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go @@ -49,7 +49,7 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { bullseyeMemberGroupType = "GROUP" testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@examplestest.com" callbackHours = "7" callbackHours2 = "7" ) @@ -111,10 +111,6 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { validateBullseyeSettings(queueResource1, 1, alertTimeout1, "genesyscloud_routing_skill."+queueSkillResource), validateRoutingRules(queueResource1, 0, routingRuleOpAny, "50", "5"), validateAgentOwnedRouting(queueResource1, "agent_owned_routing", util.TrueValue, callbackHours, callbackHours), - func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for resources to get deleted properly - return nil - }, ), }, { @@ -168,7 +164,7 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { validateRoutingRules(queueResource1, 1, routingRuleOpAny, "45", "15"), validateAgentOwnedRouting(queueResource1, "agent_owned_routing", util.TrueValue, callbackHours2, callbackHours2), func(s *terraform.State) error { - time.Sleep(45 * time.Second) // Wait for 30 seconds for resources to get deleted properly + time.Sleep(30 * time.Second) // Wait for 30 seconds for resources to get deleted properly return nil }, ), @@ -631,6 +627,10 @@ func TestAccResourceRoutingQueueFlows(t *testing.T) { resource.TestCheckResourceAttrPair("genesyscloud_routing_queue."+queueResource1, "queue_flow_id", "genesyscloud_flow."+queueFlowResource2, "id"), resource.TestCheckResourceAttrPair("genesyscloud_routing_queue."+queueResource1, "email_in_queue_flow_id", "genesyscloud_flow."+emailInQueueFlowResource2, "id"), resource.TestCheckResourceAttrPair("genesyscloud_routing_queue."+queueResource1, "message_in_queue_flow_id", "genesyscloud_flow."+messageInQueueFlowResource2, "id"), + func(s *terraform.State) error { + time.Sleep(45 * time.Second) // Wait for 45 seconds for proper deletion of user + return nil + }, ), }, { @@ -650,10 +650,10 @@ func TestAccResourceRoutingQueueMembers(t *testing.T) { queueName = "Terraform Test Queue3-" + uuid.NewString() queueMemberResource1 = "test-queue-user1" queueMemberResource2 = "test-queue-user2" - queueMemberEmail1 = "terraform1-" + uuid.NewString() + "@example.com" - queueMemberEmail2 = "terraform2-" + uuid.NewString() + "@example.com" - queueMemberName1 = "Henry Terraform" - queueMemberName2 = "Amanda Terraform" + queueMemberEmail1 = "terraform1-" + uuid.NewString() + "@queue.com" + queueMemberEmail2 = "terraform2-" + uuid.NewString() + "@queue.com" + queueMemberName1 = "Henry Terraform Test" + queueMemberName2 = "Amanda Terraform Test" defaultQueueRingNum = "1" queueRingNum = "3" ) @@ -662,38 +662,27 @@ func TestAccResourceRoutingQueueMembers(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { + PreConfig: func() { + // Wait for a specified duration to avoid runtime error + time.Sleep(30 * time.Second) + }, // Create - Config: GenerateRoutingQueueResourceBasic( - queueResource, - queueName, - GenerateMemberBlock("genesyscloud_user."+queueMemberResource1+".id", util.NullValue), - ) + genesyscloud.GenerateBasicUserResource( + Config: genesyscloud.GenerateBasicUserResource( queueMemberResource1, queueMemberEmail1, queueMemberName1, - ) + genesyscloud.GenerateBasicUserResource( - queueMemberResource2, - queueMemberEmail2, - queueMemberName2, + ) + GenerateRoutingQueueResourceBasic( + queueResource, + queueName, + GenerateMemberBlock("genesyscloud_user."+queueMemberResource1+".id", util.NullValue), ), Check: resource.ComposeTestCheckFunc( validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource1, defaultQueueRingNum), ), }, { - PreConfig: func() { - time.Sleep(45 * time.Second) - }, // Update with another queue member and modify rings - Config: GenerateRoutingQueueResourceBasic( - queueResource, - queueName, - GenerateMemberBlock("genesyscloud_user."+queueMemberResource1+".id", queueRingNum), - GenerateMemberBlock("genesyscloud_user."+queueMemberResource2+".id", queueRingNum), - GenerateBullseyeSettings("10"), - GenerateBullseyeSettings("10"), - GenerateBullseyeSettings("10"), - ) + genesyscloud.GenerateBasicUserResource( + Config: genesyscloud.GenerateBasicUserResource( queueMemberResource1, queueMemberEmail1, queueMemberName1, @@ -701,6 +690,14 @@ func TestAccResourceRoutingQueueMembers(t *testing.T) { queueMemberResource2, queueMemberEmail2, queueMemberName2, + ) + GenerateRoutingQueueResourceBasic( + queueResource, + queueName, + GenerateMemberBlock("genesyscloud_user."+queueMemberResource1+".id", queueRingNum), + GenerateMemberBlock("genesyscloud_user."+queueMemberResource2+".id", queueRingNum), + GenerateBullseyeSettings("10"), + GenerateBullseyeSettings("10"), + GenerateBullseyeSettings("10"), ), Check: resource.ComposeTestCheckFunc( validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource1, queueRingNum), @@ -1450,10 +1447,6 @@ func TestAccResourceRoutingQueueSkillGroups(t *testing.T) { GenerateBullseyeSettings("10")), Check: resource.ComposeTestCheckFunc( validateGroups("genesyscloud_routing_queue."+queueResource, "genesyscloud_routing_skill_group."+skillGroupResource, "genesyscloud_group."+groupResource), - func(s *terraform.State) error { - time.Sleep(45 * time.Second) // Wait for 45 seconds for resource to get deleted properly - return nil - }, ), }, { @@ -1464,6 +1457,12 @@ func TestAccResourceRoutingQueueSkillGroups(t *testing.T) { ImportStateVerifyIgnore: []string{ "suppress_in_queue_call_recording", }, + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + time.Sleep(45 * time.Second) // Wait for 45 seconds for resource to get deleted properly + return nil + }, + ), }, }, CheckDestroy: testVerifyQueuesDestroyed, diff --git a/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing_test.go b/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing_test.go index b2ec366a9..03d3566a0 100644 --- a/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing_test.go +++ b/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing_test.go @@ -2,8 +2,10 @@ package routing_queue_conditional_group_routing import ( "fmt" + "log" "os" "strings" + "sync" gcloud "terraform-provider-genesyscloud/genesyscloud" "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" @@ -16,6 +18,12 @@ import ( "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/mypurecloud/platform-client-sdk-go/v130/platformclientv2" +) + +var ( + sdkConfig *platformclientv2.Configuration + mu sync.Mutex ) func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { @@ -36,7 +44,7 @@ func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() - testUserEmail = uuid.NewString() + "@example.com" + testUserEmail = uuid.NewString() + "@exampletest.com" groupResourceId = "group" groupName = "terraform test group" + uuid.NewString() @@ -46,6 +54,7 @@ func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { conditionalGroupRoutingRule2ConditionValue = "5" conditionalGroupRoutingRule2WaitSeconds = "15" conditionalGroupRoutingRule2GroupType = "GROUP" + userID string ) // Use this to save the id of the parent queue @@ -242,7 +251,12 @@ func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { "genesyscloud_routing_queue_conditional_group_routing."+conditionalGroupRoutingResource, "rules.0.groups.0.member_group_id", "genesyscloud_group."+groupResourceId, "id", ), func(s *terraform.State) error { - time.Sleep(60 * time.Second) // Wait for 60 seconds for resources to get deleted properly + rs, ok := s.RootModule().Resources["genesyscloud_user."+testUserResource] + if !ok { + return fmt.Errorf("not found: %s", "genesyscloud_user."+testUserResource) + } + userID = rs.Primary.ID + log.Printf("User ID: %s\n", userID) // Print user ID return nil }, ), @@ -252,6 +266,9 @@ func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { ResourceName: "genesyscloud_routing_queue_conditional_group_routing." + conditionalGroupRoutingResource, ImportState: true, ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + checkUserDeleted(userID), + ), }, }, }) @@ -313,3 +330,44 @@ func generateUserWithCustomAttrs(resourceID string, email string, name string, a } `, resourceID, email, name, strings.Join(attrs, "\n")) } +func checkUserDeleted(id string) resource.TestCheckFunc { + log.Printf("Fetching user with ID: %s\n", id) + return func(s *terraform.State) error { + maxAttempts := 18 + for i := 0; i < maxAttempts; i++ { + + deleted, err := isUserDeleted(id) + if err != nil { + return err + } + if deleted { + return nil + } + time.Sleep(10 * time.Second) + } + return fmt.Errorf("user %s was not deleted properly", id) + } +} + +func isUserDeleted(id string) (bool, error) { + mu.Lock() + defer mu.Unlock() + + usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + // Attempt to get the user + _, response, err := usersAPI.GetUser(id, nil, "", "") + + // Check if the user is not found (deleted) + if response != nil && response.StatusCode == 404 { + return true, nil // User is deleted + } + + // Handle other errors + if err != nil { + log.Printf("Error fetching user: %v", err) + return false, err + } + + // If user is found, it means the user is not deleted + return false, nil +} diff --git a/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_utils.go b/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_utils.go index 403d4c92a..679931d83 100644 --- a/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_utils.go +++ b/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_utils.go @@ -79,25 +79,63 @@ func getWorktypecreateFromResourceData(d *schema.ResourceData) platformclientv2. // getWorktypeupdateFromResourceData maps data from schema ResourceData object to a platformclientv2.Worktypeupdate func getWorktypeupdateFromResourceData(d *schema.ResourceData, statuses *[]platformclientv2.Workitemstatus) platformclientv2.Worktypeupdate { - worktype := platformclientv2.Worktypeupdate{ - Name: platformclientv2.String(d.Get("name").(string)), - Description: platformclientv2.String(d.Get("description").(string)), - DefaultWorkbinId: platformclientv2.String(d.Get("default_workbin_id").(string)), - DefaultPriority: platformclientv2.Int(d.Get("default_priority").(int)), + worktype := platformclientv2.Worktypeupdate{} + worktype.SetField("Name", platformclientv2.String(d.Get("name").(string))) + if d.HasChange("Description") { + worktype.SetField("Description", platformclientv2.String(d.Get("description").(string))) + } + if d.HasChange("default_workbin_id") { + worktype.SetField("DefaultWorkbinId", platformclientv2.String(d.Get("default_workbin_id").(string))) + } - DefaultLanguageId: resourcedata.GetNillableValue[string](d, "default_language_id"), - DefaultQueueId: resourcedata.GetNillableValue[string](d, "default_queue_id"), - DefaultSkillIds: lists.BuildSdkStringListFromInterfaceArray(d, "default_skills_ids"), - AssignmentEnabled: platformclientv2.Bool(d.Get("assignment_enabled").(bool)), + if d.HasChange("default_priority") { + worktype.SetField("DefaultPriority", platformclientv2.Int(d.Get("default_priority").(int))) + } - DefaultDurationSeconds: resourcedata.GetNillableValue[int](d, "default_duration_seconds"), - DefaultExpirationSeconds: resourcedata.GetNillableValue[int](d, "default_expiration_seconds"), - DefaultDueDurationSeconds: resourcedata.GetNillableValue[int](d, "default_due_duration_seconds"), - DefaultTtlSeconds: resourcedata.GetNillableValue[int](d, "default_ttl_seconds"), + if d.HasChange("schema_id") { + worktype.SetField("SchemaId", platformclientv2.String(d.Get("schema_id").(string))) + } + + if d.HasChange("division_id") { + worktype.SetField("DivisionId", platformclientv2.String(d.Get("division_id").(string))) + } - DefaultStatusId: getStatusIdFromName(d.Get("default_status_name").(string), statuses), - SchemaVersion: resourcedata.GetNillableValue[int](d, "schema_version"), + if d.HasChange("default_language_id") { + worktype.SetField("DefaultLanguageId", resourcedata.GetNillableValue[string](d, "default_language_id")) + } + + if d.HasChange("default_queue_id") { + worktype.SetField("DefaultQueueId", resourcedata.GetNillableValue[string](d, "default_queue_id")) + } + + if d.HasChange("default_skills_ids") { + worktype.SetField("DefaultSkillIds", lists.BuildSdkStringListFromInterfaceArray(d, "default_skills_ids")) + } + + if d.HasChange("assignment_enabled") { + worktype.SetField("AssignmentEnabled", platformclientv2.Bool(d.Get("assignment_enabled").(bool))) + } + + if d.HasChange("default_status_name") { + worktype.SetField("DefaultStatusId", getStatusIdFromName(d.Get("default_status_name").(string), statuses)) + } + + if d.HasChange("schema_version") { + worktype.SetField("SchemaVersion", resourcedata.GetNillableValue[int](d, "schema_version")) + } + + if d.HasChange("default_duration_seconds") { + worktype.SetField("DefaultDurationSeconds", resourcedata.GetNillableValue[int](d, "default_duration_seconds")) + } + if d.HasChange("default_expiration_seconds") { + worktype.SetField("DefaultExpirationSeconds", resourcedata.GetNillableValue[int](d, "default_duration_seconds")) + } + if d.HasChange("default_due_duration_seconds") { + worktype.SetField("DefaultDueDurationSeconds", resourcedata.GetNillableValue[int](d, "default_due_duration_seconds")) + } + if d.HasChange("default_ttl_seconds") { + worktype.SetField("DefaultTtlSeconds", resourcedata.GetNillableValue[int](d, "default_ttl_seconds")) } return worktype diff --git a/genesyscloud/team/resource_genesyscloud_team_test.go b/genesyscloud/team/resource_genesyscloud_team_test.go index 541c7b96f..978b50b0f 100644 --- a/genesyscloud/team/resource_genesyscloud_team_test.go +++ b/genesyscloud/team/resource_genesyscloud_team_test.go @@ -188,6 +188,7 @@ func TestAccResourceTeamRemoveMembers(t *testing.T) { name1, "genesyscloud_auth_division."+divResource+".id", description1, + generateMemberIdsArray([]string{}), ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_team."+resourceId, "name", name1), diff --git a/genesyscloud/telephony_providers_edges_phone/resource_genesyscloud_telephony_providers_edges_phone_test.go b/genesyscloud/telephony_providers_edges_phone/resource_genesyscloud_telephony_providers_edges_phone_test.go index 01e9ad90d..eca79cff8 100644 --- a/genesyscloud/telephony_providers_edges_phone/resource_genesyscloud_telephony_providers_edges_phone_test.go +++ b/genesyscloud/telephony_providers_edges_phone/resource_genesyscloud_telephony_providers_edges_phone_test.go @@ -6,10 +6,12 @@ import ( "strings" gcloud "terraform-provider-genesyscloud/genesyscloud" "terraform-provider-genesyscloud/genesyscloud/provider" + didPool "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_did_pool" phoneBaseSettings "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_phonebasesettings" edgeSite "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_site" "terraform-provider-genesyscloud/genesyscloud/util" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -186,17 +188,7 @@ func TestAccResourcePhoneBasic(t *testing.T) { func TestAccResourcePhoneStandalone(t *testing.T) { number := "+12005538112" - // TODO: Use did pool resource inside config once cyclic dependency issue is resolved between genesyscloud and did_pools package - didPoolId, err := createDidPoolForEdgesPhoneTest(sdkConfig, number) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := deleteDidPool(sdkConfig, didPoolId); err != nil { - t.Logf("failed to delete did pool '%s': %v", didPoolId, err) - } - }() - + didPoolResource1 := "test-didpool1" lineAddresses := []string{number} phoneRes := "phone_standalone1234" name1 := "test-phone-standalone_" + uuid.NewString() @@ -207,7 +199,7 @@ func TestAccResourcePhoneStandalone(t *testing.T) { locationRes := "test-location" emergencyNumber := "+13173114121" - if err = edgeSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { + if err := edgeSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { t.Skipf("failed to delete location with number %s: %v", emergencyNumber, err) } @@ -253,8 +245,15 @@ func TestAccResourcePhoneStandalone(t *testing.T) { "mac", []string{}, ) - - config := phoneBaseSettings.GeneratePhoneBaseSettingsResourceWithCustomAttrs( + config := didPool.GenerateDidPoolResource(&didPool.DidPoolStruct{ + ResourceID: didPoolResource1, + StartPhoneNumber: lineAddresses[0], + EndPhoneNumber: lineAddresses[0], + Description: util.NullValue, // No description + Comments: util.NullValue, // No comments + PoolProvider: util.NullValue, // No provider + }) + config += phoneBaseSettings.GeneratePhoneBaseSettingsResourceWithCustomAttrs( phoneBaseSettingsRes, phoneBaseSettingsName, "phoneBaseSettings description", @@ -267,7 +266,7 @@ func TestAccResourcePhoneStandalone(t *testing.T) { "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", lineAddresses, "", // no web rtc user - "", // no Depends On + "genesyscloud_telephony_providers_edges_did_pool." + didPoolResource1, }, capabilities, generatePhoneProperties(uuid.NewString())) resource.Test(t, resource.TestCase{ @@ -275,6 +274,9 @@ func TestAccResourcePhoneStandalone(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { + PreConfig: func() { + time.Sleep(30 * time.Second) + }, Config: locationConfig + siteConfig + config, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "name", name1), diff --git a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_test.go b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_test.go index 8cc09c981..aa1a90ca5 100644 --- a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_test.go +++ b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_test.go @@ -161,6 +161,197 @@ func TestAccResourceSite(t *testing.T) { }) } +func TestAccResourceSiteoutboundRoute(t *testing.T) { + if exists := featureToggles.OutboundRoutesToggleExists(); exists { + // Unset outbound routes feature toggle so outbound routes will be managed by the site resource for this test + err := os.Unsetenv(featureToggles.OutboundRoutesToggleName()) + if err != nil { + t.Skipf("%v is set and unable to unset, skipping test", featureToggles.OutboundRoutesToggleName()) + } + } + var ( + // site + siteRes = "site" + name = "site " + uuid.NewString() + description = "terraform description 1" + mediaModel = "Cloud" + + // location + locationRes = "test-location1" + ) + + emergencyNumber := "+13173124743" + if err := DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { + t.Skipf("failed to delete location with number %s, %v", emergencyNumber, err) + } + + location := gcloud.GenerateLocationResource( + locationRes, + "Terraform location"+uuid.NewString(), + "HQ1", + []string{}, + gcloud.GenerateLocationEmergencyNum( + emergencyNumber, + util.NullValue, // Default number type + ), gcloud.GenerateLocationAddress( + "7601 Interactive Way", + "Indianapolis", + "IN", + "US", + "46278", + )) + + trunkBaseSettings1 := telephony.GenerateTrunkBaseSettingsResourceWithCustomAttrs( + "trunkBaseSettings1", + "test trunk base settings "+uuid.NewString(), + "test description", + "external_sip.json", + "EXTERNAL", + false) + + trunkBaseSettings2 := telephony.GenerateTrunkBaseSettingsResourceWithCustomAttrs( + "trunkBaseSettings2", + "test trunk base settings "+uuid.NewString(), + "test description", + "external_sip.json", + "EXTERNAL", + false) + + trunkBaseSettings3 := telephony.GenerateTrunkBaseSettingsResourceWithCustomAttrs( + "trunkBaseSettings3", + "test trunk base settings "+uuid.NewString(), + "test description", + "external_sip.json", + "EXTERNAL", + false) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + Config: GenerateSiteResourceWithCustomAttrs( + siteRes, + name, + description, + "genesyscloud_location."+locationRes+".id", + mediaModel, + false, + util.AssignRegion(), + strconv.Quote("+19205551212"), + strconv.Quote("Wilco plumbing"), + generateSiteOutboundRoutesWithCustomAttrs( + "outboundRoute name 1", + "outboundRoute description", + "\"International\"", + "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1.id", + "RANDOM", + false), + generateSiteOutboundRoutesWithCustomAttrs( + "outboundRoute name 2", + "outboundRoute description", + "\"National\"", + "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2.id", + "SEQUENTIAL", + false)+"set_as_default_site = false") + trunkBaseSettings1 + trunkBaseSettings2 + location, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.name", "outboundRoute name 1"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.description", "outboundRoute description"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.0", "International"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.distribution", "RANDOM"), + resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1", "id"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.enabled", util.FalseValue), + + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.name", "outboundRoute name 2"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.description", "outboundRoute description"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.classification_types.0", "National"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.distribution", "SEQUENTIAL"), + resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2", "id"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.enabled", util.FalseValue), + ), + }, + // Switch around the order of outbound routes which shouldn't have any effect + { + Config: GenerateSiteResourceWithCustomAttrs( + siteRes, + name, + description, + "genesyscloud_location."+locationRes+".id", + mediaModel, + false, + util.AssignRegion(), + strconv.Quote("+19205551212"), + strconv.Quote("Wilco plumbing"), + generateSiteOutboundRoutesWithCustomAttrs( + "outboundRoute name 2", + "outboundRoute description", + "\"National\"", + "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2.id", + "SEQUENTIAL", + false), + generateSiteOutboundRoutesWithCustomAttrs( + "outboundRoute name 1", + "outboundRoute description", + "\"International\"", + "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1.id", + "RANDOM", + false)) + trunkBaseSettings1 + trunkBaseSettings2 + location, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.name", "outboundRoute name 1"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.description", "outboundRoute description"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.0", "International"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.distribution", "RANDOM"), + resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1", "id"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.enabled", util.FalseValue), + + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.name", "outboundRoute name 2"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.description", "outboundRoute description"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.classification_types.0", "National"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.distribution", "SEQUENTIAL"), + resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2", "id"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.enabled", util.FalseValue), + ), + }, + // Remove a route and update the description, classification types, trunk base ids, distribution and enabled value of another route + { + Config: GenerateSiteResourceWithCustomAttrs( + siteRes, + name, + description, + "genesyscloud_location."+locationRes+".id", + mediaModel, + false, + util.AssignRegion(), + strconv.Quote("+19205551212"), + strconv.Quote("Wilco plumbing"), + generateSiteOutboundRoutesWithCustomAttrs( + "outboundRoute name 1", + "outboundRoute description updated", + strings.Join([]string{strconv.Quote("Network"), strconv.Quote("International")}, ","), + strings.Join([]string{"genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1.id", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings3.id"}, ","), + "RANDOM", + true)+"set_as_default_site = false") + trunkBaseSettings1 + trunkBaseSettings2 + trunkBaseSettings3 + location, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.name", "outboundRoute name 1"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.description", "outboundRoute description updated"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.0", "Network"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.1", "International"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.distribution", "RANDOM"), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.enabled", util.TrueValue), + resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1", "id"), + resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.1", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings3", "id"), + ), + }, + { + // Import/Read + ResourceName: "genesyscloud_telephony_providers_edges_site." + siteRes, + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testVerifySitesDestroyed, + }) +} func TestAccResourceSiteNumberPlans(t *testing.T) { t.Parallel() var ( @@ -356,200 +547,6 @@ func TestAccResourceSiteNumberPlans(t *testing.T) { }) } -func TestAccResourceSiteOutboundRoutes(t *testing.T) { - if exists := featureToggles.OutboundRoutesToggleExists(); exists { - // Unset outbound routes feature toggle so outbound routes will be managed by the site resource for this test - err := os.Unsetenv(featureToggles.OutboundRoutesToggleName()) - if err != nil { - t.Skipf("%v is set and unable to unset, skipping test", featureToggles.OutboundRoutesToggleName()) - } - } - - t.Parallel() - var ( - // site - siteRes = "site" - name = "site " + uuid.NewString() - description = "terraform description 1" - mediaModel = "Cloud" - - // location - locationRes = "test-location1" - ) - - emergencyNumber := "+13173124743" - if err := DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { - t.Skipf("failed to delete location with number %s, %v", emergencyNumber, err) - } - - location := gcloud.GenerateLocationResource( - locationRes, - "Terraform location"+uuid.NewString(), - "HQ1", - []string{}, - gcloud.GenerateLocationEmergencyNum( - emergencyNumber, - util.NullValue, // Default number type - ), gcloud.GenerateLocationAddress( - "7601 Interactive Way", - "Indianapolis", - "IN", - "US", - "46278", - )) - - trunkBaseSettings1 := telephony.GenerateTrunkBaseSettingsResourceWithCustomAttrs( - "trunkBaseSettings1", - "test trunk base settings "+uuid.NewString(), - "test description", - "external_sip.json", - "EXTERNAL", - false) - - trunkBaseSettings2 := telephony.GenerateTrunkBaseSettingsResourceWithCustomAttrs( - "trunkBaseSettings2", - "test trunk base settings "+uuid.NewString(), - "test description", - "external_sip.json", - "EXTERNAL", - false) - - trunkBaseSettings3 := telephony.GenerateTrunkBaseSettingsResourceWithCustomAttrs( - "trunkBaseSettings3", - "test trunk base settings "+uuid.NewString(), - "test description", - "external_sip.json", - "EXTERNAL", - false) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { util.TestAccPreCheck(t) }, - ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), - Steps: []resource.TestStep{ - { - Config: GenerateSiteResourceWithCustomAttrs( - siteRes, - name, - description, - "genesyscloud_location."+locationRes+".id", - mediaModel, - false, - util.AssignRegion(), - strconv.Quote("+19205551212"), - strconv.Quote("Wilco plumbing"), - generateSiteOutboundRoutesWithCustomAttrs( - "outboundRoute name 1", - "outboundRoute description", - "\"International\"", - "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1.id", - "RANDOM", - false), - generateSiteOutboundRoutesWithCustomAttrs( - "outboundRoute name 2", - "outboundRoute description", - "\"National\"", - "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2.id", - "SEQUENTIAL", - false)+"set_as_default_site = false") + trunkBaseSettings1 + trunkBaseSettings2 + location, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.name", "outboundRoute name 1"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.description", "outboundRoute description"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.0", "International"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.distribution", "RANDOM"), - resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1", "id"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.enabled", util.FalseValue), - - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.name", "outboundRoute name 2"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.description", "outboundRoute description"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.classification_types.0", "National"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.distribution", "SEQUENTIAL"), - resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2", "id"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.enabled", util.FalseValue), - ), - }, - // Switch around the order of outbound routes which shouldn't have any effect - { - Config: GenerateSiteResourceWithCustomAttrs( - siteRes, - name, - description, - "genesyscloud_location."+locationRes+".id", - mediaModel, - false, - util.AssignRegion(), - strconv.Quote("+19205551212"), - strconv.Quote("Wilco plumbing"), - generateSiteOutboundRoutesWithCustomAttrs( - "outboundRoute name 2", - "outboundRoute description", - "\"National\"", - "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2.id", - "SEQUENTIAL", - false), - generateSiteOutboundRoutesWithCustomAttrs( - "outboundRoute name 1", - "outboundRoute description", - "\"International\"", - "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1.id", - "RANDOM", - false)) + trunkBaseSettings1 + trunkBaseSettings2 + location, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.name", "outboundRoute name 1"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.description", "outboundRoute description"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.0", "International"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.distribution", "RANDOM"), - resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1", "id"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.enabled", util.FalseValue), - - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.name", "outboundRoute name 2"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.description", "outboundRoute description"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.classification_types.0", "National"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.distribution", "SEQUENTIAL"), - resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings2", "id"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.1.enabled", util.FalseValue), - ), - }, - // Remove a route and update the description, classification types, trunk base ids, distribution and enabled value of another route - { - Config: GenerateSiteResourceWithCustomAttrs( - siteRes, - name, - description, - "genesyscloud_location."+locationRes+".id", - mediaModel, - false, - util.AssignRegion(), - strconv.Quote("+19205551212"), - strconv.Quote("Wilco plumbing"), - generateSiteOutboundRoutesWithCustomAttrs( - "outboundRoute name 1", - "outboundRoute description updated", - strings.Join([]string{strconv.Quote("Network"), strconv.Quote("International")}, ","), - strings.Join([]string{"genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1.id", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings3.id"}, ","), - "RANDOM", - true)+"set_as_default_site = false") + trunkBaseSettings1 + trunkBaseSettings2 + trunkBaseSettings3 + location, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.name", "outboundRoute name 1"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.description", "outboundRoute description updated"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.0", "Network"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.classification_types.1", "International"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.distribution", "RANDOM"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.enabled", util.TrueValue), - resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.0", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings1", "id"), - resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_site."+siteRes, "outbound_routes.0.external_trunk_base_ids.1", "genesyscloud_telephony_providers_edges_trunkbasesettings.trunkBaseSettings3", "id"), - ), - }, - { - // Import/Read - ResourceName: "genesyscloud_telephony_providers_edges_site." + siteRes, - ImportState: true, - ImportStateVerify: true, - }, - }, - CheckDestroy: testVerifySitesDestroyed, - }) -} - func TestAccResourceSiteDefaultSite(t *testing.T) { var ( // site diff --git a/genesyscloud/telephony_providers_edges_site_outbound_route/resource_genesyscloud_telephony_providers_edges_site_outbound_route_test.go b/genesyscloud/telephony_providers_edges_site_outbound_route/resource_genesyscloud_telephony_providers_edges_site_outbound_route_test.go index a110231d8..d6ee123e1 100644 --- a/genesyscloud/telephony_providers_edges_site_outbound_route/resource_genesyscloud_telephony_providers_edges_site_outbound_route_test.go +++ b/genesyscloud/telephony_providers_edges_site_outbound_route/resource_genesyscloud_telephony_providers_edges_site_outbound_route_test.go @@ -2,8 +2,6 @@ package telephony_providers_edges_site_outbound_route import ( "fmt" - "github.com/google/uuid" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "log" "os" "strconv" @@ -15,9 +13,12 @@ import ( "terraform-provider-genesyscloud/genesyscloud/util" featureToggles "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles" "testing" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccResourceSiteOutboundRoutes(t *testing.T) { +func TestAccResourceSiteoutboundRoutes(t *testing.T) { defer func() { err := os.Unsetenv(featureToggles.OutboundRoutesToggleName()) if err != nil { @@ -43,7 +44,7 @@ func TestAccResourceSiteOutboundRoutes(t *testing.T) { locationRes = "test-location1" ) - emergencyNumber := "+13173124743" + emergencyNumber := "+13173124741" if err := telephonyProvidersEdgesSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { t.Skipf("failed to delete location with number %s, %v", emergencyNumber, err) } @@ -95,7 +96,7 @@ func TestAccResourceSiteOutboundRoutes(t *testing.T) { "genesyscloud_location."+locationRes+".id", mediaModel, false, - "[\"us-west-2\"]", + util.AssignRegion(), strconv.Quote("+19205551212"), strconv.Quote("Wilco plumbing"), "set_as_default_site = false") diff --git a/genesyscloud/tfexporter/tf_exporter_resource_test.go b/genesyscloud/tfexporter/tf_exporter_resource_test.go index ae76448ae..3ea8cfa57 100644 --- a/genesyscloud/tfexporter/tf_exporter_resource_test.go +++ b/genesyscloud/tfexporter/tf_exporter_resource_test.go @@ -20,6 +20,8 @@ import ( flowOutcome "terraform-provider-genesyscloud/genesyscloud/flow_outcome" "terraform-provider-genesyscloud/genesyscloud/group" groupRoles "terraform-provider-genesyscloud/genesyscloud/group_roles" + idpAdfs "terraform-provider-genesyscloud/genesyscloud/idp_adfs" + idpOkta "terraform-provider-genesyscloud/genesyscloud/idp_okta" idpSalesforce "terraform-provider-genesyscloud/genesyscloud/idp_salesforce" integration "terraform-provider-genesyscloud/genesyscloud/integration" integrationAction "terraform-provider-genesyscloud/genesyscloud/integration_action" @@ -115,10 +117,10 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_flow_loglevel"] = flowLogLevel.ResourceFlowLoglevel() providerResources["genesyscloud_group"] = group.ResourceGroup() providerResources["genesyscloud_group_roles"] = groupRoles.ResourceGroupRoles() - providerResources["genesyscloud_idp_adfs"] = gcloud.ResourceIdpAdfs() + providerResources["genesyscloud_idp_adfs"] = idpAdfs.ResourceIdpAdfs() providerResources["genesyscloud_idp_generic"] = gcloud.ResourceIdpGeneric() providerResources["genesyscloud_idp_gsuite"] = gcloud.ResourceIdpGsuite() - providerResources["genesyscloud_idp_okta"] = gcloud.ResourceIdpOkta() + providerResources["genesyscloud_idp_okta"] = idpOkta.ResourceIdpOkta() providerResources["genesyscloud_idp_onelogin"] = gcloud.ResourceIdpOnelogin() providerResources["genesyscloud_idp_ping"] = gcloud.ResourceIdpPing() providerResources["genesyscloud_idp_salesforce"] = idpSalesforce.ResourceIdpSalesforce() @@ -214,10 +216,10 @@ func (r *registerTestInstance) registerTestExporters() { RegisterExporter("genesyscloud_flow_outcome", flowOutcome.FlowOutcomeExporter()) RegisterExporter("genesyscloud_group", group.GroupExporter()) RegisterExporter("genesyscloud_group_roles", groupRoles.GroupRolesExporter()) - RegisterExporter("genesyscloud_idp_adfs", gcloud.IdpAdfsExporter()) + RegisterExporter("genesyscloud_idp_adfs", idpAdfs.IdpAdfsExporter()) RegisterExporter("genesyscloud_idp_generic", gcloud.IdpGenericExporter()) RegisterExporter("genesyscloud_idp_gsuite", gcloud.IdpGsuiteExporter()) - RegisterExporter("genesyscloud_idp_okta", gcloud.IdpOktaExporter()) + RegisterExporter("genesyscloud_idp_okta", idpOkta.IdpOktaExporter()) RegisterExporter("genesyscloud_idp_onelogin", gcloud.IdpOneloginExporter()) RegisterExporter("genesyscloud_idp_ping", gcloud.IdpPingExporter()) RegisterExporter("genesyscloud_idp_salesforce", idpSalesforce.IdpSalesforceExporter()) diff --git a/go.mod b/go.mod index afd17f429..97d022f1b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/hcl/v2 v2.20.1 - github.com/hashicorp/terraform-plugin-docs v0.19.2 + github.com/hashicorp/terraform-plugin-docs v0.19.4 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 @@ -35,7 +35,8 @@ require ( github.com/yuin/goldmark-meta v1.1.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -59,8 +60,8 @@ require ( github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hc-install v0.6.4 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hc-install v0.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect @@ -93,11 +94,11 @@ require ( github.com/subosito/gotenv v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.25.0 - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.26.0 + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.34.0 // indirect diff --git a/go.sum b/go.sum index e8ae8dce9..fc5087feb 100644 --- a/go.sum +++ b/go.sum @@ -157,13 +157,13 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= -github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= +github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= +github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= @@ -177,8 +177,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-docs v0.19.2 h1:YjdKa1vuqt9EnPYkkrv9HnGZz175HhSJ7Vsn8yZeWus= -github.com/hashicorp/terraform-plugin-docs v0.19.2/go.mod h1:gad2aP6uObFKhgNE8DR9nsEuEQnibp7il0jZYYOunWY= +github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= +github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= @@ -362,8 +362,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -385,8 +385,8 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -404,8 +404,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -415,7 +415,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.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= @@ -441,8 +442,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -453,8 +454,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -476,8 +477,8 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= diff --git a/jenkins/tests/Jenkinsfile b/jenkins/tests/Jenkinsfile index 7e5a91cd5..7fd3f102b 100644 --- a/jenkins/tests/Jenkinsfile +++ b/jenkins/tests/Jenkinsfile @@ -328,6 +328,7 @@ pipeline { steps { sh 'GOBIN=$HOME/bin go install github.com/wadey/gocovmerge@latest' + // Generate merged coverage report sh '$HOME/bin/gocovmerge coverageArchitect.out coverageIdp.out coverageAuth.out coverageIntegration.out coverageFlow.out coverageJourney.out coverageKnowledge.out coverageOutbound.out coverageResponseManagement.out coverageRouting.out coverageExport.out coverageLocation.out coverageWebDeployment.out coverageRemaining.out coverageSite.out coverageRoleTeam.out > merged_coverage.out' @@ -335,10 +336,167 @@ pipeline { sh 'go tool cover -html merged_coverage.out -o coverageAcceptance.html' sh 'junit-merger -o test-results.xml architect.xml idp.xml auth.xml integration.xml flow.xml journey.xml knowledge.xml outbound.xml response.xml routing.xml location.xml web.xml site.xml team.xml export.xml remaining.xml' - - - script { - // Read the XML file content + + script { + // Read the generated HTML file + def htmlFile = readFile 'coverageAcceptance.html' + def selectStart = htmlFile.indexOf('', selectStart) + def fileCoverageSection = htmlFile.substring(selectStart, selectEnd + 9) // +9 to include + + def packageCoverage = [:] + def packageFiles = [:] + def packageFileMap = [:] // To store package and their corresponding files + def fileNumberMap = [:] // To store package and their corresponding file numbers + + // Parse the existing options to calculate package-wise coverage + fileCoverageSection.split('\n').each { line -> + if (line.contains('