diff --git a/.github/workflows/generate.yml b/.github/workflows/go-checks.yml similarity index 64% rename from .github/workflows/generate.yml rename to .github/workflows/go-checks.yml index a51777042..26d45ef2f 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/go-checks.yml @@ -1,12 +1,11 @@ -name: Ensure Documentation in Sync +name: Go Checks on: push: paths-ignore: - 'README.md' jobs: - # ensure the documentation is up to date - generate: - name: Generate + go-checks: + name: Go Checks runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -35,5 +34,20 @@ jobs: if [[ -n $(git status -s) ]]; then echo "There are untracked documentation changes:\n" git status + fi + + - name: Tidy + run: | + go mod tidy + if [[ -n $(git status -s) ]]; then + echo "go mod tidy produced changes:\n" + git status + fi + + - name: Vet + run: | + if [[ -n $(go vet ./genesyscloud/... 2>&1) ]]; then + echo "go vet highlighted the following:\n" + go vet ./genesyscloud/... exit 1 fi \ No newline at end of file diff --git a/docs/data-sources/conversations_messaging_settings.md b/docs/data-sources/conversations_messaging_settings.md new file mode 100644 index 000000000..d093dd0ed --- /dev/null +++ b/docs/data-sources/conversations_messaging_settings.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "genesyscloud_conversations_messaging_settings Data Source - terraform-provider-genesyscloud" +subcategory: "" +description: |- + Genesys Cloud conversations messaging settings data source. Select an conversations messaging settings by name +--- + +# genesyscloud_conversations_messaging_settings (Data Source) + +Genesys Cloud conversations messaging settings data source. Select an conversations messaging settings by name + +## Example Usage + +```terraform +data "genesyscloud_conversations_messaging_settings" "example-messaging-settings" { + name = "settings name" +} +``` + + +## Schema + +### Required + +- `name` (String) conversations messaging settings name + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/data-sources/outbound_contact_list_template.md b/docs/data-sources/outbound_contact_list_template.md new file mode 100644 index 000000000..ea530a9c4 --- /dev/null +++ b/docs/data-sources/outbound_contact_list_template.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "genesyscloud_outbound_contact_list_template Data Source - terraform-provider-genesyscloud" +subcategory: "" +description: |- + Data source for Genesys Cloud Outbound Contact Lists Templates. Select a contact list template by name. +--- + +# genesyscloud_outbound_contact_list_template (Data Source) + +Data source for Genesys Cloud Outbound Contact Lists Templates. Select a contact list template by name. + +## Example Usage + +```terraform +data "genesyscloud_outbound_contact_list_template" "contact_list_template" { + name = "Example Contact List Template" +} +``` + + +## Schema + +### Required + +- `name` (String) Contact List Template name. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/data-sources/telephony_providers_edges_site.md b/docs/data-sources/telephony_providers_edges_site.md index 0f4612dc6..635ca22d5 100644 --- a/docs/data-sources/telephony_providers_edges_site.md +++ b/docs/data-sources/telephony_providers_edges_site.md @@ -25,10 +25,6 @@ data "genesyscloud_telephony_providers_edges_site" "site" { - `name` (String) Site name. -### Optional - -- `managed` (Boolean) Return entities that are managed by Genesys Cloud. Defaults to `false`. - ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/conversations_messaging_settings.md b/docs/resources/conversations_messaging_settings.md new file mode 100644 index 000000000..b2a86a76b --- /dev/null +++ b/docs/resources/conversations_messaging_settings.md @@ -0,0 +1,116 @@ +--- +page_title: "genesyscloud_conversations_messaging_settings Resource - terraform-provider-genesyscloud" +subcategory: "" +description: |- + Genesys Cloud conversations messaging settings +--- +# genesyscloud_conversations_messaging_settings (Resource) + +Genesys Cloud conversations messaging settings + +## 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/conversations/messaging/settings](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-conversations-messaging-settings) +* [POST /api/v2/conversations/messaging/settings](https://developer.genesys.cloud/devapps/api-explorer#post-api-v2-conversations-messaging-settings) +* [GET /api/v2/conversations/messaging/settings/{messageSettingId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-conversations-messaging-settings--messageSettingId-) +* [PATCH /api/v2/conversations/messaging/settings/{messageSettingId}](https://developer.genesys.cloud/devapps/api-explorer#patch-api-v2-conversations-messaging-settings--messageSettingId-) +* [DELETE /api/v2/conversations/messaging/settings/{messageSettingId}](https://developer.genesys.cloud/devapps/api-explorer#delete-api-v2-conversations-messaging-settings--messageSettingId-) + +## Example Usage + +```terraform +resource "genesyscloud_conversations_messaging_settings" "example-messaging-settings" { + name = "Sample Messaging Settings" + content { + story { + mention { + inbound = "Enabled" + } + reply { + inbound = "Enabled" + } + } + } + event { + typing { + on { + inbound = "Enabled" + outbound = "Enabled" + } + } + } +} +``` + + +## Schema + +### Required + +- `name` (String) The messaging Setting profile name + +### Optional + +- `content` (Block List, Max: 1) Settings relating to message contents (see [below for nested schema](#nestedblock--content)) +- `event` (Block List, Max: 1) Settings relating to events which may occur (see [below for nested schema](#nestedblock--event)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `content` + +Optional: + +- `story` (Block List, Max: 1) Settings relating to facebook and instagram stories feature (see [below for nested schema](#nestedblock--content--story)) + + +### Nested Schema for `content.story` + +Optional: + +- `mention` (Block List, Max: 1) Setting relating to Story Mentions (see [below for nested schema](#nestedblock--content--story--mention)) +- `reply` (Block List, Max: 1) Setting relating to Story Replies (see [below for nested schema](#nestedblock--content--story--reply)) + + +### Nested Schema for `content.story.mention` + +Optional: + +- `inbound` (String) Valid values: Enabled, Disabled. + + + +### Nested Schema for `content.story.reply` + +Optional: + +- `inbound` (String) Valid values: Enabled, Disabled. + + + + + +### Nested Schema for `event` + +Optional: + +- `typing` (Block List, Max: 1) Settings regarding typing events (see [below for nested schema](#nestedblock--event--typing)) + + +### Nested Schema for `event.typing` + +Optional: + +- `on` (Block List, Max: 1) Should typing indication Events be sent (see [below for nested schema](#nestedblock--event--typing--on)) + + +### Nested Schema for `event.typing.on` + +Optional: + +- `inbound` (String) Status for the Inbound Direction. Valid values: Enabled, Disabled. +- `outbound` (String) Status for the outbound Direction. Valid values: Enabled, Disabled. + diff --git a/docs/resources/journey_action_map.md b/docs/resources/journey_action_map.md index 883fd233a..95b29d98c 100644 --- a/docs/resources/journey_action_map.md +++ b/docs/resources/journey_action_map.md @@ -54,7 +54,8 @@ resource "genesyscloud_journey_action_map" "example_journey_action_map" { - `is_active` (Boolean) Whether the action map is active. Defaults to `true`. - `page_url_conditions` (Block Set) URL conditions that a page must match for web actions to be displayable. (see [below for nested schema](#nestedblock--page_url_conditions)) - `trigger_with_event_conditions` (Block Set) List of event conditions that must be satisfied to trigger the action map. (see [below for nested schema](#nestedblock--trigger_with_event_conditions)) -- `trigger_with_outcome_probability_conditions` (Block Set) Probability conditions for outcomes that must be satisfied to trigger the action map. (see [below for nested schema](#nestedblock--trigger_with_outcome_probability_conditions)) +- `trigger_with_outcome_probability_conditions` (Block Set, Deprecated) Probability conditions for outcomes that must be satisfied to trigger the action map. (see [below for nested schema](#nestedblock--trigger_with_outcome_probability_conditions)) +- `trigger_with_outcome_quantile_conditions` (Block Set) Quantile conditions for outcomes that must be satisfied to trigger the action map. (see [below for nested schema](#nestedblock--trigger_with_outcome_quantile_conditions)) - `trigger_with_segments` (Set of String) Trigger action map if any segment in the list is assigned to a given customer. - `weight` (Number) Weight of the action map with higher number denoting higher weight. Low=1, Medium=2, High=3. Defaults to `2`. @@ -192,3 +193,16 @@ Optional: - `probability` (Number) Additional probability condition, where if set, the action map will trigger if the current outcome probability is lower or equal to the value. + + +### Nested Schema for `trigger_with_outcome_quantile_conditions` + +Required: + +- `max_quantile_threshold` (Number) This Outcome Quantile Condition is met when sessionMaxQuantile of the OutcomeScore is above this value, (unless fallbackQuantile is set). Range 0.00-1.00 +- `outcome_id` (String) The outcome ID. + +Optional: + +- `fallback_quantile_threshold` (Number) If set, this Condition is met when max_quantile_threshold is met, AND the current quantile of the OutcomeScore is below this fallback_quantile_threshold. Range 0.00-1.00 + diff --git a/docs/resources/outbound_contact_list_template.md b/docs/resources/outbound_contact_list_template.md new file mode 100644 index 000000000..292585fc0 --- /dev/null +++ b/docs/resources/outbound_contact_list_template.md @@ -0,0 +1,101 @@ +--- +page_title: "genesyscloud_outbound_contact_list_template Resource - terraform-provider-genesyscloud" +subcategory: "" +description: |- + Genesys Cloud Outbound Contact List Template +--- +# genesyscloud_outbound_contact_list_template (Resource) + +Genesys Cloud Outbound Contact List Template + +## 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/outbound/contactlisttemplates](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-outbound-contactlisttemplates) +- [POST /api/v2/outbound/contactlisttemplates](https://developer.genesys.cloud/devapps/api-explorer#post-api-v2-outbound-contactlisttemplates) +- [GET /api/v2/outbound/contactlisttemplates/{contactListTemplateId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-outbound-contactlisttemplates--contactListTemplateId-) +- [PUT /api/v2/outbound/contactlisttemplates/{contactListTemplateId}](https://developer.genesys.cloud/devapps/api-explorer#put-api-v2-outbound-contactlisttemplates--contactListTemplateId-) +- [DELETE /api/v2/outbound/contactlisttemplates/{contactListTemplateId}](https://developer.genesys.cloud/devapps/api-explorer#delete-api-v2-outbound-contactlisttemplates--contactListTemplateId-) + + +## Example Usage + +```terraform +resource "genesyscloud_outbound_contact_list_template" "contact-list-template" { + name = "Example Contact List Template" + column_names = ["First Name", "Last Name", "Cell", "Home"] + attempt_limit_id = genesyscloud_outbound_attempt_limit.attempt-limit.id + phone_columns { + column_name = "Cell" + type = "cell" + } + phone_columns { + column_name = "Home" + type = "home" + } +} +``` + + +## Schema + +### Required + +- `column_names` (List of String) The names of the contact template data columns. Changing the column_names attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID +- `name` (String) The name for the contact list template. + +### Optional + +- `attempt_limit_id` (String) Attempt Limit for this Contact List Template. +- `automatic_time_zone_mapping` (Boolean) Indicates if automatic time zone mapping is to be used for this Contact List Template. Changing the automatic_time_zone_mappings attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID +- `column_data_type_specifications` (Block List) The settings of the columns selected for dynamic queueing. If updated, the contact list template is dropped and recreated with a new ID (see [below for nested schema](#nestedblock--column_data_type_specifications)) +- `email_columns` (Block Set) Indicates which columns are email addresses. Changing the email_columns attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID. Required if phone_columns is empty (see [below for nested schema](#nestedblock--email_columns)) +- `phone_columns` (Block Set) Indicates which columns are phone numbers. Changing the phone_columns attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID. Required if email_columns is empty (see [below for nested schema](#nestedblock--phone_columns)) +- `preview_mode_accepted_values` (List of String) The values in the preview_mode_column_name column that indicate a contact should always be dialed in preview mode. +- `preview_mode_column_name` (String) A column to check if a contact should always be dialed in preview mode. +- `zip_code_column_name` (String) The name of contact list column containing the zip code for use with automatic time zone mapping. Only allowed if 'automatic_time_zone_mapping' is set to true. Changing the zip_code_column_name attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `column_data_type_specifications` + +Required: + +- `column_name` (String) The column name of a column selected for dynamic queueing. +- `max_length` (Number) The maximum length of the text column selected for dynamic queueing. + +Optional: + +- `column_data_type` (String) The data type of the column selected for dynamic queueing (TEXT, NUMERIC or TIMESTAMP) +- `max` (Number) The maximum length of the numeric column selected for dynamic queueing. +- `min` (Number) The minimum length of the numeric column selected for dynamic queueing. + + + +### Nested Schema for `email_columns` + +Required: + +- `column_name` (String) The name of the email column. +- `type` (String) Indicates the type of the email column. For example, 'work' or 'personal'. + +Optional: + +- `contactable_time_column` (String) A column that indicates the timezone to use for a given contact when checking contactable times. + + + +### Nested Schema for `phone_columns` + +Required: + +- `column_name` (String) The name of the phone column. +- `type` (String) Indicates the type of the phone column. For example, 'cell' or 'home'. + +Optional: + +- `callable_time_column` (String) A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true. + diff --git a/docs/resources/outbound_contactlistfilter.md b/docs/resources/outbound_contactlistfilter.md index 1009a7ad9..9d5935ea1 100644 --- a/docs/resources/outbound_contactlistfilter.md +++ b/docs/resources/outbound_contactlistfilter.md @@ -42,12 +42,13 @@ resource "genesyscloud_outbound_contactlistfilter" "contact_list_filter" { ### Required -- `contact_list_id` (String) The contact list the filter is based on. - `name` (String) The name of the list. ### Optional - `clauses` (Block List) Groups of conditions to filter the contacts by. (see [below for nested schema](#nestedblock--clauses)) +- `contact_list_id` (String) The contact list the filter is based on. Mutually exclusive to 'contact_list_template_id', however, one of the two must be specified +- `contact_list_template_id` (String) The contact list template the filter is based on. Mutually exclusive to 'contact_list_id', however, one of the two must be specified. - `filter_type` (String) How to join clauses together. ### Read-Only diff --git a/docs/resources/routing_queue_conditional_group_routing.md b/docs/resources/routing_queue_conditional_group_routing.md index 2e563cf2a..29c2db333 100644 --- a/docs/resources/routing_queue_conditional_group_routing.md +++ b/docs/resources/routing_queue_conditional_group_routing.md @@ -28,8 +28,8 @@ resource "genesyscloud_routing_queue_conditional_group_routing" "example-name" { condition_value = 0 wait_seconds = 20 groups { - member_group_id = "" - member_group_type = "" + member_group_id = genesyscloud_group.example-group.id + member_group_type = "GROUP" } } rules { @@ -39,8 +39,8 @@ resource "genesyscloud_routing_queue_conditional_group_routing" "example-name" { condition_value = 5 wait_seconds = 15 groups { - member_group_id = "" - member_group_type = "" + member_group_id = genesyscloud_group.another-group.id + member_group_type = "GROUP" } } } diff --git a/docs/resources/user.md b/docs/resources/user.md index 85f45da51..287da0a78 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -11,18 +11,24 @@ Genesys Cloud User ## 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/users](https://developer.mypurecloud.com/api/rest/v2/users/#post-api-v2-users) -* [GET /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId-) -* [PATCH /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId-) -* [DELETE /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId-) -* [PUT /api/v2/users/{userId}/routingskills/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--routingskills-bulk) -* [DELETE /api/v2/users/{userId}/routinglanguages/{languageId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId--routinglanguages--languageId-) -* [PATCH /api/v2/users/{userId}/routinglanguages/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId--routinglanguages-bulk) -* [GET /api/v2/users/{userId}/routinglanguages](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId--routinglanguages) -* [PUT /api/v2/users/{userId}/profileskills](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--profileskills) -* [GET /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-routing-users--userId--utilization) -* [PUT /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-routing-users--userId--utilization) -* [DELETE /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-routing-users--userId--utilization) +- [POST /api/v2/users](https://developer.mypurecloud.com/api/rest/v2/users/#post-api-v2-users) +- [GET /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId-) +- [PATCH /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId-) +- [DELETE /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId-) +- [PUT /api/v2/users/{userId}/routingskills/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--routingskills-bulk) +- [DELETE /api/v2/users/{userId}/routinglanguages/{languageId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId--routinglanguages--languageId-) +- [PATCH /api/v2/users/{userId}/routinglanguages/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId--routinglanguages-bulk) +- [GET /api/v2/users/{userId}/routinglanguages](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId--routinglanguages) +- [PUT /api/v2/users/{userId}/profileskills](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--profileskills) +- [GET /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-routing-users--userId--utilization) +- [PUT /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-routing-users--userId--utilization) +- [DELETE /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-routing-users--userId--utilization) + +--- + +This resource has a feature toggle experimenting with new logic for returning phone addresses in the read action. +You can enable this by setting the `USE_NEW_USER_ADDRESS_LOGIC` environmental variable before running `terraform apply`. + ## Example Usage diff --git a/examples/data-sources/genesyscloud_conversations_messaging_settings/data-source.tf b/examples/data-sources/genesyscloud_conversations_messaging_settings/data-source.tf new file mode 100644 index 000000000..b1f4d48b1 --- /dev/null +++ b/examples/data-sources/genesyscloud_conversations_messaging_settings/data-source.tf @@ -0,0 +1,3 @@ +data "genesyscloud_conversations_messaging_settings" "example-messaging-settings" { + name = "settings name" +} \ No newline at end of file diff --git a/examples/data-sources/genesyscloud_outbound_contact_list_template/data-source.tf b/examples/data-sources/genesyscloud_outbound_contact_list_template/data-source.tf new file mode 100644 index 000000000..a2c72141a --- /dev/null +++ b/examples/data-sources/genesyscloud_outbound_contact_list_template/data-source.tf @@ -0,0 +1,3 @@ +data "genesyscloud_outbound_contact_list_template" "contact_list_template" { + name = "Example Contact List Template" +} diff --git a/examples/resources/genesyscloud_conversations_messaging_settings/apis.md b/examples/resources/genesyscloud_conversations_messaging_settings/apis.md new file mode 100644 index 000000000..9f6c3fe3e --- /dev/null +++ b/examples/resources/genesyscloud_conversations_messaging_settings/apis.md @@ -0,0 +1,5 @@ +* [GET /api/v2/conversations/messaging/settings](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-conversations-messaging-settings) +* [POST /api/v2/conversations/messaging/settings](https://developer.genesys.cloud/devapps/api-explorer#post-api-v2-conversations-messaging-settings) +* [GET /api/v2/conversations/messaging/settings/{messageSettingId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-conversations-messaging-settings--messageSettingId-) +* [PATCH /api/v2/conversations/messaging/settings/{messageSettingId}](https://developer.genesys.cloud/devapps/api-explorer#patch-api-v2-conversations-messaging-settings--messageSettingId-) +* [DELETE /api/v2/conversations/messaging/settings/{messageSettingId}](https://developer.genesys.cloud/devapps/api-explorer#delete-api-v2-conversations-messaging-settings--messageSettingId-) \ No newline at end of file diff --git a/examples/resources/genesyscloud_conversations_messaging_settings/resource.tf b/examples/resources/genesyscloud_conversations_messaging_settings/resource.tf new file mode 100644 index 000000000..d80fe743a --- /dev/null +++ b/examples/resources/genesyscloud_conversations_messaging_settings/resource.tf @@ -0,0 +1,21 @@ +resource "genesyscloud_conversations_messaging_settings" "example-messaging-settings" { + name = "Sample Messaging Settings" + content { + story { + mention { + inbound = "Enabled" + } + reply { + inbound = "Enabled" + } + } + } + event { + typing { + on { + inbound = "Enabled" + outbound = "Enabled" + } + } + } +} \ No newline at end of file diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml index 47bbbf4ab..e1f261bad 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml @@ -1,5 +1,5 @@ inboundCall: - name: Terraform Test Flow log level f79fc952-6a58-4d00-996e-2c3873e1871a + name: Terraform Emergency Test Flow 7fa2e8a9-b1d9-4d78-93b8-05b4465314a7 defaultLanguage: en-us startUpRef: ./menus/menu[mainMenu] initialGreeting: diff --git a/examples/resources/genesyscloud_outbound_contact_list_template/apis.md b/examples/resources/genesyscloud_outbound_contact_list_template/apis.md new file mode 100644 index 000000000..edded9d77 --- /dev/null +++ b/examples/resources/genesyscloud_outbound_contact_list_template/apis.md @@ -0,0 +1,5 @@ +- [GET /api/v2/outbound/contactlisttemplates](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-outbound-contactlisttemplates) +- [POST /api/v2/outbound/contactlisttemplates](https://developer.genesys.cloud/devapps/api-explorer#post-api-v2-outbound-contactlisttemplates) +- [GET /api/v2/outbound/contactlisttemplates/{contactListTemplateId}](https://developer.genesys.cloud/devapps/api-explorer#get-api-v2-outbound-contactlisttemplates--contactListTemplateId-) +- [PUT /api/v2/outbound/contactlisttemplates/{contactListTemplateId}](https://developer.genesys.cloud/devapps/api-explorer#put-api-v2-outbound-contactlisttemplates--contactListTemplateId-) +- [DELETE /api/v2/outbound/contactlisttemplates/{contactListTemplateId}](https://developer.genesys.cloud/devapps/api-explorer#delete-api-v2-outbound-contactlisttemplates--contactListTemplateId-) diff --git a/examples/resources/genesyscloud_outbound_contact_list_template/resource.tf b/examples/resources/genesyscloud_outbound_contact_list_template/resource.tf new file mode 100644 index 000000000..b582982b0 --- /dev/null +++ b/examples/resources/genesyscloud_outbound_contact_list_template/resource.tf @@ -0,0 +1,13 @@ +resource "genesyscloud_outbound_contact_list_template" "contact-list-template" { + name = "Example Contact List Template" + column_names = ["First Name", "Last Name", "Cell", "Home"] + attempt_limit_id = genesyscloud_outbound_attempt_limit.attempt-limit.id + phone_columns { + column_name = "Cell" + type = "cell" + } + phone_columns { + column_name = "Home" + type = "home" + } +} diff --git a/examples/resources/genesyscloud_routing_queue_conditional_group_routing/resource.tf b/examples/resources/genesyscloud_routing_queue_conditional_group_routing/resource.tf index 7518db9d3..41e77cb73 100644 --- a/examples/resources/genesyscloud_routing_queue_conditional_group_routing/resource.tf +++ b/examples/resources/genesyscloud_routing_queue_conditional_group_routing/resource.tf @@ -9,8 +9,8 @@ resource "genesyscloud_routing_queue_conditional_group_routing" "example-name" { condition_value = 0 wait_seconds = 20 groups { - member_group_id = "" - member_group_type = "" + member_group_id = genesyscloud_group.example-group.id + member_group_type = "GROUP" } } rules { @@ -20,8 +20,8 @@ resource "genesyscloud_routing_queue_conditional_group_routing" "example-name" { condition_value = 5 wait_seconds = 15 groups { - member_group_id = "" - member_group_type = "" + member_group_id = genesyscloud_group.another-group.id + member_group_type = "GROUP" } } } \ No newline at end of file diff --git a/examples/resources/genesyscloud_user/apis.md b/examples/resources/genesyscloud_user/apis.md index b4d71606a..897956a1c 100644 --- a/examples/resources/genesyscloud_user/apis.md +++ b/examples/resources/genesyscloud_user/apis.md @@ -1,12 +1,17 @@ -* [POST /api/v2/users](https://developer.mypurecloud.com/api/rest/v2/users/#post-api-v2-users) -* [GET /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId-) -* [PATCH /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId-) -* [DELETE /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId-) -* [PUT /api/v2/users/{userId}/routingskills/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--routingskills-bulk) -* [DELETE /api/v2/users/{userId}/routinglanguages/{languageId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId--routinglanguages--languageId-) -* [PATCH /api/v2/users/{userId}/routinglanguages/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId--routinglanguages-bulk) -* [GET /api/v2/users/{userId}/routinglanguages](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId--routinglanguages) -* [PUT /api/v2/users/{userId}/profileskills](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--profileskills) -* [GET /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-routing-users--userId--utilization) -* [PUT /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-routing-users--userId--utilization) -* [DELETE /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-routing-users--userId--utilization) \ No newline at end of file +- [POST /api/v2/users](https://developer.mypurecloud.com/api/rest/v2/users/#post-api-v2-users) +- [GET /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId-) +- [PATCH /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId-) +- [DELETE /api/v2/users/{userId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId-) +- [PUT /api/v2/users/{userId}/routingskills/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--routingskills-bulk) +- [DELETE /api/v2/users/{userId}/routinglanguages/{languageId}](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-users--userId--routinglanguages--languageId-) +- [PATCH /api/v2/users/{userId}/routinglanguages/bulk](https://developer.mypurecloud.com/api/rest/v2/users/#patch-api-v2-users--userId--routinglanguages-bulk) +- [GET /api/v2/users/{userId}/routinglanguages](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-users--userId--routinglanguages) +- [PUT /api/v2/users/{userId}/profileskills](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-users--userId--profileskills) +- [GET /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#get-api-v2-routing-users--userId--utilization) +- [PUT /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#put-api-v2-routing-users--userId--utilization) +- [DELETE /api/v2/routing/users/{userId}/utilization](https://developer.mypurecloud.com/api/rest/v2/users/#delete-api-v2-routing-users--userId--utilization) + +--- + +This resource has a feature toggle experimenting with new logic for returning phone addresses in the read action. +You can enable this by setting the `USE_NEW_USER_ADDRESS_LOGIC` environmental variable before running `terraform apply`. diff --git a/genesyscloud/architect_emergencygroup/resource_genesyscloud_architect_emergencygroup_test.go b/genesyscloud/architect_emergencygroup/resource_genesyscloud_architect_emergencygroup_test.go index 7be483c9f..7c2b3ecb7 100644 --- a/genesyscloud/architect_emergencygroup/resource_genesyscloud_architect_emergencygroup_test.go +++ b/genesyscloud/architect_emergencygroup/resource_genesyscloud_architect_emergencygroup_test.go @@ -2,6 +2,7 @@ package architect_emergencygroup import ( "fmt" + "os" "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud/architect_flow" @@ -39,6 +40,10 @@ func TestAccResourceArchitectEmergencyGroups(t *testing.T) { // TODO: Create the IVR inside the test config once emergency group has been moved to its own package. // Currently, the ivr resource cannot be registered for these tests because of a cyclic dependency issue. ivrId := "f94e084e-40eb-470b-80d6-0f99cf22d102" + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" { + ivrId = "d37d14fe-1e6c-4ed6-a9bb-b6ef0dd8e9cd" + } + if !ivrExists(config, ivrId) { t.Skip("Skipping because IVR does not exists in the target org.") } diff --git a/genesyscloud/architect_ivr/data_source_genesyscloud_architect_ivr.go b/genesyscloud/architect_ivr/data_source_genesyscloud_architect_ivr.go index 01e69c1ef..f4371b944 100644 --- a/genesyscloud/architect_ivr/data_source_genesyscloud_architect_ivr.go +++ b/genesyscloud/architect_ivr/data_source_genesyscloud_architect_ivr.go @@ -18,6 +18,7 @@ func dataSourceIvrRead(ctx context.Context, d *schema.ResourceData, m interface{ sdkConfig := m.(*provider.ProviderMeta).ClientConfig ap := getArchitectIvrProxy(sdkConfig) name := d.Get("name").(string) + // Query ivr by name. Retry in case search has not yet indexed the ivr. return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { id, retryable, resp, err := ap.getArchitectIvrIdByName(ctx, name) diff --git a/genesyscloud/conversations_messaging_settings/data_source_conversations_messaging_settings.go b/genesyscloud/conversations_messaging_settings/data_source_conversations_messaging_settings.go new file mode 100644 index 000000000..853421688 --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/data_source_conversations_messaging_settings.go @@ -0,0 +1,32 @@ +package conversations_messaging_settings + +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" +) + +func dataSourceConversationsMessagingSettingsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sdkConfig := m.(*provider.ProviderMeta).ClientConfig + proxy := getConversationsMessagingSettingsProxy(sdkConfig) + name := d.Get("name").(string) + + return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + messageSettingsId, resp, retryable, err := proxy.getConversationsMessagingSettingsIdByName(ctx, name) + if err != nil && !retryable { + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting conversations messaging setting by name %s | error: %s", name, err), resp)) + } + + if retryable { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no conversations messaging setting found with name %s", name), resp)) + } + d.SetId(messageSettingsId) + return nil + }) +} diff --git a/genesyscloud/conversations_messaging_settings/data_source_conversations_messaging_settings_test.go b/genesyscloud/conversations_messaging_settings/data_source_conversations_messaging_settings_test.go new file mode 100644 index 000000000..19f88cd25 --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/data_source_conversations_messaging_settings_test.go @@ -0,0 +1,55 @@ +package conversations_messaging_settings + +import ( + "fmt" + "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 TestAccDataSourceConversationsMessagingSettings(t *testing.T) { + var ( + id = "conversations_messaging_settings" + dataId = "conversations_messaging_settings_data" + name = "Messaging Settings " + uuid.NewString() + ) + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + Config: generateConversationsMessagingSettingsResource( + id, + name, + generateContentStoryBlock( + generateMentionInboundOnlySetting("Enabled"), + generateReplyInboundOnlySetting("Enabled"), + ), + generateTypingOnSetting( + "Enabled", + "Disabled", + ), + ) + generateConversationsMessagingSettingsDataSource( + dataId, + name, + "genesyscloud_conversations_messaging_settings."+id, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.genesyscloud_conversations_messaging_settings."+dataId, "id", + "genesyscloud_conversations_messaging_settings."+id, "id"), + ), + }, + }, + }) +} + +func generateConversationsMessagingSettingsDataSource(id, name, dependsOn string) string { + return fmt.Sprintf(`data "genesyscloud_conversations_messaging_settings" "%s" { + name = "%s" + depends_on = [%s] + } +`, id, name, dependsOn) +} diff --git a/genesyscloud/conversations_messaging_settings/genesyscloud_conversations_messaging_settings_init_test.go b/genesyscloud/conversations_messaging_settings/genesyscloud_conversations_messaging_settings_init_test.go new file mode 100644 index 000000000..7d372594f --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/genesyscloud_conversations_messaging_settings_init_test.go @@ -0,0 +1,54 @@ +package conversations_messaging_settings + +import ( + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var providerDataSources map[string]*schema.Resource + +// providerResources holds a map of all registered resources +var providerResources map[string]*schema.Resource + +type registerTestInstance struct { + resourceMapMutex sync.RWMutex + datasourceMapMutex sync.RWMutex +} + +// registerTestResources registers all resources used in the tests +func (r *registerTestInstance) registerTestResources() { + r.resourceMapMutex.Lock() + defer r.resourceMapMutex.Unlock() + + providerResources[resourceName] = ResourceConversationsMessagingSettings() +} + +// registerTestDataSources registers all data sources used in the tests. +func (r *registerTestInstance) registerTestDataSources() { + r.datasourceMapMutex.Lock() + defer r.datasourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceConversationsMessagingSettings() +} + +// initTestResources initializes all test resources and data sources. +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() + regInstance.registerTestDataSources() +} + +// TestMain is a "setup" function called by the testing framework when run the test +func TestMain(m *testing.M) { + // Run setup function before starting the test suite for the conversations_messaging_settings package + initTestResources() + + // Run the test suite for the conversations_messaging_settings package + m.Run() +} diff --git a/genesyscloud/conversations_messaging_settings/genesyscloud_conversations_messaging_settings_proxy.go b/genesyscloud/conversations_messaging_settings/genesyscloud_conversations_messaging_settings_proxy.go new file mode 100644 index 000000000..0a277e2bc --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/genesyscloud_conversations_messaging_settings_proxy.go @@ -0,0 +1,170 @@ +package conversations_messaging_settings + +import ( + "context" + "fmt" + "log" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +var internalProxy *conversationsMessagingSettingsProxy + +type getAllConversationsMessagingSettingsFunc func(ctx context.Context, p *conversationsMessagingSettingsProxy) (*[]platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) +type createConversationsMessagingSettingsFunc func(ctx context.Context, p *conversationsMessagingSettingsProxy, messagingSettingRequest *platformclientv2.Messagingsettingrequest) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) +type getConversationsMessagingSettingsByIdFunc func(ctx context.Context, p *conversationsMessagingSettingsProxy, id string) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) +type getConversationsMessagingSettingsIdByNameFunc func(ctx context.Context, p *conversationsMessagingSettingsProxy, name string) (string, *platformclientv2.APIResponse, bool, error) +type updateConversationsMessagingSettingsFunc func(ctx context.Context, p *conversationsMessagingSettingsProxy, id string, messagingSettingRequest *platformclientv2.Messagingsettingpatchrequest) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) +type deleteConversationsMessagingSettingsFunc func(ctx context.Context, p *conversationsMessagingSettingsProxy, id string) (*platformclientv2.APIResponse, error) + +// conversationsMessagingSettingsProxy contains all of the methods that call genesys cloud APIs. +type conversationsMessagingSettingsProxy struct { + clientConfig *platformclientv2.Configuration + conversationsApi *platformclientv2.ConversationsApi + createConversationsMessagingSettingsAttr createConversationsMessagingSettingsFunc + getAllConversationsMessagingSettingsAttr getAllConversationsMessagingSettingsFunc + getConversationsMessagingSettingsIdByNameAttr getConversationsMessagingSettingsIdByNameFunc + getConversationsMessagingSettingsByIdAttr getConversationsMessagingSettingsByIdFunc + updateConversationsMessagingSettingsAttr updateConversationsMessagingSettingsFunc + deleteConversationsMessagingSettingsAttr deleteConversationsMessagingSettingsFunc + messagingSettingsCache rc.CacheInterface[platformclientv2.Messagingsetting] +} + +// newConversationsMessagingSettingsProxy initializes the conversations messaging settings proxy with all of the data needed to communicate with Genesys Cloud +func newConversationsMessagingSettingsProxy(clientConfig *platformclientv2.Configuration) *conversationsMessagingSettingsProxy { + api := platformclientv2.NewConversationsApiWithConfig(clientConfig) + messagingSettingsCache := rc.NewResourceCache[platformclientv2.Messagingsetting]() + return &conversationsMessagingSettingsProxy{ + clientConfig: clientConfig, + conversationsApi: api, + createConversationsMessagingSettingsAttr: createConversationsMessagingSettingsFn, + getAllConversationsMessagingSettingsAttr: getAllConversationsMessagingSettingsFn, + getConversationsMessagingSettingsIdByNameAttr: getConversationsMessagingSettingsIdByNameFn, + getConversationsMessagingSettingsByIdAttr: getConversationsMessagingSettingsByIdFn, + updateConversationsMessagingSettingsAttr: updateConversationsMessagingSettingsFn, + deleteConversationsMessagingSettingsAttr: deleteConversationsMessagingSettingsFn, + messagingSettingsCache: messagingSettingsCache, + } +} + +// getConversationsMessagingSettingsProxy 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 getConversationsMessagingSettingsProxy(clientConfig *platformclientv2.Configuration) *conversationsMessagingSettingsProxy { + if internalProxy == nil { + internalProxy = newConversationsMessagingSettingsProxy(clientConfig) + } + return internalProxy +} + +// getConversationsMessagingSettings retrieves all Genesys Cloud conversations messaging settings +func (p *conversationsMessagingSettingsProxy) getAllConversationsMessagingSettings(ctx context.Context) (*[]platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + return p.getAllConversationsMessagingSettingsAttr(ctx, p) +} + +// createConversationsMessagingSettings creates a Genesys Cloud conversations messaging settings +func (p *conversationsMessagingSettingsProxy) createConversationsMessagingSettings(ctx context.Context, conversationsMessagingSettings *platformclientv2.Messagingsettingrequest) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + return p.createConversationsMessagingSettingsAttr(ctx, p, conversationsMessagingSettings) +} + +// getConversationsMessagingSettingsById returns a single Genesys Cloud conversations messaging settings by Id +func (p *conversationsMessagingSettingsProxy) getConversationsMessagingSettingsById(ctx context.Context, id string) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + return p.getConversationsMessagingSettingsByIdAttr(ctx, p, id) +} + +// getConversationsMessagingSettingsIdByName returns a single Genesys Cloud conversations messaging settings by a name +func (p *conversationsMessagingSettingsProxy) getConversationsMessagingSettingsIdByName(ctx context.Context, name string) (string, *platformclientv2.APIResponse, bool, error) { + return p.getConversationsMessagingSettingsIdByNameAttr(ctx, p, name) +} + +// updateConversationsMessagingSettings updates a Genesys Cloud conversations messaging settings +func (p *conversationsMessagingSettingsProxy) updateConversationsMessagingSettings(ctx context.Context, id string, conversationsMessagingSettings *platformclientv2.Messagingsettingpatchrequest) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + return p.updateConversationsMessagingSettingsAttr(ctx, p, id, conversationsMessagingSettings) +} + +// deleteConversationsMessagingSettings deletes a Genesys Cloud conversations messaging settings by Id +func (p *conversationsMessagingSettingsProxy) deleteConversationsMessagingSettings(ctx context.Context, id string) (*platformclientv2.APIResponse, error) { + return p.deleteConversationsMessagingSettingsAttr(ctx, p, id) +} + +// getAllConversationsMessagingSettingsFn is the implementation for retrieving all conversations messaging settings in Genesys Cloud +func getAllConversationsMessagingSettingsFn(ctx context.Context, p *conversationsMessagingSettingsProxy) (*[]platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + var ( + allMessagingSettings []platformclientv2.Messagingsetting + pageSize = 100 + response *platformclientv2.APIResponse + ) + + messagingSettings, resp, err := p.conversationsApi.GetConversationsMessagingSettings(pageSize, 1) + if err != nil { + return nil, resp, fmt.Errorf("failed to get messaging setting request: %v", err) + } + + if messagingSettings.Entities == nil || len(*messagingSettings.Entities) == 0 { + return &allMessagingSettings, resp, nil + } + allMessagingSettings = append(allMessagingSettings, *messagingSettings.Entities...) + + for pageNum := 2; pageNum <= *messagingSettings.PageCount; pageNum++ { + messagingSettings, resp, err := p.conversationsApi.GetConversationsMessagingSettings(pageSize, pageNum) + if err != nil { + return nil, resp, fmt.Errorf("failed to get messaging setting request: %v", err) + } + response = resp + + if messagingSettings.Entities == nil || len(*messagingSettings.Entities) == 0 { + break + } + allMessagingSettings = append(allMessagingSettings, *messagingSettings.Entities...) + } + + for _, setting := range allMessagingSettings { + rc.SetCache(p.messagingSettingsCache, *setting.Id, setting) + } + + return &allMessagingSettings, response, nil +} + +// createConversationsMessagingSettingsFn is an implementation function for creating a Genesys Cloud conversations messaging settings +func createConversationsMessagingSettingsFn(ctx context.Context, p *conversationsMessagingSettingsProxy, conversationsMessagingSettings *platformclientv2.Messagingsettingrequest) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + return p.conversationsApi.PostConversationsMessagingSettings(*conversationsMessagingSettings) +} + +// getConversationsMessagingSettingsByIdFn is an implementation of the function to get a Genesys Cloud conversations messaging settings by Id +func getConversationsMessagingSettingsByIdFn(ctx context.Context, p *conversationsMessagingSettingsProxy, id string) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + if setting := rc.GetCacheItem(p.messagingSettingsCache, id); setting != nil { + return setting, nil, nil + } + return p.conversationsApi.GetConversationsMessagingSetting(id) +} + +// getConversationsMessagingSettingsIdByNameFn is an implementation of the function to get a Genesys Cloud conversations messaging settings by name +func getConversationsMessagingSettingsIdByNameFn(ctx context.Context, p *conversationsMessagingSettingsProxy, name string) (string, *platformclientv2.APIResponse, bool, error) { + messagingSettings, resp, err := getAllConversationsMessagingSettingsFn(ctx, p) + if err != nil { + return "", resp, false, err + } + + if messagingSettings == nil || len(*messagingSettings) == 0 { + return "", resp, true, fmt.Errorf("no conversations messaging settings found with name %s", name) + } + + for _, messagingSetting := range *messagingSettings { + if *messagingSetting.Name == name { + log.Printf("Retrieved the conversations messaging settings id %s by name %s", *messagingSetting.Id, name) + return *messagingSetting.Id, resp, false, nil + } + } + + return "", resp, true, fmt.Errorf("unable to find conversations messaging settings with name %s", name) +} + +// updateConversationsMessagingSettingsFn is an implementation of the function to update a Genesys Cloud conversations messaging settings +func updateConversationsMessagingSettingsFn(ctx context.Context, p *conversationsMessagingSettingsProxy, id string, messagingSettingRequest *platformclientv2.Messagingsettingpatchrequest) (*platformclientv2.Messagingsetting, *platformclientv2.APIResponse, error) { + return p.conversationsApi.PatchConversationsMessagingSetting(id, *messagingSettingRequest) +} + +// deleteConversationsMessagingSettingsFn is an implementation function for deleting a Genesys Cloud conversations messaging settings +func deleteConversationsMessagingSettingsFn(ctx context.Context, p *conversationsMessagingSettingsProxy, id string) (*platformclientv2.APIResponse, error) { + return p.conversationsApi.DeleteConversationsMessagingSetting(id) +} diff --git a/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings.go b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings.go new file mode 100644 index 000000000..f1304dc77 --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings.go @@ -0,0 +1,128 @@ +package conversations_messaging_settings + +import ( + "context" + "fmt" + "log" + "terraform-provider-genesyscloud/genesyscloud/consistency_checker" + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + "terraform-provider-genesyscloud/genesyscloud/util" + "terraform-provider-genesyscloud/genesyscloud/util/constants" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + "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" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +func getAllAuthConversationsMessagingSettings(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + proxy := getConversationsMessagingSettingsProxy(clientConfig) + resources := make(resourceExporter.ResourceIDMetaMap) + + messagingSettings, resp, err := proxy.getAllConversationsMessagingSettings(ctx) + if err != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get Conversations messaging Settings: %s", err), resp) + } + + for _, messagingSetting := range *messagingSettings { + resources[*messagingSetting.Id] = &resourceExporter.ResourceMeta{Name: *messagingSetting.Name} + } + + return resources, nil +} + +func createConversationsMessagingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getConversationsMessagingSettingsProxy(sdkConfig) + + conversationsMessagingSettingsReq := getConversationsMessagingSettingsFromResourceData(d) + + log.Printf("Creating conversations messaging settings %s", *conversationsMessagingSettingsReq.Name) + messagingSetting, resp, err := proxy.createConversationsMessagingSettings(ctx, &conversationsMessagingSettingsReq) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create conversations messaging setting %s error: %s", *conversationsMessagingSettingsReq.Name, err), resp) + } + + d.SetId(*messagingSetting.Id) + log.Printf("Created conversations messaging settings %s", *messagingSetting.Id) + return readConversationsMessagingSettings(ctx, d, meta) +} + +func readConversationsMessagingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getConversationsMessagingSettingsProxy(sdkConfig) + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceConversationsMessagingSettings(), constants.DefaultConsistencyChecks, resourceName) + + log.Printf("Reading conversations messaging settings %s", d.Id()) + + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + messagingSetting, resp, err := proxy.getConversationsMessagingSettingsById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read conversations messaging settings %s | error: %s", d.Id(), err), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read conversations messaging settings %s | error: %s", d.Id(), err), resp)) + } + + resourcedata.SetNillableValue(d, "name", messagingSetting.Name) + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "content", messagingSetting.Content, flattenContentSettings) + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "event", messagingSetting.Event, flattenEventSettings) + + log.Printf("Read conversations messaging settings %s", d.Id()) + return cc.CheckState(d) + }) +} + +func updateConversationsMessagingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getConversationsMessagingSettingsProxy(sdkConfig) + + name := d.Get("name").(string) + content := d.Get("content").([]interface{}) + event := d.Get("event").([]interface{}) + + var conversationsMessagingSettings platformclientv2.Messagingsettingpatchrequest + + if name != "" { + conversationsMessagingSettings.Name = &name + } + if content != nil { + conversationsMessagingSettings.Content = buildContentSettings(content) + } + if event != nil { + conversationsMessagingSettings.Event = buildEventSetting(event) + } + + _, resp, err := proxy.updateConversationsMessagingSettings(ctx, d.Id(), &conversationsMessagingSettings) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update conversations messaging settings %s error: %s", d.Id(), err), resp) + } + + log.Printf("Updated conversations messaging settings %s", d.Id()) + return readConversationsMessagingSettings(ctx, d, meta) +} + +func deleteConversationsMessagingSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getConversationsMessagingSettingsProxy(sdkConfig) + + resp, err := proxy.deleteConversationsMessagingSettings(ctx, d.Id()) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete conversations messaging setting %s error: %s", d.Id(), err), resp) + } + + return util.WithRetries(ctx, 180*time.Second, func() *retry.RetryError { + _, resp, err := proxy.getConversationsMessagingSettingsById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + log.Printf("Deleted Conversations messaging Setting") + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Conversations messaging Setting: %s | error: %s", d.Id(), err), resp)) + } + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Conversations messaging Setting %s still exists", d.Id()), resp)) + }) +} diff --git a/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_schema.go b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_schema.go new file mode 100644 index 000000000..9564bbd70 --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_schema.go @@ -0,0 +1,163 @@ +package conversations_messaging_settings + +import ( + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +const resourceName = "genesyscloud_conversations_messaging_settings" + +var ( + eventSettingResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "typing": { + Description: "Settings regarding typing events", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: typingSettingResource, + }, + }, + } + typingSettingResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "on": { + Description: "Should typing indication Events be sent", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: settingDirectionResource, + }, + }, + } + settingDirectionResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "inbound": { + Description: "Status for the Inbound Direction. Valid values: Enabled, Disabled.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"Enabled", "Disabled"}, false), + }, + "outbound": { + Description: "Status for the outbound Direction. Valid values: Enabled, Disabled.", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"Enabled", "Disabled"}, false), + }, + }, + } + + contentSettingResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "story": { + Description: "Settings relating to facebook and instagram stories feature", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: storySettingResource, + }, + }, + } + storySettingResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mention": { + Description: "Setting relating to Story Mentions", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: inboundOnlySettingResource, + }, + "reply": { + Description: "Setting relating to Story Replies", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: inboundOnlySettingResource, + }, + }, + } + inboundOnlySettingResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "inbound": { + Description: "Valid values: Enabled, Disabled.", + Optional: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"Enabled", "Disabled"}, false), + }, + }, + } +) + +// SetRegistrar registers all of the resources, datasources and exporters in the package +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceConversationsMessagingSettings()) + regInstance.RegisterDataSource(resourceName, DataSourceConversationsMessagingSettings()) + regInstance.RegisterExporter(resourceName, ConversationsMessagingSettingsExporter()) +} + +func ResourceConversationsMessagingSettings() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud conversations messaging settings", + + CreateContext: provider.CreateWithPooledClient(createConversationsMessagingSettings), + ReadContext: provider.ReadWithPooledClient(readConversationsMessagingSettings), + UpdateContext: provider.UpdateWithPooledClient(updateConversationsMessagingSettings), + DeleteContext: provider.DeleteWithPooledClient(deleteConversationsMessagingSettings), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "name": { + Description: "The messaging Setting profile name", + Required: true, + Type: schema.TypeString, + }, + "content": { + Description: "Settings relating to message contents", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: contentSettingResource, + AtLeastOneOf: []string{"content", "event"}, + }, + "event": { + Description: "Settings relating to events which may occur", + Optional: true, + Type: schema.TypeList, + MaxItems: 1, + Elem: eventSettingResource, + AtLeastOneOf: []string{"content", "event"}, + }, + }, + } +} + +// DataSourceConversationsMessagingSettings registers the genesyscloud_conversations_messaging_settings data source +func DataSourceConversationsMessagingSettings() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud conversations messaging settings data source. Select an conversations messaging settings by name", + ReadContext: provider.ReadWithPooledClient(dataSourceConversationsMessagingSettingsRead), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "name": { + Description: "conversations messaging settings name", + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +// ConversationsMessagingSettingsExporter returns the resourceExporter object used to hold the genesyscloud_conversations_messaging_settings exporter's config +func ConversationsMessagingSettingsExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthConversationsMessagingSettings), + } +} diff --git a/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_test.go b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_test.go new file mode 100644 index 000000000..9f569e324 --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_test.go @@ -0,0 +1,144 @@ +package conversations_messaging_settings + +import ( + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "testing" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccResourceConversationsMessagingSettings(t *testing.T) { + var ( + resource1 = "testConversationsMessagingSettings" + name1 = "testSettings" + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + // Create with Content Block + Config: generateConversationsMessagingSettingsResource( + resource1, + name1, + generateContentStoryBlock( + generateMentionInboundOnlySetting("Disabled"), + generateReplyInboundOnlySetting("Enabled"), + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "name", name1), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "content.0.story.0.mention.0.inbound", "Disabled"), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "content.0.story.0.reply.0.inbound", "Enabled"), + ), + }, + { + // Update and Add Event Block + Config: generateConversationsMessagingSettingsResource( + resource1, + name1, + generateContentStoryBlock( + generateMentionInboundOnlySetting("Enabled"), + generateReplyInboundOnlySetting("Enabled"), + ), + generateTypingOnSetting( + "Enabled", + "Disabled", + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "name", name1), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "content.0.story.0.mention.0.inbound", "Enabled"), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "content.0.story.0.reply.0.inbound", "Enabled"), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "event.0.typing.0.on.0.inbound", "Enabled"), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource1, "event.0.typing.0.on.0.outbound", "Disabled"), + ), + }, + { + // Import/Read + ResourceName: "genesyscloud_conversations_messaging_settings." + resource1, + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testVerifySettingDestroyed, + }) +} + +func TestAccResourceConversationsMessagingSettingsContentOnly(t *testing.T) { + var ( + resource2 = "testConversationsMessagingSettingsContentOnly" + name2 = "testSettingsContentOnly" + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + // Create + Config: generateConversationsMessagingSettingsResource( + resource2, + name2, + generateContentStoryBlock( + generateMentionInboundOnlySetting("Disabled"), + generateReplyInboundOnlySetting("Disabled"), + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource2, "name", name2), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource2, "content.0.story.0.mention.0.inbound", "Disabled"), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource2, "content.0.story.0.reply.0.inbound", "Disabled"), + ), + }, + { + // Update + Config: generateConversationsMessagingSettingsResource( + resource2, + name2, + generateContentStoryBlock( + generateMentionInboundOnlySetting("Enabled"), + generateReplyInboundOnlySetting("Enabled"), + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource2, "name", name2), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource2, "content.0.story.0.mention.0.inbound", "Enabled"), + resource.TestCheckResourceAttr("genesyscloud_conversations_messaging_settings."+resource2, "content.0.story.0.reply.0.inbound", "Enabled"), + ), + }, + { + // Import/Read + ResourceName: "genesyscloud_conversations_messaging_settings." + resource2, + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testVerifySettingDestroyed, + }) +} + +func testVerifySettingDestroyed(state *terraform.State) error { + messagingAPI := platformclientv2.NewConversationsApi() + for _, rs := range state.RootModule().Resources { + if rs.Type != "genesyscloud_conversations_messaging_settings" { + continue + } + + setting, resp, err := messagingAPI.GetConversationsMessagingSetting(rs.Primary.ID) + if setting != nil { + return fmt.Errorf("Messaging setting (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + continue + } else { + return fmt.Errorf("Unexpected error: %s", err) + } + } + return nil +} diff --git a/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_utils.go b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_utils.go new file mode 100644 index 000000000..582676fb3 --- /dev/null +++ b/genesyscloud/conversations_messaging_settings/resource_conversations_messaging_settings_utils.go @@ -0,0 +1,225 @@ +package conversations_messaging_settings + +import ( + "fmt" + "strings" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +func getConversationsMessagingSettingsFromResourceData(d *schema.ResourceData) platformclientv2.Messagingsettingrequest { + return platformclientv2.Messagingsettingrequest{ + Name: platformclientv2.String(d.Get("name").(string)), + Content: buildContentSettings(d.Get("content").([]interface{})), + Event: buildEventSetting(d.Get("event").([]interface{})), + } +} + +func buildContentSettings(contentSettings []interface{}) *platformclientv2.Contentsetting { + var sdkContentSetting platformclientv2.Contentsetting + + for _, contentSetting := range contentSettings { + contentSettingsMap, ok := contentSetting.(map[string]interface{}) + if !ok { + continue + } + + resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkContentSetting.Story, contentSettingsMap, "story", buildStorySettings) + } + return &sdkContentSetting +} + +func buildStorySettings(storySettings []interface{}) *platformclientv2.Storysetting { + var sdkStorySetting platformclientv2.Storysetting + + for _, storySetting := range storySettings { + storySettingsMap, ok := storySetting.(map[string]interface{}) + if !ok { + continue + } + + resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkStorySetting.Mention, storySettingsMap, "mention", buildInboundOnlySettings) + resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkStorySetting.Reply, storySettingsMap, "reply", buildInboundOnlySettings) + } + + return &sdkStorySetting +} + +func buildInboundOnlySettings(inboundOnlySettings []interface{}) *platformclientv2.Inboundonlysetting { + var sdkInboundOnlySetting platformclientv2.Inboundonlysetting + + for _, inboundOnlySetting := range inboundOnlySettings { + inboundOnlySettingsMap, ok := inboundOnlySetting.(map[string]interface{}) + if !ok { + continue + } + resourcedata.BuildSDKStringValueIfNotNil(&sdkInboundOnlySetting.Inbound, inboundOnlySettingsMap, "inbound") + } + + return &sdkInboundOnlySetting +} + +func buildEventSetting(eventSettings []interface{}) *platformclientv2.Eventsetting { + var sdkEventSetting platformclientv2.Eventsetting + + for _, eventSetting := range eventSettings { + eventSettingsMap, ok := eventSetting.(map[string]interface{}) + if !ok { + continue + } + resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkEventSetting.Typing, eventSettingsMap, "typing", buildTypingSettings) + } + + return &sdkEventSetting +} + +func buildTypingSettings(typingSettings []interface{}) *platformclientv2.Typingsetting { + var sdkTypingSetting platformclientv2.Typingsetting + + for _, typingSetting := range typingSettings { + typingSettingsMap, ok := typingSetting.(map[string]interface{}) + if !ok { + continue + } + + resourcedata.BuildSDKInterfaceArrayValueIfNotNil(&sdkTypingSetting.On, typingSettingsMap, "on", buildSettingDirections) + } + + return &sdkTypingSetting +} + +func buildSettingDirections(settingDirections []interface{}) *platformclientv2.Settingdirection { + var sdkSettingDirection platformclientv2.Settingdirection + + for _, settingDirection := range settingDirections { + settingDirectionsMap, ok := settingDirection.(map[string]interface{}) + if !ok { + continue + } + + resourcedata.BuildSDKStringValueIfNotNil(&sdkSettingDirection.Inbound, settingDirectionsMap, "inbound") + resourcedata.BuildSDKStringValueIfNotNil(&sdkSettingDirection.Outbound, settingDirectionsMap, "outbound") + } + + return &sdkSettingDirection +} + +// flattenInboundOnlySettings maps a Genesys Cloud *[]platformclientv2.Inboundonlysetting into a []interface{} +func flattenInboundOnlySettings(inboundOnlySettings *platformclientv2.Inboundonlysetting) []interface{} { + var inboundOnlySettingList []interface{} + inboundOnlySettingMap := make(map[string]interface{}) + + resourcedata.SetMapValueIfNotNil(inboundOnlySettingMap, "inbound", inboundOnlySettings.Inbound) + + inboundOnlySettingList = append(inboundOnlySettingList, inboundOnlySettingMap) + + return inboundOnlySettingList +} + +// flattenStorySettings maps a Genesys Cloud *[]platformclientv2.Storysetting into a []interface{} +func flattenStorySettings(storySettings *platformclientv2.Storysetting) []interface{} { + var storySettingList []interface{} + storySettingMap := make(map[string]interface{}) + + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(storySettingMap, "mention", storySettings.Mention, flattenInboundOnlySettings) + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(storySettingMap, "reply", storySettings.Reply, flattenInboundOnlySettings) + + storySettingList = append(storySettingList, storySettingMap) + + return storySettingList +} + +// flattenContentSettings maps a Genesys Cloud *[]platformclientv2.Contentsetting into a []interface{} +func flattenContentSettings(contentSettings *platformclientv2.Contentsetting) []interface{} { + var contentSettingList []interface{} + contentSettingMap := make(map[string]interface{}) + + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(contentSettingMap, "story", contentSettings.Story, flattenStorySettings) + + contentSettingList = append(contentSettingList, contentSettingMap) + + return contentSettingList +} + +// flattenSettingDirections maps a Genesys Cloud *[]platformclientv2.Settingdirection into a []interface{} +func flattenSettingDirections(settingDirections *platformclientv2.Settingdirection) []interface{} { + var settingDirectionList []interface{} + settingDirectionMap := make(map[string]interface{}) + + resourcedata.SetMapValueIfNotNil(settingDirectionMap, "inbound", settingDirections.Inbound) + resourcedata.SetMapValueIfNotNil(settingDirectionMap, "outbound", settingDirections.Outbound) + + settingDirectionList = append(settingDirectionList, settingDirectionMap) + + return settingDirectionList +} + +// flattenTypingSettings maps a Genesys Cloud *[]platformclientv2.Typingsetting into a []interface{} +func flattenTypingSettings(typingSettings *platformclientv2.Typingsetting) []interface{} { + var typingSettingList []interface{} + typingSettingMap := make(map[string]interface{}) + + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(typingSettingMap, "on", typingSettings.On, flattenSettingDirections) + + typingSettingList = append(typingSettingList, typingSettingMap) + + return typingSettingList +} + +// flattenEventSettings maps a Genesys Cloud *[]platformclientv2.Eventsetting into a []interface{} +func flattenEventSettings(eventSettings *platformclientv2.Eventsetting) []interface{} { + var eventSettingList []interface{} + eventSettingMap := make(map[string]interface{}) + + resourcedata.SetMapInterfaceArrayWithFuncIfNotNil(eventSettingMap, "typing", eventSettings.Typing, flattenTypingSettings) + + eventSettingList = append(eventSettingList, eventSettingMap) + + return eventSettingList +} + +func generateConversationsMessagingSettingsResource(resourceID string, name string, nestedBlocks ...string) string { + return fmt.Sprintf(`resource "genesyscloud_conversations_messaging_settings" "%s" { + name = "%s" + %s + } + `, resourceID, name, strings.Join(nestedBlocks, "\n")) +} + +func generateTypingOnSetting(inbound, outbound string) string { + return fmt.Sprintf(` + event { + typing { + on { + inbound = "%s" + outbound = "%s" + } + } + }`, inbound, outbound) +} + +func generateContentStoryBlock(nestedBlocks ...string) string { + return fmt.Sprintf(` + content { + story { + %s + } + }`, strings.Join(nestedBlocks, "\n")) +} + +func generateMentionInboundOnlySetting(value string) string { + return fmt.Sprintf(` + mention { + inbound = "%s" + } + `, value) +} + +func generateReplyInboundOnlySetting(value string) string { + return fmt.Sprintf(` + reply { + inbound = "%s" + }`, value) +} diff --git a/genesyscloud/data_source_genesyscloud_auth_division_test.go b/genesyscloud/data_source_genesyscloud_auth_division_test.go index 2fbde2513..be4d3e75e 100644 --- a/genesyscloud/data_source_genesyscloud_auth_division_test.go +++ b/genesyscloud/data_source_genesyscloud_auth_division_test.go @@ -2,6 +2,7 @@ package genesyscloud import ( "fmt" + "log" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -17,6 +18,7 @@ func TestAccDataSourceAuthDivision(t *testing.T) { divResource = "auth-division" divDataSource = "auth-div-data" divName = "Terraform Divisions-" + uuid.NewString() + divisionID string ) resource.Test(t, resource.TestCase{ @@ -27,6 +29,26 @@ func TestAccDataSourceAuthDivision(t *testing.T) { PreConfig: func() { time.Sleep(30 * time.Second) }, + Config: GenerateAuthDivisionResource( + divResource, + divName, + util.NullValue, + util.NullValue, + ), + Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["genesyscloud_auth_division."+divResource] + if !ok { + return fmt.Errorf("not found: %s", "genesyscloud_auth_division."+divResource) + } + divisionID = rs.Primary.ID + log.Printf("Division ID: %s\n", divisionID) // Print ID + return nil + }, + ), + PreventPostDestroyRefresh: true, + }, + { Config: GenerateAuthDivisionResource( divResource, divName, @@ -39,14 +61,20 @@ func TestAccDataSourceAuthDivision(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair("data.genesyscloud_auth_division."+divDataSource, "id", "genesyscloud_auth_division."+divResource, "id"), - func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion - return nil - }, ), }, + { + // Import/Read + ResourceName: "genesyscloud_auth_division." + divResource, + ImportState: true, + ImportStateVerify: true, + Destroy: true, + }, + }, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyDivisionsDestroyed(state) }, - CheckDestroy: testVerifyDivisionsDestroyed, }) } diff --git a/genesyscloud/data_source_genesyscloud_routing_email_domain.go b/genesyscloud/data_source_genesyscloud_routing_email_domain.go deleted file mode 100644 index dadf36ac9..000000000 --- a/genesyscloud/data_source_genesyscloud_routing_email_domain.go +++ /dev/null @@ -1,63 +0,0 @@ -package genesyscloud - -import ( - "context" - "fmt" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" -) - -// Returns the schema for the routing email domain -func DataSourceRoutingEmailDomain() *schema.Resource { - return &schema.Resource{ - Description: "Data source for Genesys Cloud Email Domains. Select an email domain by name", - ReadContext: provider.ReadWithPooledClient(DataSourceRoutingEmailDomainRead), - Schema: map[string]*schema.Schema{ - "name": { - Description: "Email domain name.", - Type: schema.TypeString, - Required: true, - }, - }, - } -} - -// Looks up the data for the Email Domain -func DataSourceRoutingEmailDomainRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - sdkConfig := m.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - name := d.Get("name").(string) - - return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - for pageNum := 1; ; pageNum++ { - const pageSize = 100 - - domains, resp, getErr := routingAPI.GetRoutingEmailDomains(pageSize, pageNum, false, "") - - if getErr != nil { - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Error requesting email domain %s | error: %s", name, getErr), resp)) - } - - //// No record found, keep trying for X seconds as this might an eventual consistency problem - if domains.Entities == nil || len(*domains.Entities) == 0 { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("No email domains found with name %s", name), resp)) - } - - // Once I get a result, cycle through until we find a name that matches - for _, domain := range *domains.Entities { - if domain.Id != nil && *domain.Id == name { - d.SetId(*domain.Id) - return nil - } - } - } - }) -} diff --git a/genesyscloud/data_source_genesyscloud_routing_language.go b/genesyscloud/data_source_genesyscloud_routing_language.go deleted file mode 100644 index 729080fc8..000000000 --- a/genesyscloud/data_source_genesyscloud_routing_language.go +++ /dev/null @@ -1,59 +0,0 @@ -package genesyscloud - -import ( - "context" - "fmt" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" -) - -func dataSourceRoutingLanguage() *schema.Resource { - return &schema.Resource{ - Description: "Data source for Genesys Cloud Routing Languages. Select a language by name.", - ReadContext: provider.ReadWithPooledClient(dataSourceRoutingLanguageRead), - Schema: map[string]*schema.Schema{ - "name": { - Description: "Language name.", - Type: schema.TypeString, - Required: true, - }, - }, - } -} - -func dataSourceRoutingLanguageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - sdkConfig := m.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - name := d.Get("name").(string) - - // Find first non-deleted language by name. Retry in case new language is not yet indexed by search - return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - for pageNum := 1; ; pageNum++ { - const pageSize = 50 - languages, resp, getErr := routingAPI.GetRoutingLanguages(pageSize, pageNum, "", name, nil) - if getErr != nil { - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Error requesting language %s | error: %s", name, getErr), resp)) - } - - if languages.Entities == nil || len(*languages.Entities) == 0 { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("No routing languages found with name %s", name), resp)) - } - - for _, language := range *languages.Entities { - if language.Name != nil && *language.Name == name && - language.State != nil && *language.State != "deleted" { - d.SetId(*language.Id) - return nil - } - } - } - }) -} diff --git a/genesyscloud/data_source_genesyscloud_user_test.go b/genesyscloud/data_source_genesyscloud_user_test.go index f5c639ac1..502d63edb 100644 --- a/genesyscloud/data_source_genesyscloud_user_test.go +++ b/genesyscloud/data_source_genesyscloud_user_test.go @@ -2,12 +2,15 @@ package genesyscloud import ( "fmt" + "log" "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" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccDataSourceUser(t *testing.T) { @@ -15,8 +18,9 @@ func TestAccDataSourceUser(t *testing.T) { userResource = "test-user" userDataSource = "test-user-data" randomString = uuid.NewString() - userEmail = "John_Doe" + randomString + "@example.com" + userEmail = "John_Doe" + randomString + "@exampleuser.com" userName = "John_Doe" + randomString + userID string ) resource.Test(t, resource.TestCase{ @@ -37,6 +41,15 @@ func TestAccDataSourceUser(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair("data.genesyscloud_user."+userDataSource, "id", "genesyscloud_user."+userResource, "id"), + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["genesyscloud_user."+userResource] + if !ok { + return fmt.Errorf("not found: %s", "genesyscloud_user."+userResource) + } + userID = rs.Primary.ID + log.Printf("User ID: %s\n", userID) // Print user ID + return nil + }, ), }, { @@ -53,9 +66,17 @@ func TestAccDataSourceUser(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair("data.genesyscloud_user."+userDataSource, "id", "genesyscloud_user."+userResource, "id"), + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper deletion + return nil + }, ), }, }, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyUsersDestroyed(state) + }, }) } diff --git a/genesyscloud/group/data_source_genesyscloud_group_test.go b/genesyscloud/group/data_source_genesyscloud_group_test.go index 548649fc7..24561d178 100644 --- a/genesyscloud/group/data_source_genesyscloud_group_test.go +++ b/genesyscloud/group/data_source_genesyscloud_group_test.go @@ -1,6 +1,7 @@ package group import ( + "context" "fmt" "log" "sync" @@ -13,12 +14,12 @@ import ( "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) var ( - sdkConfig *platformclientv2.Configuration - mu sync.Mutex + mu sync.Mutex ) func TestAccDataSourceGroup(t *testing.T) { @@ -37,6 +38,9 @@ func TestAccDataSourceGroup(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { + PreConfig: func() { + time.Sleep(30 * time.Second) + }, Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateGroupResource( groupResource, @@ -62,16 +66,20 @@ func TestAccDataSourceGroup(t *testing.T) { return nil }, ), + + PreventPostDestroyRefresh: true, }, { ResourceName: "genesyscloud_user." + testUserResource, ImportState: true, ImportStateVerify: true, - Check: resource.ComposeTestCheckFunc( - checkUserDeleted(userID), - ), + Destroy: true, }, }, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyUsersDestroyed(state) + }, }) } @@ -89,9 +97,9 @@ func generateGroupDataSource( } func checkUserDeleted(id string) resource.TestCheckFunc { - log.Printf("Fetching user with ID: %s\n", id) return func(s *terraform.State) error { - maxAttempts := 18 + maxAttempts := 30 + fmt.Printf("Fetching user with ID: %s\n", id) for i := 0; i < maxAttempts; i++ { deleted, err := isUserDeleted(id) @@ -111,8 +119,9 @@ func isUserDeleted(id string) (bool, error) { mu.Lock() defer mu.Unlock() - usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + usersAPI := platformclientv2.NewUsersApi() // Attempt to get the user + fmt.Printf("User ID: %s\n", id) _, response, err := usersAPI.GetUser(id, nil, "", "") // Check if the user is not found (deleted) @@ -129,3 +138,36 @@ func isUserDeleted(id string) (bool, error) { // If user is found, it means the user is not deleted return false, nil } + +func testVerifyUsersDestroyed(state *terraform.State) error { + usersAPI := platformclientv2.NewUsersApi() + + diagErr := util.WithRetries(context.Background(), 20*time.Second, func() *retry.RetryError { + for _, rs := range state.RootModule().Resources { + if rs.Type != "genesyscloud_user" { + continue + } + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } + _, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + + if err != nil { + if util.IsStatus404(resp) { + continue + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("Unexpected error: %s", err), resp)) + } + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("User (%s) still exists", rs.Primary.ID), resp)) + } + return nil + }) + + if diagErr != nil { + return fmt.Errorf(fmt.Sprintf("%v", diagErr)) + } + + // Success. All users destroyed + return nil +} diff --git a/genesyscloud/group/resource_genesyscloud_group_test.go b/genesyscloud/group/resource_genesyscloud_group_test.go index c889d8a4b..8503d22c3 100644 --- a/genesyscloud/group/resource_genesyscloud_group_test.go +++ b/genesyscloud/group/resource_genesyscloud_group_test.go @@ -28,6 +28,7 @@ func TestAccResourceGroupBasic(t *testing.T) { testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() testUserEmail = uuid.NewString() + "@group.com" + userID string ) resource.Test(t, resource.TestCase{ @@ -76,19 +77,36 @@ func TestAccResourceGroupBasic(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "rules_visible", util.FalseValue), resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "roles_enabled", util.TrueValue), 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 }, ), + + PreventPostDestroyRefresh: true, }, { + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateGroupResource( + groupResource1, + groupName, + strconv.Quote(groupDesc2), + strconv.Quote(typeOfficial), // Cannot change type + strconv.Quote(visMembers), + util.FalseValue, + GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + ), // Import/Read ResourceName: "genesyscloud_group." + groupResource1, ImportState: true, ImportStateVerify: true, + Destroy: true, }, }, - CheckDestroy: testVerifyGroupsDestroyed, + CheckDestroy: testVerifyGroupsAndUsersDestroyed, }) } @@ -105,6 +123,7 @@ func TestAccResourceGroupAddresses(t *testing.T) { testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() testUserEmail = uuid.NewString() + "@groupadd.com" + userID string ) resource.Test(t, resource.TestCase{ @@ -163,6 +182,15 @@ func TestAccResourceGroupAddresses(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "name", groupName), resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "addresses.0.type", typeGroupPhone), resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "addresses.0.extension", addrPhoneExt), + func(s *terraform.State) error { + 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 + }, ), }, { @@ -178,24 +206,36 @@ func TestAccResourceGroupAddresses(t *testing.T) { GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), ), Check: resource.ComposeTestCheckFunc( - func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for resources to get deleted properly - return nil - }, resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "name", groupName), resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "addresses.0.type", typeGroupPhone), resource.TestCheckResourceAttr("genesyscloud_group."+groupResource1, "addresses.0.extension", addrPhoneExt2), ), + + PreventPostDestroyRefresh: true, }, { + Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + GenerateBasicGroupResource( + groupResource1, + groupName, + generateGroupAddress( + util.NullValue, + typeGroupPhone, + strconv.Quote(addrPhoneExt2), + ), + GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), + ), // Import/Read ResourceName: "genesyscloud_group." + groupResource1, ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"addresses"}, + Destroy: true, + Check: resource.ComposeTestCheckFunc( + checkUserDeleted(userID), + ), }, }, - CheckDestroy: testVerifyGroupsDestroyed, + CheckDestroy: testVerifyGroupsAndUsersDestroyed, }) } @@ -312,17 +352,19 @@ func TestAccResourceGroupMembers(t *testing.T) { return nil }, ), + PreventPostDestroyRefresh: true, }, { ResourceName: "genesyscloud_user." + testUserResource, ImportState: true, ImportStateVerify: true, - Check: resource.ComposeTestCheckFunc( - checkUserDeleted(userID), - ), + Destroy: true, }, }, - CheckDestroy: testVerifyGroupsDestroyed, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyGroupsAndUsersDestroyed(state) + }, }) } @@ -348,6 +390,45 @@ func testVerifyGroupsDestroyed(state *terraform.State) error { return nil } +func testVerifyGroupsAndUsersDestroyed(state *terraform.State) error { + groupsAPI := platformclientv2.NewGroupsApi() + usersAPI := platformclientv2.NewUsersApi() + for _, rs := range state.RootModule().Resources { + if rs.Type == "genesyscloud_group" { + group, resp, err := groupsAPI.GetGroup(rs.Primary.ID) + if group != nil { + return fmt.Errorf("Group (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Group not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + } + for _, rs := range state.RootModule().Resources { + if rs.Type == "genesyscloud_user" { + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue //Try one more time + } + user, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + if user != nil { + return fmt.Errorf("User Resource (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // User not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + + } + return nil +} + func validateGroupMember(groupResourceName string, userResourceName string, attrName string) resource.TestCheckFunc { return func(state *terraform.State) error { groupResource, ok := state.RootModule().Resources[groupResourceName] diff --git a/genesyscloud/group/resource_genesyscloud_group_utils.go b/genesyscloud/group/resource_genesyscloud_group_utils.go index 1c93548fd..4526a8cfb 100644 --- a/genesyscloud/group/resource_genesyscloud_group_utils.go +++ b/genesyscloud/group/resource_genesyscloud_group_utils.go @@ -34,7 +34,7 @@ func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2 // Strip off any parentheses from phone numbers if address.Address != nil { - phoneNumber["number"] = strings.Trim(*address.Address, "()") + phoneNumber["number"], _ = util.FormatAsE164Number(strings.Trim(*address.Address, "()")) } resourcedata.SetMapValueIfNotNil(phoneNumber, "extension", address.Extension) @@ -77,7 +77,7 @@ func setExtensionOrNumberBasedOnDisplay(d *schema.ResourceData, addressMap map[s if ext, _ := currentAddress["extension"].(string); ext != "" { addressMap["extension"] = display } else if number, _ := currentAddress["number"].(string); number != "" { - addressMap["number"] = display + addressMap["number"], _ = util.FormatAsE164Number(display) } } } diff --git a/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go b/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go index 308356348..dfbae37df 100644 --- a/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go +++ b/genesyscloud/group_roles/resource_genesyscloud_group_roles_test.go @@ -2,8 +2,10 @@ package group_roles import ( "fmt" + "log" "strconv" "strings" + "sync" "terraform-provider-genesyscloud/genesyscloud" "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" @@ -13,6 +15,7 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" @@ -20,6 +23,10 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) +var ( + mu sync.Mutex +) + func TestAccResourceGroupRolesMembership(t *testing.T) { var ( groupRoleResource = "test-group-roles1" @@ -35,6 +42,7 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() testUserEmail = uuid.NewString() + "@example.com" + userID string ) resource.Test(t, resource.TestCase{ @@ -58,6 +66,15 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( validateResourceRole("genesyscloud_group_roles."+groupRoleResource, "genesyscloud_auth_role."+roleResource1), + func(s *terraform.State) error { + 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 + }, ), }, { @@ -125,14 +142,25 @@ func TestAccResourceGroupRolesMembership(t *testing.T) { return nil }, ), + + PreventPostDestroyRefresh: true, }, { + Config: generateGroupRoles( + groupRoleResource, + groupResource1, + ), // Import/Read ResourceName: "genesyscloud_group_roles." + groupRoleResource, ImportState: true, ImportStateVerify: true, + Destroy: true, + Check: resource.ComposeTestCheckFunc( + checkUserDeleted(userID), + ), }, }, + CheckDestroy: testVerifyGroupsAndUsersDestroyed, }) } @@ -229,3 +257,82 @@ 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 := 30 + 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.NewUsersApi() + // 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 +} + +func testVerifyGroupsAndUsersDestroyed(state *terraform.State) error { + groupsAPI := platformclientv2.NewGroupsApi() + usersAPI := platformclientv2.NewUsersApi() + for _, rs := range state.RootModule().Resources { + if rs.Type == "genesyscloud_group" { + group, resp, err := groupsAPI.GetGroup(rs.Primary.ID) + if group != nil { + return fmt.Errorf("Group (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Group not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + if rs.Type == "genesyscloud_user" { + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } + user, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + if user != nil { + return fmt.Errorf("User (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // User not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + + } + return nil +} diff --git a/genesyscloud/integration/resource_genesyscloud_integration_test.go b/genesyscloud/integration/resource_genesyscloud_integration_test.go index 41a195fd3..0f607f033 100644 --- a/genesyscloud/integration/resource_genesyscloud_integration_test.go +++ b/genesyscloud/integration/resource_genesyscloud_integration_test.go @@ -2,8 +2,10 @@ package integration import ( "fmt" + "log" "strconv" "strings" + "sync" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -17,6 +19,10 @@ import ( "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" ) +var ( + mu sync.Mutex +) + /* The resource_genesyscloud_integration_test.go contains all of the test cases for running the resource tests for integrations. @@ -333,7 +339,7 @@ func TestAccResourceIntegration(t *testing.T) { ImportStateVerify: true, }, }, - CheckDestroy: testVerifyIntegrationDestroyed, + CheckDestroy: testVerifyIntegrationAndUsersDestroyed, }) } @@ -375,22 +381,37 @@ func validateIntegrationProperties(integrationResourceName string, groupResource } } -func testVerifyIntegrationDestroyed(state *terraform.State) error { +func testVerifyIntegrationAndUsersDestroyed(state *terraform.State) error { integrationAPI := platformclientv2.NewIntegrationsApi() + usersAPI := platformclientv2.NewUsersApi() for _, rs := range state.RootModule().Resources { - if rs.Type != "genesyscloud_integration" { - continue + if rs.Type == "genesyscloud_integration" { + integration, resp, err := integrationAPI.GetIntegration(rs.Primary.ID, 100, 1, "", nil, "", "") + if integration != nil { + return fmt.Errorf("Integration (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Integration not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } } - - integration, resp, err := integrationAPI.GetIntegration(rs.Primary.ID, 100, 1, "", nil, "", "") - if integration != nil { - return fmt.Errorf("Integration (%s) still exists", rs.Primary.ID) - } else if util.IsStatus404(resp) { - // Integration not found as expected - continue - } else { - // Unexpected error - return fmt.Errorf("Unexpected error: %s", err) + if rs.Type == "genesyscloud_user" { + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } + user, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + if user != nil { + return fmt.Errorf("User Resource (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // User not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } } } // Success. All integrations destroyed @@ -435,3 +456,45 @@ func generateGroupOwners(userIDs ...string) string { return fmt.Sprintf(`owner_ids = [%s] `, strings.Join(userIDs, ",")) } + +func checkUserDeleted(id string) resource.TestCheckFunc { + log.Printf("Fetching user with ID: %s\n", id) + return func(s *terraform.State) error { + maxAttempts := 30 + 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.NewUsersApi() + // 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/integration_credential/data_source_genesyscloud_integration_credential_test.go b/genesyscloud/integration_credential/data_source_genesyscloud_integration_credential_test.go index a96945459..f5cf01753 100644 --- a/genesyscloud/integration_credential/data_source_genesyscloud_integration_credential_test.go +++ b/genesyscloud/integration_credential/data_source_genesyscloud_integration_credential_test.go @@ -6,9 +6,11 @@ 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" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) /* @@ -43,6 +45,10 @@ func TestAccDataSourceIntegrationCredential(t *testing.T) { credName1, "genesyscloud_integration_credential."+credResource1), Check: resource.ComposeTestCheckFunc( + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper creation + return nil + }, resource.TestCheckResourceAttrPair("data.genesyscloud_integration_credential."+credResource2, "id", "genesyscloud_integration_credential."+credResource1, "id"), // Default value would be "DISABLED" ), }, diff --git a/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go b/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go index 10117e498..65e0c948f 100644 --- a/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go +++ b/genesyscloud/orgauthorization_pairing/resource_genesyscloud_orgauthorization_pairing_test.go @@ -38,6 +38,9 @@ func TestAccResourceOrgAuthorizationPairing(t *testing.T) { Steps: []resource.TestStep{ // 1 user and 1 group { + PreConfig: func() { + time.Sleep(45 * time.Second) + }, Config: generateUserWithCustomAttrs(testUserResource, testUserEmail, testUserName) + genesyscloud.GenerateBasicUserResource( userResource1, email1, diff --git a/genesyscloud/outbound/data_source_genesyscloud_outbound_messagingcampaign_test.go b/genesyscloud/outbound/data_source_genesyscloud_outbound_messagingcampaign_test.go index 56192696e..e80e5f35f 100644 --- a/genesyscloud/outbound/data_source_genesyscloud_outbound_messagingcampaign_test.go +++ b/genesyscloud/outbound/data_source_genesyscloud_outbound_messagingcampaign_test.go @@ -2,6 +2,7 @@ package outbound import ( "fmt" + "os" "strconv" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" @@ -86,21 +87,23 @@ func TestAccDataSourceOutboundMessagingCampaign(t *testing.T) { ) ) + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" { + smsConfigSenderSMSPhoneNumber = "+18159823725" + } + config, err := provider.AuthorizeSdk() if err != nil { t.Errorf("failed to authorize client: %v", err) } - api := platformclientv2.NewRoutingApiWithConfig(config) - err = createRoutingSmsPhoneNumber(smsConfigSenderSMSPhoneNumber, api) - if err != nil { - t.Errorf("error creating sms phone number %s: %v", smsConfigSenderSMSPhoneNumber, err) - } - defer func() { - _, err := api.DeleteRoutingSmsPhonenumber(smsConfigSenderSMSPhoneNumber) + + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "us-east-1" { + api := platformclientv2.NewRoutingApiWithConfig(config) + err = createRoutingSmsPhoneNumber(smsConfigSenderSMSPhoneNumber, api) if err != nil { - t.Logf("error deleting phone number %s: %v", smsConfigSenderSMSPhoneNumber, err) + t.Errorf("error creating sms phone number %s: %v", smsConfigSenderSMSPhoneNumber, err) } - }() + //Do not delete the smsPhoneNumber + } resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, diff --git a/genesyscloud/outbound/resource_genesyscloud_outbound_messagingcampaign_test.go b/genesyscloud/outbound/resource_genesyscloud_outbound_messagingcampaign_test.go index 7f4e83de3..a43ed30e0 100644 --- a/genesyscloud/outbound/resource_genesyscloud_outbound_messagingcampaign_test.go +++ b/genesyscloud/outbound/resource_genesyscloud_outbound_messagingcampaign_test.go @@ -3,6 +3,7 @@ package outbound import ( "fmt" "net/http" + "os" "strconv" "strings" obDnclist "terraform-provider-genesyscloud/genesyscloud/outbound_dnclist" @@ -107,21 +108,23 @@ func TestAccResourceOutboundMessagingCampaign(t *testing.T) { ) ) + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" { + smsConfigSenderSMSPhoneNumber = "+18159823725" + } + config, err := provider.AuthorizeSdk() if err != nil { t.Errorf("failed to authorize client: %v", err) } - api := platformclientv2.NewRoutingApiWithConfig(config) - err = createRoutingSmsPhoneNumber(smsConfigSenderSMSPhoneNumber, api) - if err != nil { - t.Errorf("error creating sms phone number %s: %v", smsConfigSenderSMSPhoneNumber, err) - } - defer func() { - _, err := api.DeleteRoutingSmsPhonenumber(smsConfigSenderSMSPhoneNumber) + + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "us-east-1" { + api := platformclientv2.NewRoutingApiWithConfig(config) + err = createRoutingSmsPhoneNumber(smsConfigSenderSMSPhoneNumber, api) if err != nil { - t.Logf("error deleting phone number %s: %v", smsConfigSenderSMSPhoneNumber, err) + t.Errorf("error creating sms phone number %s: %v", smsConfigSenderSMSPhoneNumber, err) } - }() + //Do not delete the smsPhoneNumber + } resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, diff --git a/genesyscloud/outbound_campaign/resource_genesyscloud_outbound_campaign_test.go b/genesyscloud/outbound_campaign/resource_genesyscloud_outbound_campaign_test.go index 55a7c5004..a420ba332 100644 --- a/genesyscloud/outbound_campaign/resource_genesyscloud_outbound_campaign_test.go +++ b/genesyscloud/outbound_campaign/resource_genesyscloud_outbound_campaign_test.go @@ -605,11 +605,10 @@ func TestAccResourceOutboundCampaignCampaignStatus(t *testing.T) { } func TestAccResourceOutboundCampaignStatusOn(t *testing.T) { - t.Skip("Outbound Campaign is not switched off, destroy fails as campaign keeps running") t.Parallel() var ( resourceId = "campaign3" - name = "Test Campaign " + uuid.NewString() + name = "Test Campaign - " + uuid.NewString() contactListResourceId = "contact_list" carResourceId = "car" siteId = "site" @@ -679,9 +678,12 @@ func TestAccResourceOutboundCampaignStatusOn(t *testing.T) { resource.TestCheckResourceAttrPair("genesyscloud_outbound_campaign."+resourceId, "call_analysis_response_set_id", "genesyscloud_outbound_callanalysisresponseset."+carResourceId, "id"), util.VerifyAttributeInArrayOfPotentialValues("genesyscloud_outbound_campaign."+resourceId, "campaign_status", []string{"on", "complete"}), + func(s *terraform.State) error { + time.Sleep(300 * time.Second) // Takes approx. 300 seconds for campaign to be completed / stopped + return nil + }, ), }, - // Don't turn campaign back off to ensure campaign can be destroyed properly by turning it off within the destroy handler { // Import/Read ResourceName: "genesyscloud_outbound_campaign." + resourceId, diff --git a/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contact_list.go b/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contact_list.go new file mode 100644 index 000000000..78287bd86 --- /dev/null +++ b/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contact_list.go @@ -0,0 +1,35 @@ +package outbound_contact_list + +import ( + "context" + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceOutboundContactListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sdkConfig := m.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlistProxy(sdkConfig) + name := d.Get("name").(string) + + return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + const pageNum = 1 + const pageSize = 100 + contactListId, retryable, resp, err := proxy.getOutboundContactlistIdByName(ctx, name) + + if err != nil && !retryable { + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting contact list %s | error: %s", name, err), resp)) + } + if retryable { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no contact list found with name %s", name), resp)) + } + d.SetId(contactListId) + return nil + }) +} diff --git a/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contactlist_test.go b/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contact_list_test.go similarity index 83% rename from genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contactlist_test.go rename to genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contact_list_test.go index 4e6194bd9..b58f84862 100644 --- a/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contactlist_test.go +++ b/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contact_list_test.go @@ -41,11 +41,11 @@ func TestAccDataSourceOutboundContactList(t *testing.T) { ) + generateOutboundContactListDataSource( dataSourceId, contactListName, - "genesyscloud_outbound_contact_list."+resourceId, + resourceName+"."+resourceId, ), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair("data.genesyscloud_outbound_contact_list."+dataSourceId, "id", - "genesyscloud_outbound_contact_list."+resourceId, "id"), + resource.TestCheckResourceAttrPair("data."+resourceName+"."+dataSourceId, "id", + resourceName+"."+resourceId, "id"), ), }, }, @@ -54,9 +54,9 @@ func TestAccDataSourceOutboundContactList(t *testing.T) { func generateOutboundContactListDataSource(id string, name string, dependsOn string) string { return fmt.Sprintf(` -data "genesyscloud_outbound_contact_list" "%s" { +data "%s" "%s" { name = "%s" depends_on = [%s] } -`, id, name, dependsOn) +`, resourceName, id, name, dependsOn) } diff --git a/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contactlist.go b/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contactlist.go deleted file mode 100644 index b3fb96131..000000000 --- a/genesyscloud/outbound_contact_list/data_source_genesyscloud_outbound_contactlist.go +++ /dev/null @@ -1,50 +0,0 @@ -package outbound_contact_list - -import ( - "context" - "fmt" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" -) - -func DataSourceOutboundContactList() *schema.Resource { - return &schema.Resource{ - Description: "Data source for Genesys Cloud Outbound Contact Lists. Select a contact list by name.", - ReadContext: provider.ReadWithPooledClient(dataSourceOutboundContactListRead), - Schema: map[string]*schema.Schema{ - "name": { - Description: "Contact List name.", - Type: schema.TypeString, - Required: true, - }, - }, - } -} - -func dataSourceOutboundContactListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - sdkConfig := m.(*provider.ProviderMeta).ClientConfig - outboundAPI := platformclientv2.NewOutboundApiWithConfig(sdkConfig) - name := d.Get("name").(string) - - return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - const pageNum = 1 - const pageSize = 100 - contactLists, resp, getErr := outboundAPI.GetOutboundContactlists(false, false, pageSize, pageNum, true, "", name, []string{""}, []string{""}, "", "") - if getErr != nil { - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting contact list %s | error: %s", name, getErr), resp)) - } - if contactLists.Entities == nil || len(*contactLists.Entities) == 0 { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no contact lists found with name %s", name), resp)) - } - contactList := (*contactLists.Entities)[0] - d.SetId(*contactList.Id) - return nil - }) -} diff --git a/genesyscloud/outbound_contact_list/genesyscloud_outbound_contact_list_proxy.go b/genesyscloud/outbound_contact_list/genesyscloud_outbound_contact_list_proxy.go new file mode 100644 index 000000000..b1ec06b18 --- /dev/null +++ b/genesyscloud/outbound_contact_list/genesyscloud_outbound_contact_list_proxy.go @@ -0,0 +1,185 @@ +package outbound_contact_list + +import ( + "context" + "fmt" + "log" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +/* +The genesyscloud_outbound_contact_list_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 *outboundContactlistProxy + +// Type definitions for each func on our proxy so we can easily mock them out later +type createOutboundContactlistFunc func(ctx context.Context, p *outboundContactlistProxy, contactList *platformclientv2.Contactlist) (*platformclientv2.Contactlist, *platformclientv2.APIResponse, error) +type getAllOutboundContactlistFunc func(ctx context.Context, p *outboundContactlistProxy, name string) (*[]platformclientv2.Contactlist, *platformclientv2.APIResponse, error) +type getOutboundContactlistIdByNameFunc func(ctx context.Context, p *outboundContactlistProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) +type getOutboundContactlistByIdFunc func(ctx context.Context, p *outboundContactlistProxy, id string) (contactList *platformclientv2.Contactlist, response *platformclientv2.APIResponse, err error) +type updateOutboundContactlistFunc func(ctx context.Context, p *outboundContactlistProxy, id string, contactList *platformclientv2.Contactlist) (*platformclientv2.Contactlist, *platformclientv2.APIResponse, error) +type deleteOutboundContactlistFunc func(ctx context.Context, p *outboundContactlistProxy, id string) (response *platformclientv2.APIResponse, err error) + +// outboundContactlistProxy contains all of the methods that call genesys cloud APIs. +type outboundContactlistProxy struct { + clientConfig *platformclientv2.Configuration + outboundApi *platformclientv2.OutboundApi + createOutboundContactlistAttr createOutboundContactlistFunc + getAllOutboundContactlistAttr getAllOutboundContactlistFunc + getOutboundContactlistIdByNameAttr getOutboundContactlistIdByNameFunc + getOutboundContactlistByIdAttr getOutboundContactlistByIdFunc + updateOutboundContactlistAttr updateOutboundContactlistFunc + deleteOutboundContactlistAttr deleteOutboundContactlistFunc +} + +// newOutboundContactlistProxy initializes the outbound contactlist proxy with all of the data needed to communicate with Genesys Cloud +func newOutboundContactlistProxy(clientConfig *platformclientv2.Configuration) *outboundContactlistProxy { + api := platformclientv2.NewOutboundApiWithConfig(clientConfig) + return &outboundContactlistProxy{ + clientConfig: clientConfig, + outboundApi: api, + createOutboundContactlistAttr: createOutboundContactlistFn, + getAllOutboundContactlistAttr: getAllOutboundContactlistFn, + getOutboundContactlistIdByNameAttr: getOutboundContactlistIdByNameFn, + getOutboundContactlistByIdAttr: getOutboundContactlistByIdFn, + updateOutboundContactlistAttr: updateOutboundContactlistFn, + deleteOutboundContactlistAttr: deleteOutboundContactlistFn, + } +} + +// getOutboundContactlistProxy 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 getOutboundContactlistProxy(clientConfig *platformclientv2.Configuration) *outboundContactlistProxy { + if internalProxy == nil { + internalProxy = newOutboundContactlistProxy(clientConfig) + } + return internalProxy +} + +// createOutboundContactlist creates a Genesys Cloud outbound contactlist +func (p *outboundContactlistProxy) createOutboundContactlist(ctx context.Context, outboundContactlist *platformclientv2.Contactlist) (*platformclientv2.Contactlist, *platformclientv2.APIResponse, error) { + return p.createOutboundContactlistAttr(ctx, p, outboundContactlist) +} + +// getOutboundContactlist retrieves all Genesys Cloud outbound contactlist +func (p *outboundContactlistProxy) getAllOutboundContactlist(ctx context.Context) (*[]platformclientv2.Contactlist, *platformclientv2.APIResponse, error) { + return p.getAllOutboundContactlistAttr(ctx, p, "") +} + +// getOutboundContactlistIdByName returns a single Genesys Cloud outbound contactlist by a name +func (p *outboundContactlistProxy) getOutboundContactlistIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) { + return p.getOutboundContactlistIdByNameAttr(ctx, p, name) +} + +// getOutboundContactlistById returns a single Genesys Cloud outbound contactlist by Id +func (p *outboundContactlistProxy) getOutboundContactlistById(ctx context.Context, id string) (outboundContactlist *platformclientv2.Contactlist, response *platformclientv2.APIResponse, err error) { + return p.getOutboundContactlistByIdAttr(ctx, p, id) +} + +// updateOutboundContactlist updates a Genesys Cloud outbound contactlist +func (p *outboundContactlistProxy) updateOutboundContactlist(ctx context.Context, id string, outboundContactlist *platformclientv2.Contactlist) (*platformclientv2.Contactlist, *platformclientv2.APIResponse, error) { + return p.updateOutboundContactlistAttr(ctx, p, id, outboundContactlist) +} + +// deleteOutboundContactlist deletes a Genesys Cloud outbound contactlist by Id +func (p *outboundContactlistProxy) deleteOutboundContactlist(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) { + return p.deleteOutboundContactlistAttr(ctx, p, id) +} + +// createOutboundContactlistFn is an implementation function for creating a Genesys Cloud outbound contactlist +func createOutboundContactlistFn(ctx context.Context, p *outboundContactlistProxy, outboundContactlist *platformclientv2.Contactlist) (*platformclientv2.Contactlist, *platformclientv2.APIResponse, error) { + contactList, resp, err := p.outboundApi.PostOutboundContactlists(*outboundContactlist) + if err != nil { + return nil, resp, err + } + return contactList, resp, nil +} + +// getAllOutboundContactlistFn is the implementation for retrieving all outbound contactlist in Genesys Cloud +func getAllOutboundContactlistFn(ctx context.Context, p *outboundContactlistProxy, name string) (*[]platformclientv2.Contactlist, *platformclientv2.APIResponse, error) { + var allContactlists []platformclientv2.Contactlist + const pageSize = 100 + + contactLists, resp, err := p.outboundApi.GetOutboundContactlists(false, false, pageSize, 1, true, "", name, []string{}, []string{}, "", "") + if err != nil { + return nil, resp, fmt.Errorf("failed to get page of contact list: %v", err) + } + + if contactLists.Entities == nil || len(*contactLists.Entities) == 0 { + return &allContactlists, resp, nil + } + + for _, contactList := range *contactLists.Entities { + allContactlists = append(allContactlists, contactList) + } + + for pageNum := 2; pageNum <= *contactLists.PageCount; pageNum++ { + contactLists, resp, err := p.outboundApi.GetOutboundContactlists(false, false, pageSize, pageNum, true, "", name, []string{}, []string{}, "", "") + if err != nil { + return nil, resp, fmt.Errorf("failed to get page of contact list : %v", err) + } + + if contactLists.Entities == nil || len(*contactLists.Entities) == 0 { + break + } + + for _, contactList := range *contactLists.Entities { + allContactlists = append(allContactlists, contactList) + } + } + + return &allContactlists, resp, nil +} + +// getOutboundContactlistIdByNameFn is an implementation of the function to get a Genesys Cloud outbound contactlist by name +func getOutboundContactlistIdByNameFn(ctx context.Context, p *outboundContactlistProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) { + contactLists, resp, err := getAllOutboundContactlistFn(ctx, p, name) + if err != nil { + return "", false, resp, fmt.Errorf("error searching outbound contact list %s: %s", name, err) + } + + var list platformclientv2.Contactlist + for _, contactList := range *contactLists { + if *contactList.Name == name { + log.Printf("Retrieved the contact list id %s by name %s", *contactList.Id, name) + list = contactList + return *list.Id, false, resp, nil + } + } + + return "", true, resp, nil +} + +// getOutboundContactlistByIdFn is an implementation of the function to get a Genesys Cloud outbound contactlist by Id +func getOutboundContactlistByIdFn(ctx context.Context, p *outboundContactlistProxy, id string) (outboundContactlist *platformclientv2.Contactlist, response *platformclientv2.APIResponse, err error) { + contactList, resp, err := p.outboundApi.GetOutboundContactlist(id, false, false) + if err != nil { + return nil, resp, err + } + return contactList, resp, nil +} + +// updateOutboundContactlistFn is an implementation of the function to update a Genesys Cloud outbound contactlist +func updateOutboundContactlistFn(ctx context.Context, p *outboundContactlistProxy, id string, outboundContactlist *platformclientv2.Contactlist) (*platformclientv2.Contactlist, *platformclientv2.APIResponse, error) { + contactList, resp, err := p.outboundApi.GetOutboundContactlist(id, false, false) + if err != nil { + return nil, resp, err + } + + outboundContactlist.Version = contactList.Version + outboundContactlist, resp, updateErr := p.outboundApi.PutOutboundContactlist(id, *outboundContactlist) + if updateErr != nil { + return nil, resp, updateErr + } + return outboundContactlist, resp, nil +} + +// deleteOutboundContactlistFn is an implementation function for deleting a Genesys Cloud outbound contactlist +func deleteOutboundContactlistFn(ctx context.Context, p *outboundContactlistProxy, id string) (response *platformclientv2.APIResponse, err error) { + return p.outboundApi.DeleteOutboundContactlist(id) +} diff --git a/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init.go b/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init.go index 0668e08bf..aced7f80c 100644 --- a/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init.go +++ b/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init.go @@ -4,8 +4,10 @@ import ( registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" ) +const resourceName = "genesyscloud_outbound_contact_list" + func SetRegistrar(regInstance registrar.Registrar) { - regInstance.RegisterDataSource("genesyscloud_outbound_contact_list", DataSourceOutboundContactList()) - regInstance.RegisterResource("genesyscloud_outbound_contact_list", ResourceOutboundContactList()) - regInstance.RegisterExporter("genesyscloud_outbound_contact_list", OutboundContactListExporter()) + regInstance.RegisterDataSource(resourceName, DataSourceOutboundContactList()) + regInstance.RegisterResource(resourceName, ResourceOutboundContactList()) + regInstance.RegisterExporter(resourceName, OutboundContactListExporter()) } diff --git a/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init_test.go b/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init_test.go index 927aaf1e5..722700c19 100644 --- a/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init_test.go +++ b/genesyscloud/outbound_contact_list/outbound_contact_list_resource_init_test.go @@ -31,7 +31,7 @@ func (r *registerTestInstance) registerTestResources() { r.resourceMapMutex.Lock() defer r.resourceMapMutex.Unlock() - providerResources["genesyscloud_outbound_contact_list"] = ResourceOutboundContactList() + providerResources[resourceName] = ResourceOutboundContactList() providerResources["genesyscloud_outbound_attempt_limit"] = obAttemptLimit.ResourceOutboundAttemptLimit() } @@ -39,7 +39,7 @@ func (r *registerTestInstance) registerTestDataSources() { r.datasourceMapMutex.Lock() defer r.datasourceMapMutex.Unlock() - providerDataSources["genesyscloud_outbound_contact_list"] = DataSourceOutboundContactList() + providerDataSources[resourceName] = DataSourceOutboundContactList() providerDataSources["genesyscloud_outbound_attempt_limit"] = obAttemptLimit.DataSourceOutboundAttemptLimit() providerDataSources["genesyscloud_auth_division_home"] = gcloud.DataSourceAuthDivisionHome() providerDataSources["genesyscloud_auth_division_home"] = gcloud.DataSourceAuthDivisionHome() diff --git a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list.go b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list.go new file mode 100644 index 000000000..2890775ff --- /dev/null +++ b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list.go @@ -0,0 +1,225 @@ +package outbound_contact_list + +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/v133/platformclientv2" +) + +func getAllOutboundContactLists(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + resources := make(resourceExporter.ResourceIDMetaMap) + proxy := getOutboundContactlistProxy(clientConfig) + + contactLists, resp, getErr := proxy.getAllOutboundContactlist(ctx) + if getErr != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get contact lists error: %s", getErr), resp) + } + + for _, contactList := range *contactLists { + resources[*contactList.Id] = &resourceExporter.ResourceMeta{Name: *contactList.Name} + } + + return resources, nil +} + +func createOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{})) + previewModeColumnName := d.Get("preview_mode_column_name").(string) + previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{})) + automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool) + zipCodeColumnName := d.Get("zip_code_column_name").(string) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlistProxy(sdkConfig) + + sdkContactList := platformclientv2.Contactlist{ + Division: util.BuildSdkDomainEntityRef(d, "division_id"), + ColumnNames: &columnNames, + PhoneColumns: buildSdkOutboundContactListContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)), + EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)), + PreviewModeAcceptedValues: &previewModeAcceptedValues, + AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"), + AutomaticTimeZoneMapping: &automaticTimeZoneMapping, + ColumnDataTypeSpecifications: buildSdkOutboundContactListColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})), + } + + if name != "" { + sdkContactList.Name = &name + } + if previewModeColumnName != "" { + sdkContactList.PreviewModeColumnName = &previewModeColumnName + } + if zipCodeColumnName != "" { + sdkContactList.ZipCodeColumnName = &zipCodeColumnName + } + + log.Printf("Creating Outbound Contact List %s", name) + outboundContactList, resp, err := proxy.createOutboundContactlist(ctx, &sdkContactList) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Contact List %s error: %s", name, err), resp) + } + + d.SetId(*outboundContactList.Id) + + log.Printf("Created Outbound Contact List %s %s", name, *outboundContactList.Id) + return readOutboundContactList(ctx, d, meta) +} + +func updateOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{})) + previewModeColumnName := d.Get("preview_mode_column_name").(string) + previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{})) + automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool) + zipCodeColumnName := d.Get("zip_code_column_name").(string) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlistProxy(sdkConfig) + + sdkContactList := platformclientv2.Contactlist{ + Division: util.BuildSdkDomainEntityRef(d, "division_id"), + ColumnNames: &columnNames, + PhoneColumns: buildSdkOutboundContactListContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)), + EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)), + PreviewModeAcceptedValues: &previewModeAcceptedValues, + AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"), + AutomaticTimeZoneMapping: &automaticTimeZoneMapping, + ColumnDataTypeSpecifications: buildSdkOutboundContactListColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})), + } + + if name != "" { + sdkContactList.Name = &name + } + if previewModeColumnName != "" { + sdkContactList.PreviewModeColumnName = &previewModeColumnName + } + if zipCodeColumnName != "" { + sdkContactList.ZipCodeColumnName = &zipCodeColumnName + } + + log.Printf("Updating Outbound Contact List %s", name) + diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + + _, resp, updateErr := proxy.updateOutboundContactlist(ctx, d.Id(), &sdkContactList) + if updateErr != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound contact list %s error: %s", name, updateErr), resp) + } + return nil, nil + }) + if diagErr != nil { + return diagErr + } + + log.Printf("Updated Outbound Contact List %s", name) + return readOutboundContactList(ctx, d, meta) +} + +func readOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlistProxy(sdkConfig) + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundContactList(), constants.DefaultConsistencyChecks, resourceName) + + log.Printf("Reading Outbound Contact List %s", d.Id()) + + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + sdkContactList, resp, getErr := proxy.getOutboundContactlistById(ctx, d.Id()) + if getErr != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List %s | error: %s", d.Id(), getErr), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List %s | error: %s", d.Id(), getErr), resp)) + } + + if sdkContactList.Name != nil { + _ = d.Set("name", *sdkContactList.Name) + } + if sdkContactList.Division != nil && sdkContactList.Division.Id != nil { + _ = d.Set("division_id", *sdkContactList.Division.Id) + } + if sdkContactList.ColumnNames != nil { + var columnNames []string + for _, name := range *sdkContactList.ColumnNames { + columnNames = append(columnNames, name) + } + _ = d.Set("column_names", columnNames) + } + if sdkContactList.PhoneColumns != nil { + _ = d.Set("phone_columns", flattenSdkOutboundContactListContactPhoneNumberColumnSlice(*sdkContactList.PhoneColumns)) + } + if sdkContactList.EmailColumns != nil { + _ = d.Set("email_columns", flattenSdkOutboundContactListContactEmailAddressColumnSlice(*sdkContactList.EmailColumns)) + } + if sdkContactList.PreviewModeColumnName != nil { + _ = d.Set("preview_mode_column_name", *sdkContactList.PreviewModeColumnName) + } + if sdkContactList.PreviewModeAcceptedValues != nil { + var acceptedValues []string + for _, val := range *sdkContactList.PreviewModeAcceptedValues { + acceptedValues = append(acceptedValues, val) + } + _ = d.Set("preview_mode_accepted_values", acceptedValues) + } + if sdkContactList.AttemptLimits != nil && sdkContactList.AttemptLimits.Id != nil { + _ = d.Set("attempt_limit_id", *sdkContactList.AttemptLimits.Id) + } + if sdkContactList.AutomaticTimeZoneMapping != nil { + _ = d.Set("automatic_time_zone_mapping", *sdkContactList.AutomaticTimeZoneMapping) + } + if sdkContactList.ZipCodeColumnName != nil { + _ = d.Set("zip_code_column_name", *sdkContactList.ZipCodeColumnName) + } + if sdkContactList.ColumnDataTypeSpecifications != nil { + _ = d.Set("column_data_type_specifications", flattenSdkOutboundContactListColumnDataTypeSpecifications(*sdkContactList.ColumnDataTypeSpecifications)) + } + + log.Printf("Read Outbound Contact List %s %s", d.Id(), *sdkContactList.Name) + return cc.CheckState(d) + }) +} + +func deleteOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlistProxy(sdkConfig) + + diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + log.Printf("Deleting Outbound Contact List") + resp, err := proxy.deleteOutboundContactlist(ctx, d.Id()) + if err != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Contact List %s error: %s", d.Id(), err), resp) + } + return resp, nil + }) + if diagErr != nil { + return diagErr + } + + return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError { + _, resp, err := proxy.getOutboundContactlistById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + // Outbound Contact List deleted + log.Printf("Deleted Outbound Contact List %s", d.Id()) + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Contact List %s | error: %s", d.Id(), err), resp)) + } + + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Contact List %s still exists", d.Id()), resp)) + }) +} diff --git a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_schema.go b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_schema.go new file mode 100644 index 000000000..39d09fe70 --- /dev/null +++ b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_schema.go @@ -0,0 +1,199 @@ +package outbound_contact_list + +import ( + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* +resource_genesycloud_outbound_contact_list_schema.go holds three functions within it: + +1. The resource schema definitions for the outbound_contact_list resource. +2. The datasource schema definitions for the outbound_contact_list datasource. +3. The resource exporter configuration for the outbound_contact_list exporter. +*/ + +var ( + outboundContactListContactPhoneNumberColumnResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + `column_name`: { + Description: `The name of the phone column.`, + Required: true, + Type: schema.TypeString, + }, + `type`: { + Description: `Indicates the type of the phone column. For example, 'cell' or 'home'.`, + Required: true, + Type: schema.TypeString, + }, + `callable_time_column`: { + Description: `A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true.`, + Optional: true, + Type: schema.TypeString, + }, + }, + } + + outboundContactListEmailColumnResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + `column_name`: { + Description: `The name of the email column.`, + Required: true, + Type: schema.TypeString, + }, + `type`: { + Description: `Indicates the type of the email column. For example, 'work' or 'personal'.`, + Required: true, + Type: schema.TypeString, + }, + `contactable_time_column`: { + Description: `A column that indicates the timezone to use for a given contact when checking contactable times.`, + Optional: true, + Type: schema.TypeString, + }, + }, + } + + outboundContactListColumnDataTypeSpecification = &schema.Resource{ + Schema: map[string]*schema.Schema{ + `column_name`: { + Description: `The column name of a column selected for dynamic queueing.`, + Required: true, + Type: schema.TypeString, + }, + `column_data_type`: { + Description: `The data type of the column selected for dynamic queueing (TEXT, NUMERIC or TIMESTAMP)`, + Optional: true, + Computed: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"TEXT", "NUMERIC", "TIMESTAMP"}, false), + }, + `min`: { + Description: `The minimum length of the numeric column selected for dynamic queueing.`, + Optional: true, + Type: schema.TypeInt, + }, + `max`: { + Description: `The maximum length of the numeric column selected for dynamic queueing.`, + Optional: true, + Type: schema.TypeInt, + }, + `max_length`: { + Description: `The maximum length of the text column selected for dynamic queueing.`, + Required: true, + Type: schema.TypeInt, + }, + }, + } +) + +func ResourceOutboundContactList() *schema.Resource { + return &schema.Resource{ + Description: `Genesys Cloud Outbound Contact List`, + + CreateContext: provider.CreateWithPooledClient(createOutboundContactList), + ReadContext: provider.ReadWithPooledClient(readOutboundContactList), + UpdateContext: provider.UpdateWithPooledClient(updateOutboundContactList), + DeleteContext: provider.DeleteWithPooledClient(deleteOutboundContactList), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + `name`: { + Description: `The name for the contact list.`, + Required: true, + Type: schema.TypeString, + }, + `division_id`: { + Description: `The division this entity belongs to.`, + Optional: true, + Computed: true, + Type: schema.TypeString, + }, + `column_names`: { + Description: `The names of the contact data columns. Changing the column_names attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`, + Required: true, + ForceNew: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + `phone_columns`: { + Description: `Indicates which columns are phone numbers. Changing the phone_columns attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID. Required if email_columns is empty`, + Optional: true, + ForceNew: true, + Type: schema.TypeSet, + Elem: outboundContactListContactPhoneNumberColumnResource, + }, + `email_columns`: { + Description: `Indicates which columns are email addresses. Changing the email_columns attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID. Required if phone_columns is empty`, + Optional: true, + ForceNew: true, + Type: schema.TypeSet, + Elem: outboundContactListEmailColumnResource, + }, + `preview_mode_column_name`: { + Description: `A column to check if a contact should always be dialed in preview mode.`, + Optional: true, + Type: schema.TypeString, + }, + `preview_mode_accepted_values`: { + Description: `The values in the previewModeColumnName column that indicate a contact should always be dialed in preview mode.`, + Optional: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + `attempt_limit_id`: { + Description: `Attempt Limit for this ContactList.`, + Optional: true, + Type: schema.TypeString, + }, + `automatic_time_zone_mapping`: { + Description: `Indicates if automatic time zone mapping is to be used for this ContactList. Changing the automatic_time_zone_mappings attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`, + Optional: true, + ForceNew: true, + Type: schema.TypeBool, + }, + `zip_code_column_name`: { + Description: `The name of contact list column containing the zip code for use with automatic time zone mapping. Only allowed if 'automaticTimeZoneMapping' is set to true. Changing the zip_code_column_name attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`, + Optional: true, + ForceNew: true, + Type: schema.TypeString, + }, + `column_data_type_specifications`: { + Description: `The settings of the columns selected for dynamic queueing. If updated, the contact list is dropped and recreated with a new ID`, + Optional: true, + ForceNew: true, + Type: schema.TypeList, + Elem: outboundContactListColumnDataTypeSpecification, + }, + }, + } +} + +func OutboundContactListExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundContactLists), + RefAttrs: map[string]*resourceExporter.RefAttrSettings{ + "attempt_limit_id": {RefType: "genesyscloud_outbound_attempt_limit"}, + "division_id": {RefType: "genesyscloud_auth_division"}, + }, + } +} + +func DataSourceOutboundContactList() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Genesys Cloud Outbound Contact Lists. Select a contact list by name.", + ReadContext: provider.ReadWithPooledClient(dataSourceOutboundContactListRead), + Schema: map[string]*schema.Schema{ + "name": { + Description: "Contact List name.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} diff --git a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_test.go b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_test.go new file mode 100644 index 000000000..037a23526 --- /dev/null +++ b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_test.go @@ -0,0 +1,364 @@ +package outbound_contact_list + +import ( + "fmt" + "strconv" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "testing" + + obAttemptLimit "terraform-provider-genesyscloud/genesyscloud/outbound_attempt_limit" + + "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/v133/platformclientv2" +) + +func TestAccResourceOutboundContactListBasic(t *testing.T) { + + t.Parallel() + var ( + resourceId = "contact-list" + name = "Test Contact List " + uuid.NewString() + previewModeColumnName = "Cell" + previewModeAcceptedValues = []string{strconv.Quote(previewModeColumnName)} + columnNames = []string{ + strconv.Quote("Cell"), + strconv.Quote("Home"), + strconv.Quote("Work"), + strconv.Quote("Personal"), + } + automaticTimeZoneMapping = util.FalseValue + attemptLimitResourceID = "attempt-limit" + attemptLimitDataSourceID = "attempt-limit-data" + attemptLimitName = "Test Attempt Limit " + uuid.NewString() + + nameUpdated = "Test Contact List " + uuid.NewString() + automaticTimeZoneMappingUpdated = util.TrueValue + zipCodeColumnName = "Zipcode" + columnNamesUpdated = append(columnNames, strconv.Quote(zipCodeColumnName)) + previewModeColumnNameUpdated = "Home" + previewModeAcceptedValuesUpdated = []string{strconv.Quote(previewModeColumnName), strconv.Quote(previewModeColumnNameUpdated)} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + Config: GenerateOutboundContactList( + resourceId, + name, + util.NullValue, + strconv.Quote(previewModeColumnName), + previewModeAcceptedValues, + columnNames, + automaticTimeZoneMapping, + util.NullValue, + util.NullValue, + GeneratePhoneColumnsBlock( + "Cell", + "cell", + strconv.Quote("Cell"), + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + strconv.Quote("Home"), + ), + GenerateEmailColumnsBlock( + "Work", + "work", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + util.NullValue, + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", name), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "0"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "4"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.callable_time_column", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.callable_time_column", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), + provider.TestDefaultHomeDivision(resourceName+"."+resourceId), + ), + }, + // Update + { + Config: GenerateOutboundContactList( + resourceId, + name, + util.NullValue, + strconv.Quote(previewModeColumnName), + previewModeAcceptedValuesUpdated, + columnNames, + automaticTimeZoneMapping, + util.NullValue, + util.NullValue, + GeneratePhoneColumnsBlock( + "Cell", + "cell", + strconv.Quote("Cell"), + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + strconv.Quote("Home"), + ), + GenerateEmailColumnsBlock( + "Work", + "work", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + util.NullValue, + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", name), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "0"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "4"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.callable_time_column", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.callable_time_column", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), + provider.TestDefaultHomeDivision(resourceName+"."+resourceId), + ), + }, + { + // Update (forcenew) + Config: GenerateOutboundContactList( + resourceId, + nameUpdated, + util.NullValue, + strconv.Quote(previewModeColumnNameUpdated), + previewModeAcceptedValuesUpdated, + columnNames, + automaticTimeZoneMapping, + util.NullValue, + util.NullValue, + GeneratePhoneColumnsBlock( + "Cell", + "cell", + strconv.Quote("Cell"), + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + strconv.Quote("Home"), + ), + GenerateEmailColumnsBlock( + "Work", + "work", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + util.NullValue, + ), + GeneratePhoneColumnsDataTypeSpecBlock( + strconv.Quote("Cell"), // columnName + strconv.Quote("TEXT"), // columnDataType + "1", // min + "11", // max + "10", // maxLength + ), + GeneratePhoneColumnsDataTypeSpecBlock( + strconv.Quote("Home"), // columnName + strconv.Quote("TEXT"), // columnDataType + util.NullValue, // min + util.NullValue, // max + "5", // maxLength + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", nameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "4"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.callable_time_column", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.callable_time_column", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "2"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_data_type", "TEXT"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.min", "1"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max", "11"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max_length", "10"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.1.column_data_type", "TEXT"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.1.max_length", "5"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), + provider.TestDefaultHomeDivision(resourceName+"."+resourceId), + ), + }, + { + Config: obAttemptLimit.GenerateAttemptLimitResource( + attemptLimitResourceID, + attemptLimitName, + "5", + "5", + "America/Chicago", + "TODAY", + ) + obAttemptLimit.GenerateOutboundAttemptLimitDataSource( + attemptLimitDataSourceID, + attemptLimitName, + "genesyscloud_outbound_attempt_limit."+attemptLimitResourceID, + ) + `data "genesyscloud_auth_division_home" "home" {}` + GenerateOutboundContactList( + resourceId, + nameUpdated, + "data.genesyscloud_auth_division_home.home.id", + strconv.Quote(previewModeColumnNameUpdated), + previewModeAcceptedValuesUpdated, + columnNamesUpdated, + automaticTimeZoneMappingUpdated, + strconv.Quote(zipCodeColumnName), + "genesyscloud_outbound_attempt_limit."+attemptLimitResourceID+".id", + GeneratePhoneColumnsBlock( + "Cell", + "cell", + util.NullValue, + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Work", + "work", + strconv.Quote(zipCodeColumnName), + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + strconv.Quote(zipCodeColumnName), + ), + GeneratePhoneColumnsDataTypeSpecBlock( + strconv.Quote("Cell"), // columnName + strconv.Quote("TEXT"), // columnDataType + "2", // min + "12", // max + "11", // maxLength + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", nameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "5"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", zipCodeColumnName), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "zip_code_column_name", zipCodeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.contactable_time_column", zipCodeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.contactable_time_column", zipCodeColumnName), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "1"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_data_type", "TEXT"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.min", "2"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max", "12"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max_length", "11"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMappingUpdated), + resource.TestCheckResourceAttrPair("data.genesyscloud_outbound_attempt_limit."+attemptLimitDataSourceID, "id", + resourceName+"."+resourceId, "attempt_limit_id"), + provider.TestDefaultHomeDivision(resourceName+"."+resourceId), + ), + }, + { + ResourceName: resourceName + "." + resourceId, + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testVerifyContactListDestroyed, + }) +} + +func testVerifyContactListDestroyed(state *terraform.State) error { + outboundAPI := platformclientv2.NewOutboundApi() + for _, rs := range state.RootModule().Resources { + if rs.Type != resourceName { + continue + } + contactList, resp, err := outboundAPI.GetOutboundContactlist(rs.Primary.ID, false, false) + if contactList != nil { + return fmt.Errorf("contact list (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Contact list not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("unexpected error: %s", err) + } + } + // Success. All contact lists destroyed + return nil +} diff --git a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_utils.go b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_utils.go new file mode 100644 index 000000000..4510ecb2a --- /dev/null +++ b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contact_list_utils.go @@ -0,0 +1,228 @@ +package outbound_contact_list + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +func buildSdkOutboundContactListContactPhoneNumberColumnSlice(contactPhoneNumberColumn *schema.Set) *[]platformclientv2.Contactphonenumbercolumn { + if contactPhoneNumberColumn == nil { + return nil + } + sdkContactPhoneNumberColumnSlice := make([]platformclientv2.Contactphonenumbercolumn, 0) + contactPhoneNumberColumnList := contactPhoneNumberColumn.List() + for _, configPhoneColumn := range contactPhoneNumberColumnList { + var sdkContactPhoneNumberColumn platformclientv2.Contactphonenumbercolumn + contactPhoneNumberColumnMap := configPhoneColumn.(map[string]interface{}) + if columnName := contactPhoneNumberColumnMap["column_name"].(string); columnName != "" { + sdkContactPhoneNumberColumn.ColumnName = &columnName + } + if varType := contactPhoneNumberColumnMap["type"].(string); varType != "" { + sdkContactPhoneNumberColumn.VarType = &varType + } + if callableTimeColumn := contactPhoneNumberColumnMap["callable_time_column"].(string); callableTimeColumn != "" { + sdkContactPhoneNumberColumn.CallableTimeColumn = &callableTimeColumn + } + + sdkContactPhoneNumberColumnSlice = append(sdkContactPhoneNumberColumnSlice, sdkContactPhoneNumberColumn) + } + return &sdkContactPhoneNumberColumnSlice +} + +func flattenSdkOutboundContactListContactPhoneNumberColumnSlice(contactPhoneNumberColumns []platformclientv2.Contactphonenumbercolumn) *schema.Set { + if len(contactPhoneNumberColumns) == 0 { + return nil + } + + contactPhoneNumberColumnSet := schema.NewSet(schema.HashResource(outboundContactListContactPhoneNumberColumnResource), []interface{}{}) + for _, contactPhoneNumberColumn := range contactPhoneNumberColumns { + contactPhoneNumberColumnMap := make(map[string]interface{}) + + if contactPhoneNumberColumn.ColumnName != nil { + contactPhoneNumberColumnMap["column_name"] = *contactPhoneNumberColumn.ColumnName + } + if contactPhoneNumberColumn.VarType != nil { + contactPhoneNumberColumnMap["type"] = *contactPhoneNumberColumn.VarType + } + if contactPhoneNumberColumn.CallableTimeColumn != nil { + contactPhoneNumberColumnMap["callable_time_column"] = *contactPhoneNumberColumn.CallableTimeColumn + } + + contactPhoneNumberColumnSet.Add(contactPhoneNumberColumnMap) + } + + return contactPhoneNumberColumnSet +} + +func buildSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumn *schema.Set) *[]platformclientv2.Emailcolumn { + if contactEmailAddressColumn == nil { + return nil + } + sdkContactEmailAddressColumnSlice := make([]platformclientv2.Emailcolumn, 0) + contactEmailAddressColumnList := contactEmailAddressColumn.List() + for _, configEmailColumn := range contactEmailAddressColumnList { + var sdkContactEmailAddressColumn platformclientv2.Emailcolumn + contactEmailAddressColumnMap := configEmailColumn.(map[string]interface{}) + if columnName := contactEmailAddressColumnMap["column_name"].(string); columnName != "" { + sdkContactEmailAddressColumn.ColumnName = &columnName + } + if varType := contactEmailAddressColumnMap["type"].(string); varType != "" { + sdkContactEmailAddressColumn.VarType = &varType + } + if contactableTimeColumn := contactEmailAddressColumnMap["contactable_time_column"].(string); contactableTimeColumn != "" { + sdkContactEmailAddressColumn.ContactableTimeColumn = &contactableTimeColumn + } + + sdkContactEmailAddressColumnSlice = append(sdkContactEmailAddressColumnSlice, sdkContactEmailAddressColumn) + } + return &sdkContactEmailAddressColumnSlice +} + +func flattenSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumns []platformclientv2.Emailcolumn) *schema.Set { + if len(contactEmailAddressColumns) == 0 { + return nil + } + + contactEmailAddressColumnSet := schema.NewSet(schema.HashResource(outboundContactListEmailColumnResource), []interface{}{}) + for _, contactEmailAddressColumn := range contactEmailAddressColumns { + contactEmailAddressColumnMap := make(map[string]interface{}) + + if contactEmailAddressColumn.ColumnName != nil { + contactEmailAddressColumnMap["column_name"] = *contactEmailAddressColumn.ColumnName + } + if contactEmailAddressColumn.VarType != nil { + contactEmailAddressColumnMap["type"] = *contactEmailAddressColumn.VarType + } + if contactEmailAddressColumn.ContactableTimeColumn != nil { + contactEmailAddressColumnMap["contactable_time_column"] = *contactEmailAddressColumn.ContactableTimeColumn + } + + contactEmailAddressColumnSet.Add(contactEmailAddressColumnMap) + } + + return contactEmailAddressColumnSet +} + +func buildSdkOutboundContactListColumnDataTypeSpecifications(columnDataTypeSpecifications []interface{}) *[]platformclientv2.Columndatatypespecification { + if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) < 1 { + return nil + } + + sdkColumnDataTypeSpecificationsSlice := make([]platformclientv2.Columndatatypespecification, 0) + + for _, spec := range columnDataTypeSpecifications { + if specMap, ok := spec.(map[string]interface{}); ok { + var sdkColumnDataTypeSpecification platformclientv2.Columndatatypespecification + if columnNameStr, ok := specMap["column_name"].(string); ok { + sdkColumnDataTypeSpecification.ColumnName = &columnNameStr + } + if columnDataTypeStr, ok := specMap["column_data_type"].(string); ok && columnDataTypeStr != "" { + sdkColumnDataTypeSpecification.ColumnDataType = &columnDataTypeStr + } + if minInt, ok := specMap["min"].(int); ok { + sdkColumnDataTypeSpecification.Min = &minInt + } + if maxInt, ok := specMap["max"].(int); ok { + sdkColumnDataTypeSpecification.Max = &maxInt + } + if maxLengthInt, ok := specMap["max_length"].(int); ok { + sdkColumnDataTypeSpecification.MaxLength = &maxLengthInt + } + sdkColumnDataTypeSpecificationsSlice = append(sdkColumnDataTypeSpecificationsSlice, sdkColumnDataTypeSpecification) + } + } + + return &sdkColumnDataTypeSpecificationsSlice +} + +func flattenSdkOutboundContactListColumnDataTypeSpecifications(columnDataTypeSpecifications []platformclientv2.Columndatatypespecification) []interface{} { + if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) == 0 { + return nil + } + + columnDataTypeSpecificationsSlice := make([]interface{}, 0) + + for _, s := range columnDataTypeSpecifications { + columnDataTypeSpecification := make(map[string]interface{}) + columnDataTypeSpecification["column_name"] = *s.ColumnName + + if s.ColumnDataType != nil { + columnDataTypeSpecification["column_data_type"] = *s.ColumnDataType + } + if s.Min != nil { + columnDataTypeSpecification["min"] = *s.Min + } + if s.Max != nil { + columnDataTypeSpecification["max"] = *s.Max + } + if s.MaxLength != nil { + columnDataTypeSpecification["max_length"] = *s.MaxLength + } + + columnDataTypeSpecificationsSlice = append(columnDataTypeSpecificationsSlice, columnDataTypeSpecification) + } + + return columnDataTypeSpecificationsSlice +} + +func GeneratePhoneColumnsBlock(columnName, columnType, callableTimeColumn string) string { + return fmt.Sprintf(` + phone_columns { + column_name = "%s" + type = "%s" + callable_time_column = %s + } +`, columnName, columnType, callableTimeColumn) +} + +func GenerateOutboundContactList( + resourceId string, + name string, + divisionId string, + previewModeColumnName string, + previewModeAcceptedValues []string, + columnNames []string, + automaticTimeZoneMapping string, + zipCodeColumnName string, + attemptLimitId string, + nestedBlocks ...string) string { + return fmt.Sprintf(` +resource "%s" "%s" { + name = "%s" + division_id = %s + preview_mode_column_name = %s + preview_mode_accepted_values = [%s] + column_names = [%s] + automatic_time_zone_mapping = %s + zip_code_column_name = %s + attempt_limit_id = %s + %s +} +`, resourceName, resourceId, name, divisionId, previewModeColumnName, strings.Join(previewModeAcceptedValues, ", "), + strings.Join(columnNames, ", "), automaticTimeZoneMapping, zipCodeColumnName, attemptLimitId, strings.Join(nestedBlocks, "\n")) +} + +func GeneratePhoneColumnsDataTypeSpecBlock(columnName, columnDataType, min, max, maxLength string) string { + return fmt.Sprintf(` + column_data_type_specifications { + column_name = %s + column_data_type = %s + min = %s + max = %s + max_length = %s + } + `, columnName, columnDataType, min, max, maxLength) +} + +func GenerateEmailColumnsBlock(columnName, columnType, contactableTimeColumn string) string { + return fmt.Sprintf(` + email_columns { + column_name = "%s" + type = "%s" + contactable_time_column = %s + } +`, columnName, columnType, contactableTimeColumn) +} diff --git a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contactlist.go b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contactlist.go deleted file mode 100644 index 3e0b05fdf..000000000 --- a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contactlist.go +++ /dev/null @@ -1,637 +0,0 @@ -package outbound_contact_list - -import ( - "context" - "fmt" - "log" - "strings" - "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/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" -) - -const ( - resourceName = "genesyscloud_outbound_contactlist" -) - -var ( - outboundContactListContactPhoneNumberColumnResource = &schema.Resource{ - Schema: map[string]*schema.Schema{ - `column_name`: { - Description: `The name of the phone column.`, - Required: true, - Type: schema.TypeString, - }, - `type`: { - Description: `Indicates the type of the phone column. For example, 'cell' or 'home'.`, - Required: true, - Type: schema.TypeString, - }, - `callable_time_column`: { - Description: `A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true.`, - Optional: true, - Type: schema.TypeString, - }, - }, - } - - outboundContactListEmailColumnResource = &schema.Resource{ - Schema: map[string]*schema.Schema{ - `column_name`: { - Description: `The name of the email column.`, - Required: true, - Type: schema.TypeString, - }, - `type`: { - Description: `Indicates the type of the email column. For example, 'work' or 'personal'.`, - Required: true, - Type: schema.TypeString, - }, - `contactable_time_column`: { - Description: `A column that indicates the timezone to use for a given contact when checking contactable times.`, - Optional: true, - Type: schema.TypeString, - }, - }, - } - - outboundContactListColumnDataTypeSpecification = &schema.Resource{ - Schema: map[string]*schema.Schema{ - `column_name`: { - Description: `The column name of a column selected for dynamic queueing.`, - Required: true, - Type: schema.TypeString, - }, - `column_data_type`: { - Description: `The data type of the column selected for dynamic queueing (TEXT, NUMERIC or TIMESTAMP)`, - Optional: true, - Computed: true, - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{"TEXT", "NUMERIC", "TIMESTAMP"}, false), - }, - `min`: { - Description: `The minimum length of the numeric column selected for dynamic queueing.`, - Optional: true, - Type: schema.TypeInt, - }, - `max`: { - Description: `The maximum length of the numeric column selected for dynamic queueing.`, - Optional: true, - Type: schema.TypeInt, - }, - `max_length`: { - Description: `The maximum length of the text column selected for dynamic queueing.`, - Required: true, - Type: schema.TypeInt, - }, - }, - } -) - -func getAllOutboundContactLists(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { - resources := make(resourceExporter.ResourceIDMetaMap) - outboundAPI := platformclientv2.NewOutboundApiWithConfig(clientConfig) - - for pageNum := 1; ; pageNum++ { - const pageSize = 100 - contactListConfigs, resp, getErr := outboundAPI.GetOutboundContactlists(false, false, pageSize, pageNum, true, "", "", []string{}, []string{}, "", "") - if getErr != nil { - return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of contact list configs error: %s", getErr), resp) - } - - if contactListConfigs.Entities == nil || len(*contactListConfigs.Entities) == 0 { - break - } - - for _, contactListConfig := range *contactListConfigs.Entities { - resources[*contactListConfig.Id] = &resourceExporter.ResourceMeta{Name: *contactListConfig.Name} - } - } - - return resources, nil -} - -func OutboundContactListExporter() *resourceExporter.ResourceExporter { - return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundContactLists), - RefAttrs: map[string]*resourceExporter.RefAttrSettings{ - "attempt_limit_id": {RefType: "genesyscloud_outbound_attempt_limit"}, - "division_id": {RefType: "genesyscloud_auth_division"}, - }, - } -} - -func ResourceOutboundContactList() *schema.Resource { - return &schema.Resource{ - Description: `Genesys Cloud Outbound Contact List`, - - CreateContext: provider.CreateWithPooledClient(createOutboundContactList), - ReadContext: provider.ReadWithPooledClient(readOutboundContactList), - UpdateContext: provider.UpdateWithPooledClient(updateOutboundContactList), - DeleteContext: provider.DeleteWithPooledClient(deleteOutboundContactList), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - SchemaVersion: 1, - Schema: map[string]*schema.Schema{ - `name`: { - Description: `The name for the contact list.`, - Required: true, - Type: schema.TypeString, - }, - `division_id`: { - Description: `The division this entity belongs to.`, - Optional: true, - Computed: true, - Type: schema.TypeString, - }, - `column_names`: { - Description: `The names of the contact data columns. Changing the column_names attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`, - Required: true, - ForceNew: true, - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - `phone_columns`: { - Description: `Indicates which columns are phone numbers. Changing the phone_columns attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID. Required if email_columns is empty`, - Optional: true, - ForceNew: true, - Type: schema.TypeSet, - Elem: outboundContactListContactPhoneNumberColumnResource, - }, - `email_columns`: { - Description: `Indicates which columns are email addresses. Changing the email_columns attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID. Required if phone_columns is empty`, - Optional: true, - ForceNew: true, - Type: schema.TypeSet, - Elem: outboundContactListEmailColumnResource, - }, - `preview_mode_column_name`: { - Description: `A column to check if a contact should always be dialed in preview mode.`, - Optional: true, - Type: schema.TypeString, - }, - `preview_mode_accepted_values`: { - Description: `The values in the previewModeColumnName column that indicate a contact should always be dialed in preview mode.`, - Optional: true, - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - `attempt_limit_id`: { - Description: `Attempt Limit for this ContactList.`, - Optional: true, - Type: schema.TypeString, - }, - `automatic_time_zone_mapping`: { - Description: `Indicates if automatic time zone mapping is to be used for this ContactList. Changing the automatic_time_zone_mappings attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`, - Optional: true, - ForceNew: true, - Type: schema.TypeBool, - }, - `zip_code_column_name`: { - Description: `The name of contact list column containing the zip code for use with automatic time zone mapping. Only allowed if 'automaticTimeZoneMapping' is set to true. Changing the zip_code_column_name attribute will cause the outboundcontact_list object to be dropped and recreated with a new ID`, - Optional: true, - ForceNew: true, - Type: schema.TypeString, - }, - `column_data_type_specifications`: { - Description: `The settings of the columns selected for dynamic queueing. If updated, the contact list is dropped and recreated with a new ID`, - Optional: true, - ForceNew: true, - Type: schema.TypeList, - Elem: outboundContactListColumnDataTypeSpecification, - }, - }, - } -} - -func createOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{})) - previewModeColumnName := d.Get("preview_mode_column_name").(string) - previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{})) - automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool) - zipCodeColumnName := d.Get("zip_code_column_name").(string) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig) - - sdkContactList := platformclientv2.Contactlist{ - Division: util.BuildSdkDomainEntityRef(d, "division_id"), - ColumnNames: &columnNames, - PhoneColumns: buildSdkOutboundContactListContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)), - EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)), - PreviewModeAcceptedValues: &previewModeAcceptedValues, - AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"), - AutomaticTimeZoneMapping: &automaticTimeZoneMapping, - ColumnDataTypeSpecifications: buildSdkOutboundContactListColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})), - } - - if name != "" { - sdkContactList.Name = &name - } - if previewModeColumnName != "" { - sdkContactList.PreviewModeColumnName = &previewModeColumnName - } - if zipCodeColumnName != "" { - sdkContactList.ZipCodeColumnName = &zipCodeColumnName - } - - log.Printf("Creating Outbound Contact List %s", name) - outboundContactList, resp, err := outboundApi.PostOutboundContactlists(sdkContactList) - if err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Contact List %s error: %s", name, err), resp) - } - - d.SetId(*outboundContactList.Id) - - log.Printf("Created Outbound Contact List %s %s", name, *outboundContactList.Id) - return readOutboundContactList(ctx, d, meta) -} - -func updateOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{})) - previewModeColumnName := d.Get("preview_mode_column_name").(string) - previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{})) - automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool) - zipCodeColumnName := d.Get("zip_code_column_name").(string) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig) - - sdkContactList := platformclientv2.Contactlist{ - Division: util.BuildSdkDomainEntityRef(d, "division_id"), - ColumnNames: &columnNames, - PhoneColumns: buildSdkOutboundContactListContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)), - EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)), - PreviewModeAcceptedValues: &previewModeAcceptedValues, - AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"), - AutomaticTimeZoneMapping: &automaticTimeZoneMapping, - ColumnDataTypeSpecifications: buildSdkOutboundContactListColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})), - } - - if name != "" { - sdkContactList.Name = &name - } - if previewModeColumnName != "" { - sdkContactList.PreviewModeColumnName = &previewModeColumnName - } - if zipCodeColumnName != "" { - sdkContactList.ZipCodeColumnName = &zipCodeColumnName - } - - log.Printf("Updating Outbound Contact List %s", name) - diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { - // Get current Outbound Contact list version - outboundContactList, resp, getErr := outboundApi.GetOutboundContactlist(d.Id(), false, false) - if getErr != nil { - return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to read Outbound Contact List %s error: %s", d.Id(), getErr), resp) - } - sdkContactList.Version = outboundContactList.Version - outboundContactList, resp, updateErr := outboundApi.PutOutboundContactlist(d.Id(), sdkContactList) - if updateErr != nil { - return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound contact list %s error: %s", name, updateErr), resp) - } - return nil, nil - }) - if diagErr != nil { - return diagErr - } - - log.Printf("Updated Outbound Contact List %s", name) - return readOutboundContactList(ctx, d, meta) -} - -func readOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig) - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundContactList(), constants.DefaultConsistencyChecks, resourceName) - - log.Printf("Reading Outbound Contact List %s", d.Id()) - - return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { - sdkContactList, resp, getErr := outboundApi.GetOutboundContactlist(d.Id(), false, false) - if getErr != nil { - if util.IsStatus404(resp) { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List %s | error: %s", d.Id(), getErr), resp)) - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List %s | error: %s", d.Id(), getErr), resp)) - } - - if sdkContactList.Name != nil { - _ = d.Set("name", *sdkContactList.Name) - } - if sdkContactList.Division != nil && sdkContactList.Division.Id != nil { - _ = d.Set("division_id", *sdkContactList.Division.Id) - } - if sdkContactList.ColumnNames != nil { - var columnNames []string - for _, name := range *sdkContactList.ColumnNames { - columnNames = append(columnNames, name) - } - _ = d.Set("column_names", columnNames) - } - if sdkContactList.PhoneColumns != nil { - _ = d.Set("phone_columns", flattenSdkOutboundContactListContactPhoneNumberColumnSlice(*sdkContactList.PhoneColumns)) - } - if sdkContactList.EmailColumns != nil { - _ = d.Set("email_columns", flattenSdkOutboundContactListContactEmailAddressColumnSlice(*sdkContactList.EmailColumns)) - } - if sdkContactList.PreviewModeColumnName != nil { - _ = d.Set("preview_mode_column_name", *sdkContactList.PreviewModeColumnName) - } - if sdkContactList.PreviewModeAcceptedValues != nil { - var acceptedValues []string - for _, val := range *sdkContactList.PreviewModeAcceptedValues { - acceptedValues = append(acceptedValues, val) - } - _ = d.Set("preview_mode_accepted_values", acceptedValues) - } - if sdkContactList.AttemptLimits != nil && sdkContactList.AttemptLimits.Id != nil { - _ = d.Set("attempt_limit_id", *sdkContactList.AttemptLimits.Id) - } - if sdkContactList.AutomaticTimeZoneMapping != nil { - _ = d.Set("automatic_time_zone_mapping", *sdkContactList.AutomaticTimeZoneMapping) - } - if sdkContactList.ZipCodeColumnName != nil { - _ = d.Set("zip_code_column_name", *sdkContactList.ZipCodeColumnName) - } - if sdkContactList.ColumnDataTypeSpecifications != nil { - _ = d.Set("column_data_type_specifications", flattenSdkOutboundContactListColumnDataTypeSpecifications(*sdkContactList.ColumnDataTypeSpecifications)) - } - - log.Printf("Read Outbound Contact List %s %s", d.Id(), *sdkContactList.Name) - return cc.CheckState(d) - }) -} - -func deleteOutboundContactList(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - outboundApi := platformclientv2.NewOutboundApiWithConfig(sdkConfig) - - diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) { - log.Printf("Deleting Outbound Contact List") - resp, err := outboundApi.DeleteOutboundContactlist(d.Id()) - if err != nil { - return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Contact List %s error: %s", d.Id(), err), resp) - } - return resp, nil - }) - if diagErr != nil { - return diagErr - } - - return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError { - _, resp, err := outboundApi.GetOutboundContactlist(d.Id(), false, false) - if err != nil { - if util.IsStatus404(resp) { - // Outbound Contact List deleted - log.Printf("Deleted Outbound Contact List %s", d.Id()) - return nil - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Contact List %s | error: %s", d.Id(), err), resp)) - } - - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Contact List %s still exists", d.Id()), resp)) - }) -} - -func buildSdkOutboundContactListContactPhoneNumberColumnSlice(contactPhoneNumberColumn *schema.Set) *[]platformclientv2.Contactphonenumbercolumn { - if contactPhoneNumberColumn == nil { - return nil - } - sdkContactPhoneNumberColumnSlice := make([]platformclientv2.Contactphonenumbercolumn, 0) - contactPhoneNumberColumnList := contactPhoneNumberColumn.List() - for _, configPhoneColumn := range contactPhoneNumberColumnList { - var sdkContactPhoneNumberColumn platformclientv2.Contactphonenumbercolumn - contactPhoneNumberColumnMap := configPhoneColumn.(map[string]interface{}) - if columnName := contactPhoneNumberColumnMap["column_name"].(string); columnName != "" { - sdkContactPhoneNumberColumn.ColumnName = &columnName - } - if varType := contactPhoneNumberColumnMap["type"].(string); varType != "" { - sdkContactPhoneNumberColumn.VarType = &varType - } - if callableTimeColumn := contactPhoneNumberColumnMap["callable_time_column"].(string); callableTimeColumn != "" { - sdkContactPhoneNumberColumn.CallableTimeColumn = &callableTimeColumn - } - - sdkContactPhoneNumberColumnSlice = append(sdkContactPhoneNumberColumnSlice, sdkContactPhoneNumberColumn) - } - return &sdkContactPhoneNumberColumnSlice -} - -func flattenSdkOutboundContactListContactPhoneNumberColumnSlice(contactPhoneNumberColumns []platformclientv2.Contactphonenumbercolumn) *schema.Set { - if len(contactPhoneNumberColumns) == 0 { - return nil - } - - contactPhoneNumberColumnSet := schema.NewSet(schema.HashResource(outboundContactListContactPhoneNumberColumnResource), []interface{}{}) - for _, contactPhoneNumberColumn := range contactPhoneNumberColumns { - contactPhoneNumberColumnMap := make(map[string]interface{}) - - if contactPhoneNumberColumn.ColumnName != nil { - contactPhoneNumberColumnMap["column_name"] = *contactPhoneNumberColumn.ColumnName - } - if contactPhoneNumberColumn.VarType != nil { - contactPhoneNumberColumnMap["type"] = *contactPhoneNumberColumn.VarType - } - if contactPhoneNumberColumn.CallableTimeColumn != nil { - contactPhoneNumberColumnMap["callable_time_column"] = *contactPhoneNumberColumn.CallableTimeColumn - } - - contactPhoneNumberColumnSet.Add(contactPhoneNumberColumnMap) - } - - return contactPhoneNumberColumnSet -} - -func buildSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumn *schema.Set) *[]platformclientv2.Emailcolumn { - if contactEmailAddressColumn == nil { - return nil - } - sdkContactEmailAddressColumnSlice := make([]platformclientv2.Emailcolumn, 0) - contactEmailAddressColumnList := contactEmailAddressColumn.List() - for _, configEmailColumn := range contactEmailAddressColumnList { - var sdkContactEmailAddressColumn platformclientv2.Emailcolumn - contactEmailAddressColumnMap := configEmailColumn.(map[string]interface{}) - if columnName := contactEmailAddressColumnMap["column_name"].(string); columnName != "" { - sdkContactEmailAddressColumn.ColumnName = &columnName - } - if varType := contactEmailAddressColumnMap["type"].(string); varType != "" { - sdkContactEmailAddressColumn.VarType = &varType - } - if contactableTimeColumn := contactEmailAddressColumnMap["contactable_time_column"].(string); contactableTimeColumn != "" { - sdkContactEmailAddressColumn.ContactableTimeColumn = &contactableTimeColumn - } - - sdkContactEmailAddressColumnSlice = append(sdkContactEmailAddressColumnSlice, sdkContactEmailAddressColumn) - } - return &sdkContactEmailAddressColumnSlice -} - -func flattenSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumns []platformclientv2.Emailcolumn) *schema.Set { - if len(contactEmailAddressColumns) == 0 { - return nil - } - - contactEmailAddressColumnSet := schema.NewSet(schema.HashResource(outboundContactListEmailColumnResource), []interface{}{}) - for _, contactEmailAddressColumn := range contactEmailAddressColumns { - contactEmailAddressColumnMap := make(map[string]interface{}) - - if contactEmailAddressColumn.ColumnName != nil { - contactEmailAddressColumnMap["column_name"] = *contactEmailAddressColumn.ColumnName - } - if contactEmailAddressColumn.VarType != nil { - contactEmailAddressColumnMap["type"] = *contactEmailAddressColumn.VarType - } - if contactEmailAddressColumn.ContactableTimeColumn != nil { - contactEmailAddressColumnMap["contactable_time_column"] = *contactEmailAddressColumn.ContactableTimeColumn - } - - contactEmailAddressColumnSet.Add(contactEmailAddressColumnMap) - } - - return contactEmailAddressColumnSet -} - -func buildSdkOutboundContactListColumnDataTypeSpecifications(columnDataTypeSpecifications []interface{}) *[]platformclientv2.Columndatatypespecification { - if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) < 1 { - return nil - } - - sdkColumnDataTypeSpecificationsSlice := make([]platformclientv2.Columndatatypespecification, 0) - - for _, spec := range columnDataTypeSpecifications { - if specMap, ok := spec.(map[string]interface{}); ok { - var sdkColumnDataTypeSpecification platformclientv2.Columndatatypespecification - if columnNameStr, ok := specMap["column_name"].(string); ok { - sdkColumnDataTypeSpecification.ColumnName = &columnNameStr - } - if columnDataTypeStr, ok := specMap["column_data_type"].(string); ok && columnDataTypeStr != "" { - sdkColumnDataTypeSpecification.ColumnDataType = &columnDataTypeStr - } - if minInt, ok := specMap["min"].(int); ok { - sdkColumnDataTypeSpecification.Min = &minInt - } - if maxInt, ok := specMap["max"].(int); ok { - sdkColumnDataTypeSpecification.Max = &maxInt - } - if maxLengthInt, ok := specMap["max_length"].(int); ok { - sdkColumnDataTypeSpecification.MaxLength = &maxLengthInt - } - sdkColumnDataTypeSpecificationsSlice = append(sdkColumnDataTypeSpecificationsSlice, sdkColumnDataTypeSpecification) - } - } - - return &sdkColumnDataTypeSpecificationsSlice -} - -func flattenSdkOutboundContactListColumnDataTypeSpecifications(columnDataTypeSpecifications []platformclientv2.Columndatatypespecification) []interface{} { - if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) == 0 { - return nil - } - - columnDataTypeSpecificationsSlice := make([]interface{}, 0) - - for _, s := range columnDataTypeSpecifications { - columnDataTypeSpecification := make(map[string]interface{}) - columnDataTypeSpecification["column_name"] = *s.ColumnName - - if s.ColumnDataType != nil { - columnDataTypeSpecification["column_data_type"] = *s.ColumnDataType - } - if s.Min != nil { - columnDataTypeSpecification["min"] = *s.Min - } - if s.Max != nil { - columnDataTypeSpecification["max"] = *s.Max - } - if s.MaxLength != nil { - columnDataTypeSpecification["max_length"] = *s.MaxLength - } - - columnDataTypeSpecificationsSlice = append(columnDataTypeSpecificationsSlice, columnDataTypeSpecification) - } - - return columnDataTypeSpecificationsSlice -} - -// type OutboundContactListInstance struct{ -// } - -// func (*OutboundContactListInstance) ResourceOutboundContactList() *schema.Resource { -// ResourceOutboundContactList() *schema.Resource -// } - -func GeneratePhoneColumnsBlock(columnName, columnType, callableTimeColumn string) string { - return fmt.Sprintf(` - phone_columns { - column_name = "%s" - type = "%s" - callable_time_column = %s - } -`, columnName, columnType, callableTimeColumn) -} - -func GenerateOutboundContactList( - resourceId string, - name string, - divisionId string, - previewModeColumnName string, - previewModeAcceptedValues []string, - columnNames []string, - automaticTimeZoneMapping string, - zipCodeColumnName string, - attemptLimitId string, - nestedBlocks ...string) string { - return fmt.Sprintf(` -resource "genesyscloud_outbound_contact_list" "%s" { - name = "%s" - division_id = %s - preview_mode_column_name = %s - preview_mode_accepted_values = [%s] - column_names = [%s] - automatic_time_zone_mapping = %s - zip_code_column_name = %s - attempt_limit_id = %s - %s -} -`, resourceId, name, divisionId, previewModeColumnName, strings.Join(previewModeAcceptedValues, ", "), - strings.Join(columnNames, ", "), automaticTimeZoneMapping, zipCodeColumnName, attemptLimitId, strings.Join(nestedBlocks, "\n")) -} - -func GeneratePhoneColumnsDataTypeSpecBlock(columnName, columnDataType, min, max, maxLength string) string { - return fmt.Sprintf(` - column_data_type_specifications { - column_name = %s - column_data_type = %s - min = %s - max = %s - max_length = %s - } - `, columnName, columnDataType, min, max, maxLength) -} - -func GenerateEmailColumnsBlock(columnName, columnType, contactableTimeColumn string) string { - return fmt.Sprintf(` - email_columns { - column_name = "%s" - type = "%s" - contactable_time_column = %s - } -`, columnName, columnType, contactableTimeColumn) -} diff --git a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contactlist_test.go b/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contactlist_test.go deleted file mode 100644 index a07bfdedf..000000000 --- a/genesyscloud/outbound_contact_list/resource_genesyscloud_outbound_contactlist_test.go +++ /dev/null @@ -1,364 +0,0 @@ -package outbound_contact_list - -import ( - "fmt" - "strconv" - "terraform-provider-genesyscloud/genesyscloud/provider" - "terraform-provider-genesyscloud/genesyscloud/util" - "testing" - - obAttemptLimit "terraform-provider-genesyscloud/genesyscloud/outbound_attempt_limit" - - "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/v133/platformclientv2" -) - -func TestAccResourceOutboundContactListBasic(t *testing.T) { - - t.Parallel() - var ( - resourceId = "contact-list" - name = "Test Contact List " + uuid.NewString() - previewModeColumnName = "Cell" - previewModeAcceptedValues = []string{strconv.Quote(previewModeColumnName)} - columnNames = []string{ - strconv.Quote("Cell"), - strconv.Quote("Home"), - strconv.Quote("Work"), - strconv.Quote("Personal"), - } - automaticTimeZoneMapping = util.FalseValue - attemptLimitResourceID = "attempt-limit" - attemptLimitDataSourceID = "attempt-limit-data" - attemptLimitName = "Test Attempt Limit " + uuid.NewString() - - nameUpdated = "Test Contact List " + uuid.NewString() - automaticTimeZoneMappingUpdated = util.TrueValue - zipCodeColumnName = "Zipcode" - columnNamesUpdated = append(columnNames, strconv.Quote(zipCodeColumnName)) - previewModeColumnNameUpdated = "Home" - previewModeAcceptedValuesUpdated = []string{strconv.Quote(previewModeColumnName), strconv.Quote(previewModeColumnNameUpdated)} - ) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { util.TestAccPreCheck(t) }, - ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), - Steps: []resource.TestStep{ - { - Config: GenerateOutboundContactList( - resourceId, - name, - util.NullValue, - strconv.Quote(previewModeColumnName), - previewModeAcceptedValues, - columnNames, - automaticTimeZoneMapping, - util.NullValue, - util.NullValue, - GeneratePhoneColumnsBlock( - "Cell", - "cell", - strconv.Quote("Cell"), - ), - GeneratePhoneColumnsBlock( - "Home", - "home", - strconv.Quote("Home"), - ), - GenerateEmailColumnsBlock( - "Work", - "work", - util.NullValue, - ), - GenerateEmailColumnsBlock( - "Personal", - "personal", - util.NullValue, - ), - ), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "name", name), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.#", "0"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_names.#", "4"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Cell"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Home"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Work"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.column_name", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.type", "cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.callable_time_column", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.column_name", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.type", "home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.callable_time_column", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.column_name", "Work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.type", "work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.column_name", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.type", "personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_column_name", previewModeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), - provider.TestDefaultHomeDivision("genesyscloud_outbound_contact_list."+resourceId), - ), - }, - // Update - { - Config: GenerateOutboundContactList( - resourceId, - name, - util.NullValue, - strconv.Quote(previewModeColumnName), - previewModeAcceptedValuesUpdated, - columnNames, - automaticTimeZoneMapping, - util.NullValue, - util.NullValue, - GeneratePhoneColumnsBlock( - "Cell", - "cell", - strconv.Quote("Cell"), - ), - GeneratePhoneColumnsBlock( - "Home", - "home", - strconv.Quote("Home"), - ), - GenerateEmailColumnsBlock( - "Work", - "work", - util.NullValue, - ), - GenerateEmailColumnsBlock( - "Personal", - "personal", - util.NullValue, - ), - ), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "name", name), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.#", "0"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_names.#", "4"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Cell"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Home"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Work"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.column_name", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.type", "cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.callable_time_column", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.column_name", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.type", "home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.callable_time_column", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.column_name", "Work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.type", "work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.column_name", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.type", "personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_column_name", previewModeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), - provider.TestDefaultHomeDivision("genesyscloud_outbound_contact_list."+resourceId), - ), - }, - { - // Update (forcenew) - Config: GenerateOutboundContactList( - resourceId, - nameUpdated, - util.NullValue, - strconv.Quote(previewModeColumnNameUpdated), - previewModeAcceptedValuesUpdated, - columnNames, - automaticTimeZoneMapping, - util.NullValue, - util.NullValue, - GeneratePhoneColumnsBlock( - "Cell", - "cell", - strconv.Quote("Cell"), - ), - GeneratePhoneColumnsBlock( - "Home", - "home", - strconv.Quote("Home"), - ), - GenerateEmailColumnsBlock( - "Work", - "work", - util.NullValue, - ), - GenerateEmailColumnsBlock( - "Personal", - "personal", - util.NullValue, - ), - GeneratePhoneColumnsDataTypeSpecBlock( - strconv.Quote("Cell"), // columnName - strconv.Quote("TEXT"), // columnDataType - "1", // min - "11", // max - "10", // maxLength - ), - GeneratePhoneColumnsDataTypeSpecBlock( - strconv.Quote("Home"), // columnName - strconv.Quote("TEXT"), // columnDataType - util.NullValue, // min - util.NullValue, // max - "5", // maxLength - ), - ), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "name", nameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_names.#", "4"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Cell"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Home"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Work"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.column_name", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.type", "cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.callable_time_column", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.column_name", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.type", "home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.callable_time_column", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.column_name", "Work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.type", "work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.column_name", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.type", "personal"), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.#", "2"), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.column_name", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.column_data_type", "TEXT"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.min", "1"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.max", "11"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.max_length", "10"), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.1.column_name", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.1.column_data_type", "TEXT"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.1.max_length", "5"), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_column_name", previewModeColumnNameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), - provider.TestDefaultHomeDivision("genesyscloud_outbound_contact_list."+resourceId), - ), - }, - { - Config: obAttemptLimit.GenerateAttemptLimitResource( - attemptLimitResourceID, - attemptLimitName, - "5", - "5", - "America/Chicago", - "TODAY", - ) + obAttemptLimit.GenerateOutboundAttemptLimitDataSource( - attemptLimitDataSourceID, - attemptLimitName, - "genesyscloud_outbound_attempt_limit."+attemptLimitResourceID, - ) + `data "genesyscloud_auth_division_home" "home" {}` + GenerateOutboundContactList( - resourceId, - nameUpdated, - "data.genesyscloud_auth_division_home.home.id", - strconv.Quote(previewModeColumnNameUpdated), - previewModeAcceptedValuesUpdated, - columnNamesUpdated, - automaticTimeZoneMappingUpdated, - strconv.Quote(zipCodeColumnName), - "genesyscloud_outbound_attempt_limit."+attemptLimitResourceID+".id", - GeneratePhoneColumnsBlock( - "Cell", - "cell", - util.NullValue, - ), - GeneratePhoneColumnsBlock( - "Home", - "home", - util.NullValue, - ), - GenerateEmailColumnsBlock( - "Work", - "work", - strconv.Quote(zipCodeColumnName), - ), - GenerateEmailColumnsBlock( - "Personal", - "personal", - strconv.Quote(zipCodeColumnName), - ), - GeneratePhoneColumnsDataTypeSpecBlock( - strconv.Quote("Cell"), // columnName - strconv.Quote("TEXT"), // columnDataType - "2", // min - "12", // max - "11", // maxLength - ), - ), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "name", nameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_names.#", "5"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Cell"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Home"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Work"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", "Personal"), - util.ValidateStringInArray("genesyscloud_outbound_contact_list."+resourceId, "column_names", zipCodeColumnName), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "zip_code_column_name", zipCodeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.column_name", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.0.type", "cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.column_name", "Personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.type", "personal"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.0.contactable_time_column", zipCodeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.column_name", "Home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "phone_columns.1.type", "home"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.column_name", "Work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.type", "work"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "email_columns.1.contactable_time_column", zipCodeColumnName), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.#", "1"), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.column_name", "Cell"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.column_data_type", "TEXT"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.min", "2"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.max", "12"), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "column_data_type_specifications.0.max_length", "11"), - - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_column_name", previewModeColumnNameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), - resource.TestCheckResourceAttr("genesyscloud_outbound_contact_list."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMappingUpdated), - resource.TestCheckResourceAttrPair("data.genesyscloud_outbound_attempt_limit."+attemptLimitDataSourceID, "id", - "genesyscloud_outbound_contact_list."+resourceId, "attempt_limit_id"), - provider.TestDefaultHomeDivision("genesyscloud_outbound_contact_list."+resourceId), - ), - }, - { - ResourceName: "genesyscloud_outbound_contact_list." + resourceId, - ImportState: true, - ImportStateVerify: true, - }, - }, - CheckDestroy: testVerifyContactListDestroyed, - }) -} - -func testVerifyContactListDestroyed(state *terraform.State) error { - outboundAPI := platformclientv2.NewOutboundApi() - for _, rs := range state.RootModule().Resources { - if rs.Type != "genesyscloud_outbound_contact_list" { - continue - } - contactList, resp, err := outboundAPI.GetOutboundContactlist(rs.Primary.ID, false, false) - if contactList != nil { - return fmt.Errorf("contact list (%s) still exists", rs.Primary.ID) - } else if util.IsStatus404(resp) { - // Contact list not found as expected - continue - } else { - // Unexpected error - return fmt.Errorf("unexpected error: %s", err) - } - } - // Success. All contact lists destroyed - return nil -} diff --git a/genesyscloud/outbound_contact_list_template/data_source_genesyscloud_outbound_contact_list_template.go b/genesyscloud/outbound_contact_list_template/data_source_genesyscloud_outbound_contact_list_template.go new file mode 100644 index 000000000..653462c7c --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/data_source_genesyscloud_outbound_contact_list_template.go @@ -0,0 +1,36 @@ +package outbound_contact_list_template + +import ( + "context" + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceOutboundContactListTemplateRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sdkConfig := m.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlisttemplateProxy(sdkConfig) + name := d.Get("name").(string) + + return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + const pageNum = 1 + const pageSize = 100 + contactListTemplateId, retryable, resp, getErr := proxy.getOutboundContactlisttemplateIdByName(ctx, name) + + if getErr != nil && !retryable { + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting contact list template %s | error: %s", name, getErr), resp)) + } + if retryable { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no contact list templates found with name %s", name), resp)) + } + + d.SetId(contactListTemplateId) + return nil + }) +} diff --git a/genesyscloud/outbound_contact_list_template/data_source_genesyscloud_outbound_contact_list_template_test.go b/genesyscloud/outbound_contact_list_template/data_source_genesyscloud_outbound_contact_list_template_test.go new file mode 100644 index 000000000..fbf8ff335 --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/data_source_genesyscloud_outbound_contact_list_template_test.go @@ -0,0 +1,61 @@ +package outbound_contact_list_template + +import ( + "fmt" + "strconv" + "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 TestAccDataSourceOutboundContactListTemplate(t *testing.T) { + + var ( + resourceId = "contact_list_template" + dataSourceId = "contact_list_template_data" + contactListName = "Contact List Template" + uuid.NewString() + ) + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + Config: GenerateOutboundContactListTemplate( + resourceId, + contactListName, + util.NullValue, // previewModeColumnName + []string{}, // previewModeAcceptedValues + []string{strconv.Quote("Cell")}, + util.FalseValue, // automaticTimeZoneMapping + util.NullValue, // zipCodeColumnName + util.NullValue, // attemptLimitId + GeneratePhoneColumnsBlock( + "Cell", + "cell", + util.NullValue, + ), + ) + generateOutboundContactListTemplateDataSource( + dataSourceId, + contactListName, + resourceName+"."+resourceId, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data."+resourceName+"."+dataSourceId, "id", + resourceName+"."+resourceId, "id"), + ), + }, + }, + }) +} + +func generateOutboundContactListTemplateDataSource(id string, name string, dependsOn string) string { + return fmt.Sprintf(` +data "%s" "%s" { + name = "%s" + depends_on = [%s] +} +`, resourceName, id, name, dependsOn) +} diff --git a/genesyscloud/outbound_contact_list_template/genesyscloud_outbound_contact_list_template_proxy.go b/genesyscloud/outbound_contact_list_template/genesyscloud_outbound_contact_list_template_proxy.go new file mode 100644 index 000000000..5e1d171c1 --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/genesyscloud_outbound_contact_list_template_proxy.go @@ -0,0 +1,185 @@ +package outbound_contact_list_template + +import ( + "context" + "fmt" + "log" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +/* +The genesyscloud_outbound_contact_list_template_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 *outboundContactlisttemplateProxy + +// Type definitions for each func on our proxy so we can easily mock them out later +type createOutboundContactlisttemplateFunc func(ctx context.Context, p *outboundContactlisttemplateProxy, Contactlisttemplate *platformclientv2.Contactlisttemplate) (*platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) +type getAllOutboundContactlisttemplateFunc func(ctx context.Context, p *outboundContactlisttemplateProxy, name string) (*[]platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) +type getOutboundContactlisttemplateIdByNameFunc func(ctx context.Context, p *outboundContactlisttemplateProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) +type getOutboundContactlisttemplateByIdFunc func(ctx context.Context, p *outboundContactlisttemplateProxy, id string) (Contactlisttemplate *platformclientv2.Contactlisttemplate, response *platformclientv2.APIResponse, err error) +type updateOutboundContactlisttemplateFunc func(ctx context.Context, p *outboundContactlisttemplateProxy, id string, Contactlisttemplate *platformclientv2.Contactlisttemplate) (*platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) +type deleteOutboundContactlisttemplateFunc func(ctx context.Context, p *outboundContactlisttemplateProxy, id string) (response *platformclientv2.APIResponse, err error) + +// outboundContactlisttemplateProxy contains all of the methods that call genesys cloud APIs. +type outboundContactlisttemplateProxy struct { + clientConfig *platformclientv2.Configuration + outboundApi *platformclientv2.OutboundApi + createOutboundContactlisttemplateAttr createOutboundContactlisttemplateFunc + getAllOutboundContactlisttemplateAttr getAllOutboundContactlisttemplateFunc + getOutboundContactlisttemplateIdByNameAttr getOutboundContactlisttemplateIdByNameFunc + getOutboundContactlisttemplateByIdAttr getOutboundContactlisttemplateByIdFunc + updateOutboundContactlisttemplateAttr updateOutboundContactlisttemplateFunc + deleteOutboundContactlisttemplateAttr deleteOutboundContactlisttemplateFunc +} + +// newOutboundContactlisttemplateProxy initializes the outbound Contactlisttemplate proxy with all of the data needed to communicate with Genesys Cloud +func newOutboundContactlisttemplateProxy(clientConfig *platformclientv2.Configuration) *outboundContactlisttemplateProxy { + api := platformclientv2.NewOutboundApiWithConfig(clientConfig) + return &outboundContactlisttemplateProxy{ + clientConfig: clientConfig, + outboundApi: api, + createOutboundContactlisttemplateAttr: createOutboundContactlisttemplateFn, + getAllOutboundContactlisttemplateAttr: getAllOutboundContactlisttemplateFn, + getOutboundContactlisttemplateIdByNameAttr: getOutboundContactlisttemplateIdByNameFn, + getOutboundContactlisttemplateByIdAttr: getOutboundContactlisttemplateByIdFn, + updateOutboundContactlisttemplateAttr: updateOutboundContactlisttemplateFn, + deleteOutboundContactlisttemplateAttr: deleteOutboundContactlisttemplateFn, + } +} + +// getOutboundContactlisttemplateProxy 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 getOutboundContactlisttemplateProxy(clientConfig *platformclientv2.Configuration) *outboundContactlisttemplateProxy { + if internalProxy == nil { + internalProxy = newOutboundContactlisttemplateProxy(clientConfig) + } + return internalProxy +} + +// createOutboundContactlisttemplate creates a Genesys Cloud outbound Contactlisttemplate +func (p *outboundContactlisttemplateProxy) createOutboundContactlisttemplate(ctx context.Context, outboundContactlisttemplate *platformclientv2.Contactlisttemplate) (*platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) { + return p.createOutboundContactlisttemplateAttr(ctx, p, outboundContactlisttemplate) +} + +// getOutboundContactlisttemplate retrieves all Genesys Cloud outbound Contactlisttemplate +func (p *outboundContactlisttemplateProxy) getAllOutboundContactlisttemplate(ctx context.Context) (*[]platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) { + return p.getAllOutboundContactlisttemplateAttr(ctx, p, "") +} + +// getOutboundContactlisttemplateIdByName returns a single Genesys Cloud outbound Contactlisttemplate by a name +func (p *outboundContactlisttemplateProxy) getOutboundContactlisttemplateIdByName(ctx context.Context, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) { + return p.getOutboundContactlisttemplateIdByNameAttr(ctx, p, name) +} + +// getOutboundContactlisttemplateById returns a single Genesys Cloud outbound Contactlisttemplate by Id +func (p *outboundContactlisttemplateProxy) getOutboundContactlisttemplateById(ctx context.Context, id string) (outboundContactlisttemplate *platformclientv2.Contactlisttemplate, response *platformclientv2.APIResponse, err error) { + return p.getOutboundContactlisttemplateByIdAttr(ctx, p, id) +} + +// updateOutboundContactlisttemplate updates a Genesys Cloud outbound Contactlisttemplate +func (p *outboundContactlisttemplateProxy) updateOutboundContactlisttemplate(ctx context.Context, id string, outboundContactlisttemplate *platformclientv2.Contactlisttemplate) (*platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) { + return p.updateOutboundContactlisttemplateAttr(ctx, p, id, outboundContactlisttemplate) +} + +// deleteOutboundContactlisttemplate deletes a Genesys Cloud outbound Contactlisttemplate by Id +func (p *outboundContactlisttemplateProxy) deleteOutboundContactlisttemplate(ctx context.Context, id string) (response *platformclientv2.APIResponse, err error) { + return p.deleteOutboundContactlisttemplateAttr(ctx, p, id) +} + +// createOutboundContactlisttemplateFn is an implementation function for creating a Genesys Cloud outbound Contactlisttemplate +func createOutboundContactlisttemplateFn(ctx context.Context, p *outboundContactlisttemplateProxy, outboundContactlisttemplate *platformclientv2.Contactlisttemplate) (*platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) { + Contactlisttemplate, resp, err := p.outboundApi.PostOutboundContactlisttemplates(*outboundContactlisttemplate) + if err != nil { + return nil, resp, err + } + return Contactlisttemplate, resp, nil +} + +// getAllOutboundContactlisttemplateFn is the implementation for retrieving all outbound Contactlisttemplate in Genesys Cloud +func getAllOutboundContactlisttemplateFn(ctx context.Context, p *outboundContactlisttemplateProxy, name string) (*[]platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) { + var allContactlisttemplates []platformclientv2.Contactlisttemplate + const pageSize = 100 + + Contactlisttemplates, resp, err := p.outboundApi.GetOutboundContactlisttemplates(pageSize, 1, true, "", name, "", "") + if err != nil { + return nil, resp, fmt.Errorf("failed to get page of contact list template: %v", err) + } + + if Contactlisttemplates.Entities == nil || len(*Contactlisttemplates.Entities) == 0 { + return &allContactlisttemplates, resp, nil + } + + for _, Contactlisttemplate := range *Contactlisttemplates.Entities { + allContactlisttemplates = append(allContactlisttemplates, Contactlisttemplate) + } + + for pageNum := 2; pageNum <= *Contactlisttemplates.PageCount; pageNum++ { + Contactlisttemplates, resp, err := p.outboundApi.GetOutboundContactlisttemplates(pageSize, pageNum, true, "", name, "", "") + if err != nil { + return nil, resp, fmt.Errorf("failed to get page of contact list template: %v", err) + } + + if Contactlisttemplates.Entities == nil || len(*Contactlisttemplates.Entities) == 0 { + break + } + + for _, Contactlisttemplate := range *Contactlisttemplates.Entities { + allContactlisttemplates = append(allContactlisttemplates, Contactlisttemplate) + } + } + + return &allContactlisttemplates, resp, nil +} + +// getOutboundContactlisttemplateIdByNameFn is an implementation of the function to get a Genesys Cloud outbound Contactlisttemplate by name +func getOutboundContactlisttemplateIdByNameFn(ctx context.Context, p *outboundContactlisttemplateProxy, name string) (id string, retryable bool, response *platformclientv2.APIResponse, err error) { + Contactlisttemplates, resp, err := getAllOutboundContactlisttemplateFn(ctx, p, name) + if err != nil { + return "", false, resp, fmt.Errorf("error searching outbound contact list template %s: %s", name, err) + } + + var list platformclientv2.Contactlisttemplate + for _, Contactlisttemplate := range *Contactlisttemplates { + if *Contactlisttemplate.Name == name { + log.Printf("Retrieved the contact list template id %s by name %s", *Contactlisttemplate.Id, name) + list = Contactlisttemplate + return *list.Id, false, resp, nil + } + } + + return "", true, resp, nil +} + +// getOutboundContactlisttemplateByIdFn is an implementation of the function to get a Genesys Cloud outbound Contactlisttemplate by Id +func getOutboundContactlisttemplateByIdFn(ctx context.Context, p *outboundContactlisttemplateProxy, id string) (outboundContactlisttemplate *platformclientv2.Contactlisttemplate, response *platformclientv2.APIResponse, err error) { + Contactlisttemplate, resp, err := p.outboundApi.GetOutboundContactlisttemplate(id) + if err != nil { + return nil, resp, err + } + return Contactlisttemplate, resp, nil +} + +// updateOutboundContactlisttemplateFn is an implementation of the function to update a Genesys Cloud outbound Contactlisttemplate +func updateOutboundContactlisttemplateFn(ctx context.Context, p *outboundContactlisttemplateProxy, id string, outboundContactlisttemplate *platformclientv2.Contactlisttemplate) (*platformclientv2.Contactlisttemplate, *platformclientv2.APIResponse, error) { + Contactlisttemplate, resp, err := p.outboundApi.GetOutboundContactlisttemplate(id) + if err != nil { + return nil, resp, err + } + + outboundContactlisttemplate.Version = Contactlisttemplate.Version + outboundContactlisttemplate, resp, updateErr := p.outboundApi.PutOutboundContactlisttemplate(id, *outboundContactlisttemplate) + if updateErr != nil { + return nil, resp, updateErr + } + return outboundContactlisttemplate, resp, nil +} + +// deleteOutboundContactlisttemplateFn is an implementation function for deleting a Genesys Cloud outbound Contactlisttemplate +func deleteOutboundContactlisttemplateFn(ctx context.Context, p *outboundContactlisttemplateProxy, id string) (response *platformclientv2.APIResponse, err error) { + return p.outboundApi.DeleteOutboundContactlisttemplate(id) +} diff --git a/genesyscloud/outbound_contact_list_template/outbound_contact_list_template_resource_init.go b/genesyscloud/outbound_contact_list_template/outbound_contact_list_template_resource_init.go new file mode 100644 index 000000000..7448784b5 --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/outbound_contact_list_template_resource_init.go @@ -0,0 +1,15 @@ +package outbound_contact_list_template + +import ( + registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" +) + +const ( + resourceName = "genesyscloud_outbound_contact_list_template" +) + +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterDataSource(resourceName, DataSourceOutboundContactListTemplate()) + regInstance.RegisterResource(resourceName, ResourceOutboundContactListTemplate()) + regInstance.RegisterExporter(resourceName, OutboundContactListTemplateExporter()) +} diff --git a/genesyscloud/outbound_contact_list_template/outbound_contact_list_template_resource_init_test.go b/genesyscloud/outbound_contact_list_template/outbound_contact_list_template_resource_init_test.go new file mode 100644 index 000000000..8c9f4b6d1 --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/outbound_contact_list_template_resource_init_test.go @@ -0,0 +1,51 @@ +package outbound_contact_list_template + +import ( + "sync" + obAttemptLimit "terraform-provider-genesyscloud/genesyscloud/outbound_attempt_limit" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var providerDataSources map[string]*schema.Resource +var providerResources map[string]*schema.Resource + +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() + regInstance.registerTestDataSources() +} + +type registerTestInstance struct { + resourceMapMutex sync.RWMutex + datasourceMapMutex sync.RWMutex +} + +func (r *registerTestInstance) registerTestResources() { + r.resourceMapMutex.Lock() + defer r.resourceMapMutex.Unlock() + + providerResources[resourceName] = ResourceOutboundContactListTemplate() + providerResources["genesyscloud_outbound_attempt_limit"] = obAttemptLimit.ResourceOutboundAttemptLimit() +} + +func (r *registerTestInstance) registerTestDataSources() { + r.datasourceMapMutex.Lock() + defer r.datasourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceOutboundContactListTemplate() + providerDataSources["genesyscloud_outbound_attempt_limit"] = obAttemptLimit.DataSourceOutboundAttemptLimit() +} + +func TestMain(m *testing.M) { + // Run setup function before starting the test suite + initTestResources() + + // Run the test suite for outbound ruleset + m.Run() +} diff --git a/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template.go b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template.go new file mode 100644 index 000000000..5fd6b380d --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template.go @@ -0,0 +1,220 @@ +package outbound_contact_list_template + +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/v133/platformclientv2" +) + +func getAllOutboundContactListTemplates(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + resources := make(resourceExporter.ResourceIDMetaMap) + proxy := getOutboundContactlisttemplateProxy(clientConfig) + + contactListTemplates, resp, getErr := proxy.getAllOutboundContactlisttemplate(ctx) + if getErr != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get contact list templates error: %s", getErr), resp) + } + + for _, contactListTemplate := range *contactListTemplates { + resources[*contactListTemplate.Id] = &resourceExporter.ResourceMeta{Name: *contactListTemplate.Name} + } + + return resources, nil +} + +func createOutboundContactListTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{})) + previewModeColumnName := d.Get("preview_mode_column_name").(string) + previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{})) + automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool) + zipCodeColumnName := d.Get("zip_code_column_name").(string) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlisttemplateProxy(sdkConfig) + + sdkContactListTemplate := platformclientv2.Contactlisttemplate{ + ColumnNames: &columnNames, + PhoneColumns: buildSdkOutboundContactListTemplateContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)), + EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)), + PreviewModeAcceptedValues: &previewModeAcceptedValues, + AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"), + AutomaticTimeZoneMapping: &automaticTimeZoneMapping, + ColumnDataTypeSpecifications: buildSdkOutboundContactListTemplateColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})), + } + + if name != "" { + sdkContactListTemplate.Name = &name + } + if previewModeColumnName != "" { + sdkContactListTemplate.PreviewModeColumnName = &previewModeColumnName + } + if zipCodeColumnName != "" { + sdkContactListTemplate.ZipCodeColumnName = &zipCodeColumnName + } + + log.Printf("Creating Outbound Contact List Template %s", name) + outboundContactListTemplate, resp, err := proxy.createOutboundContactlisttemplate(ctx, &sdkContactListTemplate) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create Outbound Contact List Template %s error: %s", name, err), resp) + } + + d.SetId(*outboundContactListTemplate.Id) + + log.Printf("Created Outbound Contact List Template %s %s", name, *outboundContactListTemplate.Id) + return readOutboundContactListTemplate(ctx, d, meta) +} + +func updateOutboundContactListTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + columnNames := lists.InterfaceListToStrings(d.Get("column_names").([]interface{})) + previewModeColumnName := d.Get("preview_mode_column_name").(string) + previewModeAcceptedValues := lists.InterfaceListToStrings(d.Get("preview_mode_accepted_values").([]interface{})) + automaticTimeZoneMapping := d.Get("automatic_time_zone_mapping").(bool) + zipCodeColumnName := d.Get("zip_code_column_name").(string) + + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlisttemplateProxy(sdkConfig) + + sdkContactListTemplate := platformclientv2.Contactlisttemplate{ + ColumnNames: &columnNames, + PhoneColumns: buildSdkOutboundContactListTemplateContactPhoneNumberColumnSlice(d.Get("phone_columns").(*schema.Set)), + EmailColumns: buildSdkOutboundContactListContactEmailAddressColumnSlice(d.Get("email_columns").(*schema.Set)), + PreviewModeAcceptedValues: &previewModeAcceptedValues, + AttemptLimits: util.BuildSdkDomainEntityRef(d, "attempt_limit_id"), + AutomaticTimeZoneMapping: &automaticTimeZoneMapping, + ColumnDataTypeSpecifications: buildSdkOutboundContactListTemplateColumnDataTypeSpecifications(d.Get("column_data_type_specifications").([]interface{})), + } + + if name != "" { + sdkContactListTemplate.Name = &name + } + if previewModeColumnName != "" { + sdkContactListTemplate.PreviewModeColumnName = &previewModeColumnName + } + if zipCodeColumnName != "" { + sdkContactListTemplate.ZipCodeColumnName = &zipCodeColumnName + } + + log.Printf("Updating Outbound Contact List Template %s", name) + diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + + _, resp, updateErr := proxy.updateOutboundContactlisttemplate(ctx, d.Id(), &sdkContactListTemplate) + if updateErr != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Outbound Contact List Template %s error: %s", name, updateErr), resp) + } + return nil, nil + }) + if diagErr != nil { + return diagErr + } + + log.Printf("Updated Outbound Contact List Template %s", name) + return readOutboundContactListTemplate(ctx, d, meta) +} + +func readOutboundContactListTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlisttemplateProxy(sdkConfig) + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceOutboundContactListTemplate(), constants.DefaultConsistencyChecks, resourceName) + + log.Printf("Reading Outbound Contact List Template %s", d.Id()) + + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + sdkContactListTemplate, resp, getErr := proxy.getOutboundContactlisttemplateById(ctx, d.Id()) + if getErr != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List Template %s | error: %s", d.Id(), getErr), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read Outbound Contact List Template %s | error: %s", d.Id(), getErr), resp)) + } + + if sdkContactListTemplate.Name != nil { + _ = d.Set("name", *sdkContactListTemplate.Name) + } + if sdkContactListTemplate.ColumnNames != nil { + var columnNames []string + for _, name := range *sdkContactListTemplate.ColumnNames { + columnNames = append(columnNames, name) + } + _ = d.Set("column_names", columnNames) + } + if sdkContactListTemplate.PhoneColumns != nil { + _ = d.Set("phone_columns", flattenSdkOutboundContactListTemplateContactPhoneNumberColumnSlice(*sdkContactListTemplate.PhoneColumns)) + } + if sdkContactListTemplate.EmailColumns != nil { + _ = d.Set("email_columns", flattenSdkOutboundContactListTemplateContactEmailAddressColumnSlice(*sdkContactListTemplate.EmailColumns)) + } + if sdkContactListTemplate.PreviewModeColumnName != nil { + _ = d.Set("preview_mode_column_name", *sdkContactListTemplate.PreviewModeColumnName) + } + if sdkContactListTemplate.PreviewModeAcceptedValues != nil { + var acceptedValues []string + for _, val := range *sdkContactListTemplate.PreviewModeAcceptedValues { + acceptedValues = append(acceptedValues, val) + } + _ = d.Set("preview_mode_accepted_values", acceptedValues) + } + if sdkContactListTemplate.AttemptLimits != nil && sdkContactListTemplate.AttemptLimits.Id != nil { + _ = d.Set("attempt_limit_id", *sdkContactListTemplate.AttemptLimits.Id) + } + if sdkContactListTemplate.AutomaticTimeZoneMapping != nil { + _ = d.Set("automatic_time_zone_mapping", *sdkContactListTemplate.AutomaticTimeZoneMapping) + } + if sdkContactListTemplate.ZipCodeColumnName != nil { + _ = d.Set("zip_code_column_name", *sdkContactListTemplate.ZipCodeColumnName) + } + if sdkContactListTemplate.ColumnDataTypeSpecifications != nil { + _ = d.Set("column_data_type_specifications", flattenSdkOutboundContactListTemplateColumnDataTypeSpecifications(*sdkContactListTemplate.ColumnDataTypeSpecifications)) + } + + log.Printf("Read Outbound Contact List Template %s %s", d.Id(), *sdkContactListTemplate.Name) + return cc.CheckState(d) + }) +} + +func deleteOutboundContactListTemplate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getOutboundContactlisttemplateProxy(sdkConfig) + + diagErr := util.RetryWhen(util.IsStatus400, func() (*platformclientv2.APIResponse, diag.Diagnostics) { + log.Printf("Deleting Outbound Contact List Template") + resp, err := proxy.deleteOutboundContactlisttemplate(ctx, d.Id()) + if err != nil { + return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete Outbound Contact List Template %s error: %s", d.Id(), err), resp) + } + return resp, nil + }) + if diagErr != nil { + return diagErr + } + + return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError { + _, resp, err := proxy.getOutboundContactlisttemplateById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + // Outbound Contact List Template deleted + log.Printf("Deleted Outbound Contact List Template %s", d.Id()) + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Outbound Contact List Template %s | error: %s", d.Id(), err), resp)) + } + + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Outbound Contact List Template %s still exists", d.Id()), resp)) + }) +} diff --git a/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_schema.go b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_schema.go new file mode 100644 index 000000000..8496e838e --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_schema.go @@ -0,0 +1,194 @@ +package outbound_contact_list_template + +import ( + "terraform-provider-genesyscloud/genesyscloud/provider" + + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* +resource_genesycloud_outbound_contact_list_template_schema.go holds three functions within it: + +1. The resource schema definitions for the outbound_contact_list_template resource. +2. The datasource schema definitions for the outbound_contact_list_template datasource. +3. The resource exporter configuration for the outbound_contact_list_template exporter. +*/ + +var ( + outboundContactListTemplateContactPhoneNumberColumnResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + `column_name`: { + Description: `The name of the phone column.`, + Required: true, + Type: schema.TypeString, + }, + `type`: { + Description: `Indicates the type of the phone column. For example, 'cell' or 'home'.`, + Required: true, + Type: schema.TypeString, + }, + `callable_time_column`: { + Description: `A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true.`, + Optional: true, + Type: schema.TypeString, + }, + }, + } + + outboundContactListTemplateEmailColumnResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + `column_name`: { + Description: `The name of the email column.`, + Required: true, + Type: schema.TypeString, + }, + `type`: { + Description: `Indicates the type of the email column. For example, 'work' or 'personal'.`, + Required: true, + Type: schema.TypeString, + }, + `contactable_time_column`: { + Description: `A column that indicates the timezone to use for a given contact when checking contactable times.`, + Optional: true, + Type: schema.TypeString, + }, + }, + } + + outboundContactListTemplateColumnDataTypeSpecification = &schema.Resource{ + Schema: map[string]*schema.Schema{ + `column_name`: { + Description: `The column name of a column selected for dynamic queueing.`, + Required: true, + Type: schema.TypeString, + }, + `column_data_type`: { + Description: `The data type of the column selected for dynamic queueing (TEXT, NUMERIC or TIMESTAMP)`, + Optional: true, + Computed: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"TEXT", "NUMERIC", "TIMESTAMP"}, false), + }, + `min`: { + Description: `The minimum length of the numeric column selected for dynamic queueing.`, + Optional: true, + Type: schema.TypeInt, + }, + `max`: { + Description: `The maximum length of the numeric column selected for dynamic queueing.`, + Optional: true, + Type: schema.TypeInt, + }, + `max_length`: { + Description: `The maximum length of the text column selected for dynamic queueing.`, + Required: true, + Type: schema.TypeInt, + }, + }, + } +) + +func ResourceOutboundContactListTemplate() *schema.Resource { + return &schema.Resource{ + Description: `Genesys Cloud Outbound Contact List Template`, + + CreateContext: provider.CreateWithPooledClient(createOutboundContactListTemplate), + ReadContext: provider.ReadWithPooledClient(readOutboundContactListTemplate), + UpdateContext: provider.UpdateWithPooledClient(updateOutboundContactListTemplate), + DeleteContext: provider.DeleteWithPooledClient(deleteOutboundContactListTemplate), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + `name`: { + Description: `The name for the contact list template.`, + Required: true, + Type: schema.TypeString, + }, + `column_names`: { + Description: `The names of the contact template data columns. Changing the column_names attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID`, + Required: true, + ForceNew: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + `phone_columns`: { + Description: `Indicates which columns are phone numbers. Changing the phone_columns attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID. Required if email_columns is empty`, + Optional: true, + ForceNew: true, + Type: schema.TypeSet, + Elem: outboundContactListTemplateContactPhoneNumberColumnResource, + }, + `email_columns`: { + Description: `Indicates which columns are email addresses. Changing the email_columns attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID. Required if phone_columns is empty`, + Optional: true, + ForceNew: true, + Type: schema.TypeSet, + Elem: outboundContactListTemplateEmailColumnResource, + }, + `preview_mode_column_name`: { + Description: `A column to check if a contact should always be dialed in preview mode.`, + Optional: true, + Type: schema.TypeString, + }, + `preview_mode_accepted_values`: { + Description: `The values in the preview_mode_column_name column that indicate a contact should always be dialed in preview mode.`, + Optional: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + `attempt_limit_id`: { + Description: `Attempt Limit for this Contact List Template.`, + Optional: true, + Type: schema.TypeString, + }, + `automatic_time_zone_mapping`: { + Description: `Indicates if automatic time zone mapping is to be used for this Contact List Template. Changing the automatic_time_zone_mappings attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID`, + Optional: true, + ForceNew: true, + Type: schema.TypeBool, + }, + `zip_code_column_name`: { + Description: `The name of contact list column containing the zip code for use with automatic time zone mapping. Only allowed if 'automatic_time_zone_mapping' is set to true. Changing the zip_code_column_name attribute will cause the outbound_contact_list_template object to be dropped and recreated with a new ID`, + Optional: true, + ForceNew: true, + Type: schema.TypeString, + }, + `column_data_type_specifications`: { + Description: `The settings of the columns selected for dynamic queueing. If updated, the contact list template is dropped and recreated with a new ID`, + Optional: true, + ForceNew: true, + Type: schema.TypeList, + Elem: outboundContactListTemplateColumnDataTypeSpecification, + }, + }, + } +} + +func OutboundContactListTemplateExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllOutboundContactListTemplates), + RefAttrs: map[string]*resourceExporter.RefAttrSettings{ + "attempt_limit_id": {RefType: "genesyscloud_outbound_attempt_limit"}, + "division_id": {RefType: "genesyscloud_auth_division"}, + }, + } +} + +func DataSourceOutboundContactListTemplate() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Genesys Cloud Outbound Contact Lists Templates. Select a contact list template by name.", + ReadContext: provider.ReadWithPooledClient(dataSourceOutboundContactListTemplateRead), + Schema: map[string]*schema.Schema{ + "name": { + Description: "Contact List Template name.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} diff --git a/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_test.go b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_test.go new file mode 100644 index 000000000..71f30ea96 --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_test.go @@ -0,0 +1,356 @@ +package outbound_contact_list_template + +import ( + "fmt" + "strconv" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "testing" + + obAttemptLimit "terraform-provider-genesyscloud/genesyscloud/outbound_attempt_limit" + + "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/v133/platformclientv2" +) + +func TestAccResourceOutboundContactListTemplateBasic(t *testing.T) { + + t.Parallel() + var ( + resourceId = "contact-list-template" + name = "Test Contact List Template" + uuid.NewString() + previewModeColumnName = "Cell" + previewModeAcceptedValues = []string{strconv.Quote(previewModeColumnName)} + columnNames = []string{ + strconv.Quote("Cell"), + strconv.Quote("Home"), + strconv.Quote("Work"), + strconv.Quote("Personal"), + } + automaticTimeZoneMapping = util.FalseValue + attemptLimitResourceID = "attempt-limit" + attemptLimitDataSourceID = "attempt-limit-data" + attemptLimitName = "Test Attempt Limit " + uuid.NewString() + + nameUpdated = "Test Contact List Template" + uuid.NewString() + automaticTimeZoneMappingUpdated = util.TrueValue + zipCodeColumnName = "Zipcode" + columnNamesUpdated = append(columnNames, strconv.Quote(zipCodeColumnName)) + previewModeColumnNameUpdated = "Home" + previewModeAcceptedValuesUpdated = []string{strconv.Quote(previewModeColumnName), strconv.Quote(previewModeColumnNameUpdated)} + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + Config: GenerateOutboundContactListTemplate( + resourceId, + name, + strconv.Quote(previewModeColumnName), + previewModeAcceptedValues, + columnNames, + automaticTimeZoneMapping, + util.NullValue, + util.NullValue, + GeneratePhoneColumnsBlock( + "Cell", + "cell", + strconv.Quote("Cell"), + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + strconv.Quote("Home"), + ), + GenerateEmailColumnsBlock( + "Work", + "work", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + util.NullValue, + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", name), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "0"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "4"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.callable_time_column", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.callable_time_column", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), + ), + }, + // Update + { + Config: GenerateOutboundContactListTemplate( + resourceId, + name, + strconv.Quote(previewModeColumnName), + previewModeAcceptedValuesUpdated, + columnNames, + automaticTimeZoneMapping, + util.NullValue, + util.NullValue, + GeneratePhoneColumnsBlock( + "Cell", + "cell", + strconv.Quote("Cell"), + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + strconv.Quote("Home"), + ), + GenerateEmailColumnsBlock( + "Work", + "work", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + util.NullValue, + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", name), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "0"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "4"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.callable_time_column", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.callable_time_column", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), + ), + }, + { + // Update (forcenew) + Config: GenerateOutboundContactListTemplate( + resourceId, + nameUpdated, + strconv.Quote(previewModeColumnNameUpdated), + previewModeAcceptedValuesUpdated, + columnNames, + automaticTimeZoneMapping, + util.NullValue, + util.NullValue, + GeneratePhoneColumnsBlock( + "Cell", + "cell", + strconv.Quote("Cell"), + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + strconv.Quote("Home"), + ), + GenerateEmailColumnsBlock( + "Work", + "work", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + util.NullValue, + ), + GeneratePhoneColumnsDataTypeSpecBlock( + strconv.Quote("Cell"), // columnName + strconv.Quote("TEXT"), // columnDataType + "1", // min + "11", // max + "10", // maxLength + ), + GeneratePhoneColumnsDataTypeSpecBlock( + strconv.Quote("Home"), // columnName + strconv.Quote("TEXT"), // columnDataType + util.NullValue, // min + util.NullValue, // max + "5", // maxLength + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", nameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "4"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.callable_time_column", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.callable_time_column", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "2"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_data_type", "TEXT"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.min", "1"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max", "11"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max_length", "10"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.1.column_data_type", "TEXT"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.1.max_length", "5"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMapping), + ), + }, + { + Config: obAttemptLimit.GenerateAttemptLimitResource( + attemptLimitResourceID, + attemptLimitName, + "5", + "5", + "America/Chicago", + "TODAY", + ) + obAttemptLimit.GenerateOutboundAttemptLimitDataSource( + attemptLimitDataSourceID, + attemptLimitName, + "genesyscloud_outbound_attempt_limit."+attemptLimitResourceID, + ) + GenerateOutboundContactListTemplate( + resourceId, + nameUpdated, + strconv.Quote(previewModeColumnNameUpdated), + previewModeAcceptedValuesUpdated, + columnNamesUpdated, + automaticTimeZoneMappingUpdated, + strconv.Quote(zipCodeColumnName), + "genesyscloud_outbound_attempt_limit."+attemptLimitResourceID+".id", + GeneratePhoneColumnsBlock( + "Cell", + "cell", + util.NullValue, + ), + GeneratePhoneColumnsBlock( + "Home", + "home", + util.NullValue, + ), + GenerateEmailColumnsBlock( + "Work", + "work", + strconv.Quote(zipCodeColumnName), + ), + GenerateEmailColumnsBlock( + "Personal", + "personal", + strconv.Quote(zipCodeColumnName), + ), + GeneratePhoneColumnsDataTypeSpecBlock( + strconv.Quote("Cell"), // columnName + strconv.Quote("TEXT"), // columnDataType + "2", // min + "12", // max + "11", // maxLength + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "name", nameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_names.#", "5"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Cell"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Home"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Work"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", "Personal"), + util.ValidateStringInArray(resourceName+"."+resourceId, "column_names", zipCodeColumnName), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "zip_code_column_name", zipCodeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.0.type", "cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.column_name", "Personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.type", "personal"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.0.contactable_time_column", zipCodeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.column_name", "Home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "phone_columns.1.type", "home"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.column_name", "Work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.type", "work"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "email_columns.1.contactable_time_column", zipCodeColumnName), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.#", "1"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_name", "Cell"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.column_data_type", "TEXT"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.min", "2"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max", "12"), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "column_data_type_specifications.0.max_length", "11"), + + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_column_name", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.0", previewModeColumnName), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "preview_mode_accepted_values.1", previewModeColumnNameUpdated), + resource.TestCheckResourceAttr(resourceName+"."+resourceId, "automatic_time_zone_mapping", automaticTimeZoneMappingUpdated), + resource.TestCheckResourceAttrPair("data.genesyscloud_outbound_attempt_limit."+attemptLimitDataSourceID, "id", + resourceName+"."+resourceId, "attempt_limit_id"), + ), + }, + { + ResourceName: resourceName + "." + resourceId, + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testVerifyContactListTemplateDestroyed, + }) +} + +func testVerifyContactListTemplateDestroyed(state *terraform.State) error { + outboundAPI := platformclientv2.NewOutboundApi() + for _, rs := range state.RootModule().Resources { + if rs.Type != resourceName { + continue + } + contactList, resp, err := outboundAPI.GetOutboundContactlisttemplate(rs.Primary.ID) + if contactList != nil { + return fmt.Errorf("contact list template (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Contact list template not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("unexpected error: %s", err) + } + } + // Success. All contact lists template destroyed + return nil +} diff --git a/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_utils.go b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_utils.go new file mode 100644 index 000000000..4e02e407e --- /dev/null +++ b/genesyscloud/outbound_contact_list_template/resource_genesyscloud_outbound_contact_list_template_utils.go @@ -0,0 +1,233 @@ +package outbound_contact_list_template + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +func buildSdkOutboundContactListTemplateContactPhoneNumberColumnSlice(contactPhoneNumberColumn *schema.Set) *[]platformclientv2.Contactphonenumbercolumn { + if contactPhoneNumberColumn == nil { + return nil + } + sdkContactPhoneNumberColumnSlice := make([]platformclientv2.Contactphonenumbercolumn, 0) + contactPhoneNumberColumnList := contactPhoneNumberColumn.List() + for _, configPhoneColumn := range contactPhoneNumberColumnList { + var sdkContactPhoneNumberColumn platformclientv2.Contactphonenumbercolumn + contactPhoneNumberColumnMap := configPhoneColumn.(map[string]interface{}) + if columnName := contactPhoneNumberColumnMap["column_name"].(string); columnName != "" { + sdkContactPhoneNumberColumn.ColumnName = &columnName + } + if varType := contactPhoneNumberColumnMap["type"].(string); varType != "" { + sdkContactPhoneNumberColumn.VarType = &varType + } + if callableTimeColumn := contactPhoneNumberColumnMap["callable_time_column"].(string); callableTimeColumn != "" { + sdkContactPhoneNumberColumn.CallableTimeColumn = &callableTimeColumn + } + + sdkContactPhoneNumberColumnSlice = append(sdkContactPhoneNumberColumnSlice, sdkContactPhoneNumberColumn) + } + return &sdkContactPhoneNumberColumnSlice +} + +func flattenSdkOutboundContactListTemplateContactPhoneNumberColumnSlice(contactPhoneNumberColumns []platformclientv2.Contactphonenumbercolumn) *schema.Set { + if len(contactPhoneNumberColumns) == 0 { + return nil + } + + contactPhoneNumberColumnSet := schema.NewSet(schema.HashResource(outboundContactListTemplateContactPhoneNumberColumnResource), []interface{}{}) + for _, contactPhoneNumberColumn := range contactPhoneNumberColumns { + contactPhoneNumberColumnMap := make(map[string]interface{}) + + if contactPhoneNumberColumn.ColumnName != nil { + contactPhoneNumberColumnMap["column_name"] = *contactPhoneNumberColumn.ColumnName + } + if contactPhoneNumberColumn.VarType != nil { + contactPhoneNumberColumnMap["type"] = *contactPhoneNumberColumn.VarType + } + if contactPhoneNumberColumn.CallableTimeColumn != nil { + contactPhoneNumberColumnMap["callable_time_column"] = *contactPhoneNumberColumn.CallableTimeColumn + } + + contactPhoneNumberColumnSet.Add(contactPhoneNumberColumnMap) + } + + return contactPhoneNumberColumnSet +} + +func buildSdkOutboundContactListContactEmailAddressColumnSlice(contactEmailAddressColumn *schema.Set) *[]platformclientv2.Emailcolumn { + if contactEmailAddressColumn == nil { + return nil + } + sdkContactEmailAddressColumnSlice := make([]platformclientv2.Emailcolumn, 0) + contactEmailAddressColumnList := contactEmailAddressColumn.List() + for _, configEmailColumn := range contactEmailAddressColumnList { + var sdkContactEmailAddressColumn platformclientv2.Emailcolumn + contactEmailAddressColumnMap := configEmailColumn.(map[string]interface{}) + if columnName := contactEmailAddressColumnMap["column_name"].(string); columnName != "" { + sdkContactEmailAddressColumn.ColumnName = &columnName + } + if varType := contactEmailAddressColumnMap["type"].(string); varType != "" { + sdkContactEmailAddressColumn.VarType = &varType + } + if contactableTimeColumn := contactEmailAddressColumnMap["contactable_time_column"].(string); contactableTimeColumn != "" { + sdkContactEmailAddressColumn.ContactableTimeColumn = &contactableTimeColumn + } + + sdkContactEmailAddressColumnSlice = append(sdkContactEmailAddressColumnSlice, sdkContactEmailAddressColumn) + } + return &sdkContactEmailAddressColumnSlice +} + +func flattenSdkOutboundContactListTemplateContactEmailAddressColumnSlice(contactEmailAddressColumns []platformclientv2.Emailcolumn) *schema.Set { + if len(contactEmailAddressColumns) == 0 { + return nil + } + + contactEmailAddressColumnSet := schema.NewSet(schema.HashResource(outboundContactListTemplateEmailColumnResource), []interface{}{}) + for _, contactEmailAddressColumn := range contactEmailAddressColumns { + contactEmailAddressColumnMap := make(map[string]interface{}) + + if contactEmailAddressColumn.ColumnName != nil { + contactEmailAddressColumnMap["column_name"] = *contactEmailAddressColumn.ColumnName + } + if contactEmailAddressColumn.VarType != nil { + contactEmailAddressColumnMap["type"] = *contactEmailAddressColumn.VarType + } + if contactEmailAddressColumn.ContactableTimeColumn != nil { + contactEmailAddressColumnMap["contactable_time_column"] = *contactEmailAddressColumn.ContactableTimeColumn + } + + contactEmailAddressColumnSet.Add(contactEmailAddressColumnMap) + } + + return contactEmailAddressColumnSet +} + +func buildSdkOutboundContactListTemplateColumnDataTypeSpecifications(columnDataTypeSpecifications []interface{}) *[]platformclientv2.Columndatatypespecification { + if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) < 1 { + return nil + } + + sdkColumnDataTypeSpecificationsSlice := make([]platformclientv2.Columndatatypespecification, 0) + + for _, spec := range columnDataTypeSpecifications { + if specMap, ok := spec.(map[string]interface{}); ok { + var sdkColumnDataTypeSpecification platformclientv2.Columndatatypespecification + if columnNameStr, ok := specMap["column_name"].(string); ok { + sdkColumnDataTypeSpecification.ColumnName = &columnNameStr + } + if columnDataTypeStr, ok := specMap["column_data_type"].(string); ok && columnDataTypeStr != "" { + sdkColumnDataTypeSpecification.ColumnDataType = &columnDataTypeStr + } + if minInt, ok := specMap["min"].(int); ok { + sdkColumnDataTypeSpecification.Min = &minInt + } + if maxInt, ok := specMap["max"].(int); ok { + sdkColumnDataTypeSpecification.Max = &maxInt + } + if maxLengthInt, ok := specMap["max_length"].(int); ok { + sdkColumnDataTypeSpecification.MaxLength = &maxLengthInt + } + sdkColumnDataTypeSpecificationsSlice = append(sdkColumnDataTypeSpecificationsSlice, sdkColumnDataTypeSpecification) + } + } + + return &sdkColumnDataTypeSpecificationsSlice +} + +func flattenSdkOutboundContactListTemplateColumnDataTypeSpecifications(columnDataTypeSpecifications []platformclientv2.Columndatatypespecification) []interface{} { + if columnDataTypeSpecifications == nil || len(columnDataTypeSpecifications) == 0 { + return nil + } + + columnDataTypeSpecificationsSlice := make([]interface{}, 0) + + for _, s := range columnDataTypeSpecifications { + columnDataTypeSpecification := make(map[string]interface{}) + columnDataTypeSpecification["column_name"] = *s.ColumnName + + if s.ColumnDataType != nil { + columnDataTypeSpecification["column_data_type"] = *s.ColumnDataType + } + if s.Min != nil { + columnDataTypeSpecification["min"] = *s.Min + } + if s.Max != nil { + columnDataTypeSpecification["max"] = *s.Max + } + if s.MaxLength != nil { + columnDataTypeSpecification["max_length"] = *s.MaxLength + } + + columnDataTypeSpecificationsSlice = append(columnDataTypeSpecificationsSlice, columnDataTypeSpecification) + } + + return columnDataTypeSpecificationsSlice +} + +// type OutboundContactListInstance struct{ +// } + +// func (*OutboundContactListInstance) ResourceOutboundContactList() *schema.Resource { +// ResourceOutboundContactList() *schema.Resource +// } + +func GeneratePhoneColumnsBlock(columnName, columnType, callableTimeColumn string) string { + return fmt.Sprintf(` + phone_columns { + column_name = "%s" + type = "%s" + callable_time_column = %s + } +`, columnName, columnType, callableTimeColumn) +} + +func GenerateOutboundContactListTemplate( + resourceId string, + name string, + previewModeColumnName string, + previewModeAcceptedValues []string, + columnNames []string, + automaticTimeZoneMapping string, + zipCodeColumnName string, + attemptLimitId string, + nestedBlocks ...string) string { + return fmt.Sprintf(` +resource "%s" "%s" { + name = "%s" + preview_mode_column_name = %s + preview_mode_accepted_values = [%s] + column_names = [%s] + automatic_time_zone_mapping = %s + zip_code_column_name = %s + attempt_limit_id = %s + %s +} +`, resourceName, resourceId, name, previewModeColumnName, strings.Join(previewModeAcceptedValues, ", "), + strings.Join(columnNames, ", "), automaticTimeZoneMapping, zipCodeColumnName, attemptLimitId, strings.Join(nestedBlocks, "\n")) +} + +func GeneratePhoneColumnsDataTypeSpecBlock(columnName, columnDataType, min, max, maxLength string) string { + return fmt.Sprintf(` + column_data_type_specifications { + column_name = %s + column_data_type = %s + min = %s + max = %s + max_length = %s + } + `, columnName, columnDataType, min, max, maxLength) +} + +func GenerateEmailColumnsBlock(columnName, columnType, contactableTimeColumn string) string { + return fmt.Sprintf(` + email_columns { + column_name = "%s" + type = "%s" + contactable_time_column = %s + } +`, columnName, columnType, contactableTimeColumn) +} diff --git a/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter.go b/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter.go index ec48254e9..0ab00b849 100644 --- a/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter.go +++ b/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter.go @@ -76,7 +76,15 @@ func readOutboundContactlistfilter(ctx context.Context, d *schema.ResourceData, } resourcedata.SetNillableValue(d, "name", sdkContactListFilter.Name) - resourcedata.SetNillableReference(d, "contact_list_id", sdkContactListFilter.ContactList) + + switch { + case *sdkContactListFilter.SourceType == "ContactList": + resourcedata.SetNillableReference(d, "contact_list_id", sdkContactListFilter.ContactList) + case *sdkContactListFilter.SourceType == "ContactListTemplate": + resourcedata.SetNillableReference(d, "contact_list_template_id", sdkContactListFilter.ContactListTemplate) + default: + } + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "clauses", sdkContactListFilter.Clauses, flattenContactListFilterClauses) resourcedata.SetNillableValue(d, "filter_type", sdkContactListFilter.FilterType) diff --git a/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_schema.go b/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_schema.go index bfa6c7188..11f1c1505 100644 --- a/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_schema.go +++ b/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_schema.go @@ -1,11 +1,12 @@ package outbound_contactlistfilter import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "terraform-provider-genesyscloud/genesyscloud/provider" resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) /* @@ -134,9 +135,16 @@ func ResourceOutboundContactlistfilter() *schema.Resource { Type: schema.TypeString, }, `contact_list_id`: { - Description: `The contact list the filter is based on.`, - Required: true, - Type: schema.TypeString, + Description: `The contact list the filter is based on. Mutually exclusive to 'contact_list_template_id', however, one of the two must be specified`, + Optional: true, + Type: schema.TypeString, + ExactlyOneOf: []string{"contact_list_id", "contact_list_template_id"}, + }, + `contact_list_template_id`: { + Description: `The contact list template the filter is based on. Mutually exclusive to 'contact_list_id', however, one of the two must be specified.`, + Optional: true, + Type: schema.TypeString, + ExactlyOneOf: []string{"contact_list_id", "contact_list_template_id"}, }, `clauses`: { Description: `Groups of conditions to filter the contacts by.`, @@ -160,7 +168,8 @@ func OutboundContactlistfilterExporter() *resourceExporter.ResourceExporter { return &resourceExporter.ResourceExporter{ GetResourcesFunc: provider.GetAllWithPooledClient(getAllAuthOutboundContactlistfilters), RefAttrs: map[string]*resourceExporter.RefAttrSettings{ - "contact_list_id": {RefType: "genesyscloud_outbound_contact_list"}, + "contact_list_id": {RefType: "genesyscloud_outbound_contact_list"}, + "contact_list_template_id": {RefType: "genesyscloud_outbound_contact_list_template"}, }, } } diff --git a/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_utils.go b/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_utils.go index c76d07fdc..dd62522e1 100644 --- a/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_utils.go +++ b/genesyscloud/outbound_contactlistfilter/resource_genesyscloud_outbound_contactlistfilter_utils.go @@ -12,9 +12,21 @@ import ( func getContactlistfilterFromResourceData(d *schema.ResourceData) platformclientv2.Contactlistfilter { filter := platformclientv2.Contactlistfilter{ - Name: platformclientv2.String(d.Get("name").(string)), - ContactList: util.BuildSdkDomainEntityRef(d, "contact_list_id"), - Clauses: buildContactListFilterClauses(d.Get("clauses").([]interface{})), + Name: platformclientv2.String(d.Get("name").(string)), + Clauses: buildContactListFilterClauses(d.Get("clauses").([]interface{})), + } + contactList := util.BuildSdkDomainEntityRef(d, "contact_list_id") + contactListTemplate := util.BuildSdkDomainEntityRef(d, "contact_list_template_id") + + if contactList != nil { + filter.ContactList = contactList + contactListSource := "ContactList" + filter.SourceType = &contactListSource + } + if contactListTemplate != nil { + filter.ContactListTemplate = contactListTemplate + contactListTemplateSource := "ContactListTemplate" + filter.SourceType = &contactListTemplateSource } filterType := d.Get("filter_type").(string) @@ -224,7 +236,7 @@ func GenerateOutboundContactListFilterPredicates( %s %s %s - %s + %s %s } `, column, columnType, operator, value, inverted, varRangeBlock) diff --git a/genesyscloud/outbound_dnclist/resource_genesyscloud_outbound_dnclist_test.go b/genesyscloud/outbound_dnclist/resource_genesyscloud_outbound_dnclist_test.go index 5b24ce8af..f3c5eee6f 100644 --- a/genesyscloud/outbound_dnclist/resource_genesyscloud_outbound_dnclist_test.go +++ b/genesyscloud/outbound_dnclist/resource_genesyscloud_outbound_dnclist_test.go @@ -231,7 +231,16 @@ func TestAccResourceOutboundDncListDncListType(t *testing.T) { func TestAccResourceOutboundDncListGryphonListType(t *testing.T) { t.Parallel() - gryphonLicense, present := os.LookupEnv("TEST_DNC_GRYPHON_LICENSE_KEY") + var gryphonLicense string + var present bool + + present = false + + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" { + gryphonLicense, present = os.LookupEnv("TEST_DNC_GRYPHON_LICENSE_KEY") + } else { + gryphonLicense, present = os.LookupEnv("TEST_DNC_GRYPHON_PROD_LICENSE_KEY") + } if !present { t.Skip("Skipping because TEST_DNC_GRYPHON_LICENSE_KEY env variable is not set.") } diff --git a/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go b/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go index 99caa46b7..5f7992a0f 100644 --- a/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go +++ b/genesyscloud/recording_media_retention_policy/data_source_genesyscloud_recording_media_retention_policy_test.go @@ -8,7 +8,9 @@ import ( "terraform-provider-genesyscloud/genesyscloud/architect_flow" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" "terraform-provider-genesyscloud/genesyscloud/provider" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" userRoles "terraform-provider-genesyscloud/genesyscloud/user_roles" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -146,7 +148,7 @@ func TestAccDataSourceRecordingMediaRetentionPolicy(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, // Subdomain @@ -169,7 +171,7 @@ func TestAccDataSourceRecordingMediaRetentionPolicy(t *testing.T) { gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + - gcloud.GenerateRoutingLanguageResource(languageResource1, languageName) + + routingLanguage.GenerateRoutingLanguageResource(languageResource1, languageName) + gcloud.GenerateRoutingWrapupcodeResource(wrapupCodeResource1, wrapupCodeName) + architect_flow.GenerateFlowResource( flowResource1, diff --git a/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_init_test.go b/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_init_test.go index 21bafbcb0..b506ae6e5 100644 --- a/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_init_test.go +++ b/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_init_test.go @@ -9,7 +9,9 @@ import ( integration "terraform-provider-genesyscloud/genesyscloud/integration" "terraform-provider-genesyscloud/genesyscloud/provider" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" userRoles "terraform-provider-genesyscloud/genesyscloud/user_roles" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" "testing" @@ -44,7 +46,7 @@ func (r *registerTestInstance) registerTestResources() { defer r.resourceMapMutex.Unlock() providerResources[resourceName] = ResourceMediaRetentionPolicy() - providerResources["genesyscloud_routing_email_domain"] = gcloud.ResourceRoutingEmailDomain() + providerResources["genesyscloud_routing_email_domain"] = routingEmailDomain.ResourceRoutingEmailDomain() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() providerResources["genesyscloud_auth_role"] = authRole.ResourceAuthRole() providerResources["genesyscloud_user_roles"] = userRoles.ResourceUserRoles() @@ -52,7 +54,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_quality_forms_evaluation"] = gcloud.ResourceEvaluationForm() providerResources["genesyscloud_quality_forms_survey"] = gcloud.ResourceSurveyForm() providerResources["genesyscloud_integration"] = integration.ResourceIntegration() - providerResources["genesyscloud_routing_language"] = gcloud.ResourceRoutingLanguage() + providerResources["genesyscloud_routing_language"] = routingLanguage.ResourceRoutingLanguage() providerResources["genesyscloud_routing_wrapupcode"] = gcloud.ResourceRoutingWrapupCode() providerResources["genesyscloud_flow"] = flow.ResourceArchitectFlow() } diff --git a/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go b/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go index 9b7bcc10a..83cc84fa5 100644 --- a/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go +++ b/genesyscloud/recording_media_retention_policy/resource_genesyscloud_recording_media_retention_policy_test.go @@ -10,7 +10,9 @@ import ( "terraform-provider-genesyscloud/genesyscloud/architect_flow" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" "terraform-provider-genesyscloud/genesyscloud/provider" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" userRoles "terraform-provider-genesyscloud/genesyscloud/user_roles" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -932,7 +934,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, // Subdomain @@ -955,7 +957,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + - gcloud.GenerateRoutingLanguageResource(languageResource1, languageName) + + routingLanguage.GenerateRoutingLanguageResource(languageResource1, languageName) + gcloud.GenerateRoutingWrapupcodeResource(wrapupCodeResource1, wrapupCodeName) + architect_flow.GenerateFlowResource( flowResource1, @@ -1039,7 +1041,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { }, { - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, // Subdomain @@ -1062,7 +1064,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + - gcloud.GenerateRoutingLanguageResource(languageResource1, languageName) + + routingLanguage.GenerateRoutingLanguageResource(languageResource1, languageName) + gcloud.GenerateRoutingWrapupcodeResource(wrapupCodeResource1, wrapupCodeName) + architect_flow.GenerateFlowResource( flowResource1, @@ -1146,7 +1148,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { }, { - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, // Subdomain @@ -1169,7 +1171,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + - gcloud.GenerateRoutingLanguageResource(languageResource1, languageName) + + routingLanguage.GenerateRoutingLanguageResource(languageResource1, languageName) + gcloud.GenerateRoutingWrapupcodeResource(wrapupCodeResource1, wrapupCodeName) + architect_flow.GenerateFlowResource( flowResource1, @@ -1253,7 +1255,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { }, { - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, // Subdomain @@ -1276,7 +1278,7 @@ func TestAccResourceMediaRetentionPolicyBasic(t *testing.T) { gcloud.GenerateEvaluationFormResource(evaluationFormResource1, &evaluationFormResourceBody) + gcloud.GenerateSurveyFormResource(surveyFormResource1, &surveyFormResourceBody) + integration.GenerateIntegrationResource(integrationResource1, strconv.Quote(integrationIntendedState), strconv.Quote(integrationType), "") + - gcloud.GenerateRoutingLanguageResource(languageResource1, languageName) + + routingLanguage.GenerateRoutingLanguageResource(languageResource1, languageName) + gcloud.GenerateRoutingWrapupcodeResource(wrapupCodeResource1, wrapupCodeName) + architect_flow.GenerateFlowResource( flowResource1, diff --git a/genesyscloud/resource_exporter/resource_exporter.go b/genesyscloud/resource_exporter/resource_exporter.go index 33c97cf31..19a3b4099 100644 --- a/genesyscloud/resource_exporter/resource_exporter.go +++ b/genesyscloud/resource_exporter/resource_exporter.go @@ -46,10 +46,11 @@ type RefAttrSettings struct { } type ResourceInfo struct { - State *terraform.InstanceState - Name string - Type string - CtyType cty.Type + State *terraform.InstanceState + Name string + Type string + CtyType cty.Type + ResourceType string } // RefAttrCustomResolver allows the definition of a custom resolver for an exporter. @@ -304,3 +305,22 @@ func SetRegisterExporter(resources map[string]*ResourceExporter) { defer resourceExporterMapMutex.Unlock() resourceExporters = resources } + +var ( + ExportAsData []string + dsMutex sync.Mutex + resourceNameSanitizer = NewSanitizerProvider() +) + +// The AddDataSourceItems function adds resources to the ExportAsData []string and are formatted correctly +// The ExportAsData will be checked in the genesyscloud_resource_exporter to determine resources to be exported as data source +func AddDataSourceItems(resourceName, itemName string) { + exportName := resourceName + "::" + resourceNameSanitizer.S.SanitizeResourceName(itemName) + addDataSourceItemstoExport(exportName) +} + +func addDataSourceItemstoExport(name string) { + dsMutex.Lock() + defer dsMutex.Unlock() + ExportAsData = append(ExportAsData, name) +} diff --git a/genesyscloud/resource_genesyscloud_auth_division_test.go b/genesyscloud/resource_genesyscloud_auth_division_test.go index fab7c0fb0..473092ef5 100644 --- a/genesyscloud/resource_genesyscloud_auth_division_test.go +++ b/genesyscloud/resource_genesyscloud_auth_division_test.go @@ -22,6 +22,7 @@ func TestAccResourceAuthDivisionBasic(t *testing.T) { divName1 = "Terraform Div-" + uuid.NewString() divName2 = "Terraform Div-" + uuid.NewString() divDesc1 = "Terraform test division" + divisionID string ) cleanupAuthDivision("Terraform") @@ -30,9 +31,6 @@ func TestAccResourceAuthDivisionBasic(t *testing.T) { ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - PreConfig: func() { - time.Sleep(30 * time.Second) - }, // Create Config: GenerateAuthDivisionResource( divResource1, @@ -43,6 +41,19 @@ func TestAccResourceAuthDivisionBasic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_auth_division."+divResource1, "name", divName1), resource.TestCheckResourceAttr("genesyscloud_auth_division."+divResource1, "description", ""), + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper updation + return nil + }, + func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["genesyscloud_auth_division."+divResource1] + if !ok { + return fmt.Errorf("not found: %s", "genesyscloud_auth_division."+divResource1) + } + divisionID = rs.Primary.ID + log.Printf("Division ID: %s\n", divisionID) // Print ID + return nil + }, ), }, { @@ -78,15 +89,13 @@ 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 - }, - ), + Destroy: true, }, }, - CheckDestroy: testVerifyDivisionsDestroyed, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyDivisionsDestroyed(state) + }, }) } @@ -181,6 +190,11 @@ func testVerifyDivisionsDestroyed(state *terraform.State) error { // We do not delete home divisions continue } + err := checkDivisionDeleted(rs.Primary.ID)(state) + if err != nil { + return err + } + fmt.Printf("Check complete for division ID: %s\n", rs.Primary.ID) division, resp, err := authAPI.GetAuthorizationDivision(rs.Primary.ID, false) if division != nil { @@ -243,3 +257,44 @@ func cleanupAuthDivision(idPrefix string) { } } } + +func checkDivisionDeleted(id string) resource.TestCheckFunc { + return func(s *terraform.State) error { + log.Printf("Fetching division with ID: %s\n", id) + maxAttempts := 24 + for i := 0; i < maxAttempts; i++ { + deleted, err := isDivisionDeleted(id) + if err != nil { + return err + } + if deleted { + return nil + } + time.Sleep(10 * time.Second) + } + return fmt.Errorf("division %s was not deleted properly", id) + } +} + +func isDivisionDeleted(id string) (bool, error) { + mu.Lock() + defer mu.Unlock() + + authAPI := platformclientv2.NewAuthorizationApi() + // Attempt to get the division + _, response, err := authAPI.GetAuthorizationDivision(id, false) + + // Check if the division is not found (deleted) + if response != nil && response.StatusCode == 404 { + return true, nil // division is deleted + } + + // Handle other errors + if err != nil { + log.Printf("Error fetching user: %v", err) + return false, err + } + + // If division is found, it means the division is not deleted + return false, nil +} diff --git a/genesyscloud/resource_genesyscloud_init.go b/genesyscloud/resource_genesyscloud_init.go index d2a1a248f..1f1db0c9b 100644 --- a/genesyscloud/resource_genesyscloud_init.go +++ b/genesyscloud/resource_genesyscloud_init.go @@ -29,10 +29,8 @@ func registerDataSources(l registrar.Registrar) { l.RegisterDataSource("genesyscloud_organizations_me", DataSourceOrganizationsMe()) l.RegisterDataSource("genesyscloud_quality_forms_evaluation", DataSourceQualityFormsEvaluations()) l.RegisterDataSource("genesyscloud_quality_forms_survey", dataSourceQualityFormsSurvey()) - l.RegisterDataSource("genesyscloud_routing_language", dataSourceRoutingLanguage()) l.RegisterDataSource("genesyscloud_routing_skill", dataSourceRoutingSkill()) l.RegisterDataSource("genesyscloud_routing_skill_group", dataSourceRoutingSkillGroup()) - l.RegisterDataSource("genesyscloud_routing_email_domain", DataSourceRoutingEmailDomain()) l.RegisterDataSource("genesyscloud_routing_wrapupcode", DataSourceRoutingWrapupcode()) l.RegisterDataSource("genesyscloud_user", DataSourceUser()) l.RegisterDataSource("genesyscloud_widget_deployment", dataSourceWidgetDeployments()) @@ -56,8 +54,6 @@ func registerResources(l registrar.Registrar) { l.RegisterResource("genesyscloud_location", ResourceLocation()) l.RegisterResource("genesyscloud_quality_forms_evaluation", ResourceEvaluationForm()) l.RegisterResource("genesyscloud_quality_forms_survey", ResourceSurveyForm()) - l.RegisterResource("genesyscloud_routing_email_domain", ResourceRoutingEmailDomain()) - l.RegisterResource("genesyscloud_routing_language", ResourceRoutingLanguage()) l.RegisterResource("genesyscloud_routing_skill", ResourceRoutingSkill()) l.RegisterResource("genesyscloud_routing_skill_group", ResourceRoutingSkillGroup()) l.RegisterResource("genesyscloud_routing_wrapupcode", ResourceRoutingWrapupCode()) @@ -78,8 +74,6 @@ func registerExporters(l registrar.Registrar) { l.RegisterExporter("genesyscloud_location", LocationExporter()) l.RegisterExporter("genesyscloud_quality_forms_evaluation", EvaluationFormExporter()) l.RegisterExporter("genesyscloud_quality_forms_survey", SurveyFormExporter()) - l.RegisterExporter("genesyscloud_routing_email_domain", RoutingEmailDomainExporter()) - l.RegisterExporter("genesyscloud_routing_language", RoutingLanguageExporter()) l.RegisterExporter("genesyscloud_routing_skill", RoutingSkillExporter()) l.RegisterExporter("genesyscloud_routing_skill_group", ResourceSkillGroupExporter()) l.RegisterExporter("genesyscloud_routing_wrapupcode", RoutingWrapupCodeExporter()) diff --git a/genesyscloud/resource_genesyscloud_init_test.go b/genesyscloud/resource_genesyscloud_init_test.go index c1d69a24d..482f5e032 100644 --- a/genesyscloud/resource_genesyscloud_init_test.go +++ b/genesyscloud/resource_genesyscloud_init_test.go @@ -6,12 +6,16 @@ import ( "terraform-provider-genesyscloud/genesyscloud/architect_flow" archScheduleGroup "terraform-provider-genesyscloud/genesyscloud/architect_schedulegroups" architectSchedules "terraform-provider-genesyscloud/genesyscloud/architect_schedules" + cMessagingSettings "terraform-provider-genesyscloud/genesyscloud/conversations_messaging_settings" "terraform-provider-genesyscloud/genesyscloud/group" "terraform-provider-genesyscloud/genesyscloud/provider" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" + routinglanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" routingSettings "terraform-provider-genesyscloud/genesyscloud/routing_settings" routingUtilization "terraform-provider-genesyscloud/genesyscloud/routing_utilization" routingUtilizationLabel "terraform-provider-genesyscloud/genesyscloud/routing_utilization_label" + extensionPool "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_extension_pool" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -53,8 +57,8 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_location"] = ResourceLocation() providerResources["genesyscloud_quality_forms_evaluation"] = ResourceEvaluationForm() providerResources["genesyscloud_quality_forms_survey"] = ResourceSurveyForm() - providerResources["genesyscloud_routing_email_domain"] = ResourceRoutingEmailDomain() - providerResources["genesyscloud_routing_language"] = ResourceRoutingLanguage() + providerResources["genesyscloud_routing_language"] = routinglanguage.ResourceRoutingLanguage() + providerResources["genesyscloud_routing_email_domain"] = routingEmailDomain.ResourceRoutingEmailDomain() providerResources["genesyscloud_routing_skill"] = ResourceRoutingSkill() providerResources["genesyscloud_routing_skill_group"] = ResourceRoutingSkillGroup() providerResources["genesyscloud_routing_settings"] = routingSettings.ResourceRoutingSettings() @@ -65,7 +69,8 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_architect_schedulegroups"] = archScheduleGroup.ResourceArchitectSchedulegroups() providerResources["genesyscloud_architect_schedules"] = architectSchedules.ResourceArchitectSchedules() providerResources["genesyscloud_routing_utilization_label"] = routingUtilizationLabel.ResourceRoutingUtilizationLabel() - + providerResources["genesyscloud_conversations_messaging_settings"] = cMessagingSettings.ResourceConversationsMessagingSettings() + providerResources["genesyscloud_telephony_providers_edges_extension_pool"] = extensionPool.ResourceTelephonyExtensionPool() } func (r *registerTestInstance) registerTestDataSources() { @@ -92,14 +97,16 @@ func (r *registerTestInstance) registerTestDataSources() { providerDataSources["genesyscloud_organizations_me"] = DataSourceOrganizationsMe() providerDataSources["genesyscloud_quality_forms_evaluation"] = DataSourceQualityFormsEvaluations() providerDataSources["genesyscloud_quality_forms_survey"] = dataSourceQualityFormsSurvey() - providerDataSources["genesyscloud_routing_language"] = dataSourceRoutingLanguage() + providerDataSources["genesyscloud_routing_language"] = routinglanguage.DataSourceRoutingLanguage() providerDataSources["genesyscloud_routing_skill"] = dataSourceRoutingSkill() providerDataSources["genesyscloud_routing_skill_group"] = dataSourceRoutingSkillGroup() - providerDataSources["genesyscloud_routing_email_domain"] = DataSourceRoutingEmailDomain() + providerDataSources["genesyscloud_routing_email_domain"] = routingEmailDomain.DataSourceRoutingEmailDomain() providerDataSources["genesyscloud_routing_wrapupcode"] = DataSourceRoutingWrapupcode() providerDataSources["genesyscloud_user"] = DataSourceUser() providerDataSources["genesyscloud_widget_deployment"] = dataSourceWidgetDeployments() - providerResources["genesyscloud_routing_utilization_label"] = routingUtilizationLabel.DataSourceRoutingUtilizationLabel() + providerDataSources["genesyscloud_routing_utilization_label"] = routingUtilizationLabel.DataSourceRoutingUtilizationLabel() + providerDataSources["genesyscloud_conversations_messaging_settings"] = cMessagingSettings.DataSourceConversationsMessagingSettings() + } func initTestResources() { diff --git a/genesyscloud/resource_genesyscloud_journey_action_map.go b/genesyscloud/resource_genesyscloud_journey_action_map.go index 2b9100ec4..6d74a105f 100644 --- a/genesyscloud/resource_genesyscloud_journey_action_map.go +++ b/genesyscloud/resource_genesyscloud_journey_action_map.go @@ -58,6 +58,13 @@ var ( Type: schema.TypeSet, Optional: true, Elem: outcomeProbabilityConditionResource, + Deprecated: "Use trigger_with_outcome_quantile_conditions attribute instead.", + }, + "trigger_with_outcome_quantile_conditions": { + Description: "Quantile conditions for outcomes that must be satisfied to trigger the action map.", + Type: schema.TypeSet, + Optional: true, + Elem: outcomeQuantileConditionResource, }, "page_url_conditions": { Description: "URL conditions that a page must match for web actions to be displayable.", @@ -173,6 +180,26 @@ var ( }, } + outcomeQuantileConditionResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "outcome_id": { + Description: "The outcome ID.", + Type: schema.TypeString, + Required: true, + }, + "max_quantile_threshold": { + Description: "This Outcome Quantile Condition is met when sessionMaxQuantile of the OutcomeScore is above this value, (unless fallbackQuantile is set). Range 0.00-1.00", + Type: schema.TypeFloat, + Required: true, + }, + "fallback_quantile_threshold": { + Description: "If set, this Condition is met when max_quantile_threshold is met, AND the current quantile of the OutcomeScore is below this fallback_quantile_threshold. Range 0.00-1.00", + Type: schema.TypeFloat, + Optional: true, + }, + }, + } + urlConditionResource = &schema.Resource{ Schema: map[string]*schema.Schema{ "values": { @@ -388,6 +415,7 @@ func JourneyActionMapExporter() *resourceExporter.ResourceExporter { RefAttrs: map[string]*resourceExporter.RefAttrSettings{ "trigger_with_segments": {RefType: "genesyscloud_journey_segment"}, "trigger_with_outcome_probability_conditions.outcome_id": {RefType: "genesyscloud_journey_outcome"}, + "trigger_with_outcome_quantile_conditions.outcome_id": {RefType: "genesyscloud_journey_outcome"}, "action.architect_flow_fields.architect_flow_id": {RefType: "genesyscloud_flow"}, "action_map_schedule_groups.action_map_schedule_group_id": {RefType: "genesyscloud_architect_schedulegroups"}, "action_map_schedule_groups.emergency_action_map_schedule_group_id": {RefType: "genesyscloud_architect_schedulegroups"}, @@ -512,6 +540,7 @@ func flattenActionMap(d *schema.ResourceData, actionMap *platformclientv2.Action d.Set("trigger_with_segments", lists.StringListToSetOrNil(actionMap.TriggerWithSegments)) resourcedata.SetNillableValue(d, "trigger_with_event_conditions", lists.FlattenList(actionMap.TriggerWithEventConditions, flattenEventCondition)) resourcedata.SetNillableValue(d, "trigger_with_outcome_probability_conditions", lists.FlattenList(actionMap.TriggerWithOutcomeProbabilityConditions, flattenOutcomeProbabilityCondition)) + resourcedata.SetNillableValue(d, "trigger_with_outcome_quantile_conditions", lists.FlattenList(actionMap.TriggerWithOutcomeQuantileConditions, flattenOutcomeQuantileCondition)) resourcedata.SetNillableValue(d, "page_url_conditions", lists.FlattenList(actionMap.PageUrlConditions, flattenUrlCondition)) d.Set("activation", lists.FlattenAsList(actionMap.Activation, flattenActivation)) d.Set("weight", *actionMap.Weight) @@ -528,6 +557,7 @@ func buildSdkActionMap(actionMap *schema.ResourceData) *platformclientv2.Actionm triggerWithSegments := lists.BuildSdkStringList(actionMap, "trigger_with_segments") triggerWithEventConditions := resourcedata.BuildSdkList(actionMap, "trigger_with_event_conditions", buildSdkEventCondition) triggerWithOutcomeProbabilityConditions := resourcedata.BuildSdkList(actionMap, "trigger_with_outcome_probability_conditions", buildSdkOutcomeProbabilityCondition) + triggerWithOutcomeQuantileConditions := resourcedata.BuildSdkList(actionMap, "trigger_with_outcome_quantile_conditions", buildSdkOutcomeQuantileCondition) pageUrlConditions := resourcedata.BuildSdkList(actionMap, "page_url_conditions", buildSdkUrlCondition) activation := resourcedata.BuildSdkListFirstElement(actionMap, "activation", buildSdkActivation, true) weight := actionMap.Get("weight").(int) @@ -543,6 +573,7 @@ func buildSdkActionMap(actionMap *schema.ResourceData) *platformclientv2.Actionm TriggerWithSegments: triggerWithSegments, TriggerWithEventConditions: triggerWithEventConditions, TriggerWithOutcomeProbabilityConditions: triggerWithOutcomeProbabilityConditions, + TriggerWithOutcomeQuantileConditions: triggerWithOutcomeQuantileConditions, PageUrlConditions: pageUrlConditions, Activation: activation, Weight: &weight, @@ -560,6 +591,7 @@ func buildSdkPatchActionMap(patchActionMap *schema.ResourceData) *platformclient triggerWithSegments := lists.BuildSdkStringList(patchActionMap, "trigger_with_segments") triggerWithEventConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "trigger_with_event_conditions", buildSdkEventCondition)) triggerWithOutcomeProbabilityConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "trigger_with_outcome_probability_conditions", buildSdkOutcomeProbabilityCondition)) + triggerWithOutcomeQuantileConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "trigger_with_outcome_quantile_conditions", buildSdkOutcomeQuantileCondition)) pageUrlConditions := lists.NilToEmptyList(resourcedata.BuildSdkList(patchActionMap, "page_url_conditions", buildSdkUrlCondition)) activation := resourcedata.BuildSdkListFirstElement(patchActionMap, "activation", buildSdkActivation, true) weight := patchActionMap.Get("weight").(int) @@ -575,6 +607,7 @@ func buildSdkPatchActionMap(patchActionMap *schema.ResourceData) *platformclient sdkPatchActionMap.SetField("TriggerWithSegments", triggerWithSegments) sdkPatchActionMap.SetField("TriggerWithEventConditions", triggerWithEventConditions) sdkPatchActionMap.SetField("TriggerWithOutcomeProbabilityConditions", triggerWithOutcomeProbabilityConditions) + sdkPatchActionMap.SetField("TriggerWithOutcomeQuantileConditions", triggerWithOutcomeQuantileConditions) sdkPatchActionMap.SetField("PageUrlConditions", pageUrlConditions) sdkPatchActionMap.SetField("Activation", activation) sdkPatchActionMap.SetField("Weight", &weight) @@ -636,6 +669,27 @@ func buildSdkOutcomeProbabilityCondition(outcomeProbabilityCondition map[string] } } +func flattenOutcomeQuantileCondition(outcomeQuantileCondition *platformclientv2.Outcomequantilecondition) map[string]interface{} { + outcomeQuantileConditionMap := make(map[string]interface{}) + outcomeQuantileConditionMap["outcome_id"] = *outcomeQuantileCondition.OutcomeId + outcomeQuantileConditionMap["max_quantile_threshold"] = *typeconv.Float32to64(outcomeQuantileCondition.MaxQuantileThreshold) + stringmap.SetValueIfNotNil(outcomeQuantileConditionMap, "fallback_quantile_threshold", typeconv.Float32to64(outcomeQuantileCondition.FallbackQuantileThreshold)) + return outcomeQuantileConditionMap +} + +func buildSdkOutcomeQuantileCondition(outcomeQuantileCondition map[string]interface{}) *platformclientv2.Outcomequantilecondition { + outcomeId := outcomeQuantileCondition["outcome_id"].(string) + maxQuantileThreshold64 := outcomeQuantileCondition["max_quantile_threshold"].(float64) + maxQuantileThreshold := typeconv.Float64to32(&maxQuantileThreshold64) + fallbackQuantileThreshold := typeconv.Float64to32(stringmap.GetNonDefaultValue[float64](outcomeQuantileCondition, "fallback_quantile_threshold")) + + return &platformclientv2.Outcomequantilecondition{ + OutcomeId: &outcomeId, + MaxQuantileThreshold: maxQuantileThreshold, + FallbackQuantileThreshold: fallbackQuantileThreshold, + } +} + func flattenUrlCondition(urlCondition *platformclientv2.Urlcondition) map[string]interface{} { urlConditionMap := make(map[string]interface{}) urlConditionMap["values"] = lists.StringListToSet(*urlCondition.Values) diff --git a/genesyscloud/resource_genesyscloud_location.go b/genesyscloud/resource_genesyscloud_location.go index 910a0521e..3781d4dee 100644 --- a/genesyscloud/resource_genesyscloud_location.go +++ b/genesyscloud/resource_genesyscloud_location.go @@ -381,7 +381,7 @@ func flattenLocationEmergencyNumber(numberConfig *platformclientv2.Locationemerg } numberSettings := make(map[string]interface{}) if numberConfig.Number != nil { - numberSettings["number"] = *numberConfig.Number + numberSettings["number"], _ = util.FormatAsE164Number(*numberConfig.Number) } if numberConfig.VarType != nil { numberSettings["type"] = *numberConfig.VarType diff --git a/genesyscloud/resource_genesyscloud_routing_email_domain.go b/genesyscloud/resource_genesyscloud_routing_email_domain.go deleted file mode 100644 index f49449257..000000000 --- a/genesyscloud/resource_genesyscloud_routing_email_domain.go +++ /dev/null @@ -1,242 +0,0 @@ -package genesyscloud - -import ( - "context" - "fmt" - "log" - "strings" - "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" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" -) - -func getAllRoutingEmailDomains(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { - resources := make(resourceExporter.ResourceIDMetaMap) - routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig) - - for pageNum := 1; ; pageNum++ { - const pageSize = 100 - - domains, resp, getErr := routingAPI.GetRoutingEmailDomains(pageSize, pageNum, false, "") - if getErr != nil { - return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to get routing email domains error: %s", getErr), resp) - } - - if domains.Entities == nil || len(*domains.Entities) == 0 { - return resources, nil - } - - for _, domain := range *domains.Entities { - - resources[*domain.Id] = &resourceExporter.ResourceMeta{Name: *domain.Id} - } - } -} - -func RoutingEmailDomainExporter() *resourceExporter.ResourceExporter { - return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingEmailDomains), - UnResolvableAttributes: map[string]*schema.Schema{ - "custom_smtp_server_id": ResourceRoutingEmailDomain().Schema["custom_smtp_server_id"], - }, - } -} - -func ResourceRoutingEmailDomain() *schema.Resource { - return &schema.Resource{ - Description: "Genesys Cloud Routing Email Domain", - - CreateContext: provider.CreateWithPooledClient(createRoutingEmailDomain), - ReadContext: provider.ReadWithPooledClient(readRoutingEmailDomain), - UpdateContext: provider.UpdateWithPooledClient(updateRoutingEmailDomain), - DeleteContext: provider.DeleteWithPooledClient(deleteRoutingEmailDomain), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - SchemaVersion: 1, - Schema: map[string]*schema.Schema{ - "domain_id": { - Description: "Unique Id of the domain such as: 'example.com'. If subdomain is true, the Genesys Cloud regional domain is appended. Changing the domain_id attribute will cause the routing_email_domain to be dropped and recreated with a new ID.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "subdomain": { - Description: "Indicates if this a Genesys Cloud sub-domain. If true, then the appropriate DNS records are created for sending/receiving email. Changing the subdomain attribute will cause the routing_email_domain to be dropped and recreated with a new ID.", - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, - }, - "mail_from_domain": { - Description: "The custom MAIL FROM domain. This must be a subdomain of your email domain", - Type: schema.TypeString, - Optional: true, - }, - "custom_smtp_server_id": { - Description: "The ID of the custom SMTP server integration to use when sending outbound emails from this domain.", - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func createRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - domainID := d.Get("domain_id").(string) - subdomain := d.Get("subdomain").(bool) - mxRecordStatus := "VALID" - if !subdomain { - mxRecordStatus = "NOT_AVAILABLE" - } - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - sdkDomain := platformclientv2.Inbounddomain{ - Id: &domainID, - SubDomain: &subdomain, - MxRecordStatus: &mxRecordStatus, - } - - log.Printf("Creating routing email domain %s", domainID) - domain, resp, err := routingAPI.PostRoutingEmailDomains(sdkDomain) - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to create routing email domain %s error: %s", domainID, err), resp) - } - - d.SetId(*domain.Id) - log.Printf("Created routing email domain %s", *domain.Id) - - // Other settings must be updated in a PATCH update - if d.HasChanges("mail_from_domain", "custom_smtp_server_id") { - return updateRoutingEmailDomain(ctx, d, meta) - } else { - return readRoutingEmailDomain(ctx, d, meta) - } -} - -func readRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingEmailDomain(), constants.DefaultConsistencyChecks, "genesyscloud_routing_email_domain") - - log.Printf("Reading routing email domain %s", d.Id()) - return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { - domain, resp, getErr := routingAPI.GetRoutingEmailDomain(d.Id()) - if getErr != nil { - if util.IsStatus404(resp) { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to read routing email domain %s | error: %s", d.Id(), getErr), resp)) - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to read routing email domain %s | error: %s", d.Id(), getErr), resp)) - } - - if domain.SubDomain != nil && *domain.SubDomain { - // Strip off the regional domain suffix added by the server - d.Set("domain_id", strings.SplitN(*domain.Id, ".", 2)[0]) - } else { - d.Set("domain_id", *domain.Id) - } - - if domain.SubDomain != nil { - d.Set("subdomain", *domain.SubDomain) - } else { - d.Set("subdomain", nil) - } - - if domain.CustomSMTPServer != nil && domain.CustomSMTPServer.Id != nil { - d.Set("custom_smtp_server_id", *domain.CustomSMTPServer.Id) - } else { - d.Set("custom_smtp_server_id", nil) - } - - if domain.MailFromSettings != nil && domain.MailFromSettings.MailFromDomain != nil { - d.Set("mail_from_domain", *domain.MailFromSettings.MailFromDomain) - } else { - d.Set("mail_from_domain", nil) - } - - log.Printf("Read routing email domain %s", d.Id()) - return cc.CheckState(d) - }) -} - -func updateRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - customSMTPServer := d.Get("custom_smtp_server_id").(string) - mailFromDomain := d.Get("mail_from_domain").(string) - domainID := d.Get("domain_id").(string) - - if !strings.Contains(mailFromDomain, domainID) || mailFromDomain == domainID { - return util.BuildDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("domain_id must be a subdomain of mail_from_domain"), fmt.Errorf("domain_id must be a subdomain of mail_from_domain")) - } - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - log.Printf("Updating routing email domain %s", d.Id()) - - _, resp, err := routingAPI.PatchRoutingEmailDomain(d.Id(), platformclientv2.Inbounddomainpatchrequest{ - MailFromSettings: &platformclientv2.Mailfromresult{ - MailFromDomain: &mailFromDomain, - }, - CustomSMTPServer: &platformclientv2.Domainentityref{ - Id: &customSMTPServer, - }, - }) - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to update routing email domain %s error: %s", d.Id(), err), resp) - } - - log.Printf("Updated routing email domain %s", d.Id()) - return readRoutingEmailDomain(ctx, d, meta) -} - -func deleteRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - log.Printf("Deleting routing email domain %s", d.Id()) - resp, err := routingAPI.DeleteRoutingEmailDomain(d.Id()) - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Failed to delete routing email domain %s error: %s", d.Id(), err), resp) - } - - return util.WithRetries(ctx, 90*time.Second, func() *retry.RetryError { - _, resp, err := routingAPI.GetRoutingEmailDomain(d.Id()) - if err != nil { - if util.IsStatus404(resp) { - // Routing email domain deleted - log.Printf("Deleted Routing email domain %s", d.Id()) - return nil - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Error deleting Routing email domain %s | error: %s", d.Id(), err), resp)) - } - - routingAPI.DeleteRoutingEmailDomain(d.Id()) - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_email_domain", fmt.Sprintf("Routing email domain %s still exists", d.Id()), resp)) - }) -} - -func GenerateRoutingEmailDomainResource( - resourceID string, - domainID string, - subdomain string, - fromDomain string) string { - return fmt.Sprintf(`resource "genesyscloud_routing_email_domain" "%s" { - domain_id = "%s" - subdomain = %s - mail_from_domain = %s - } - `, resourceID, domainID, subdomain, fromDomain) -} diff --git a/genesyscloud/resource_genesyscloud_routing_language.go b/genesyscloud/resource_genesyscloud_routing_language.go deleted file mode 100644 index 9340ff2b7..000000000 --- a/genesyscloud/resource_genesyscloud_routing_language.go +++ /dev/null @@ -1,164 +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" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" -) - -func getAllRoutingLanguages(_ context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { - resources := make(resourceExporter.ResourceIDMetaMap) - routingAPI := platformclientv2.NewRoutingApiWithConfig(clientConfig) - - for pageNum := 1; ; pageNum++ { - const pageSize = 100 - languages, resp, getErr := routingAPI.GetRoutingLanguages(pageSize, pageNum, "", "", nil) - if getErr != nil { - return nil, util.BuildAPIDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to get page of languages: %v", getErr), resp) - } - - if languages.Entities == nil || len(*languages.Entities) == 0 { - break - } - - for _, language := range *languages.Entities { - if language.State != nil && *language.State != "deleted" { - resources[*language.Id] = &resourceExporter.ResourceMeta{Name: *language.Name} - } - } - } - - return resources, nil -} - -func RoutingLanguageExporter() *resourceExporter.ResourceExporter { - return &resourceExporter.ResourceExporter{ - GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingLanguages), - RefAttrs: map[string]*resourceExporter.RefAttrSettings{}, // No references - } -} - -func ResourceRoutingLanguage() *schema.Resource { - return &schema.Resource{ - Description: "Genesys Cloud Routing Language", - - CreateContext: provider.CreateWithPooledClient(createRoutingLanguage), - ReadContext: provider.ReadWithPooledClient(readRoutingLanguage), - DeleteContext: provider.DeleteWithPooledClient(deleteRoutingLanguage), - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - SchemaVersion: 1, - Schema: map[string]*schema.Schema{ - "name": { - Description: "Language name. Changing the language_name attribute will cause the language object to be dropped and recreated with a new ID.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - }, - } -} - -func createRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - log.Printf("Creating language %s", name) - language, resp, err := routingAPI.PostRoutingLanguages(platformclientv2.Language{ - Name: &name, - }) - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to create language %s error: %s", name, err), resp) - } - - d.SetId(*language.Id) - - log.Printf("Created language %s %s", name, *language.Id) - return readRoutingLanguage(ctx, d, meta) -} - -func readRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingLanguage(), constants.DefaultConsistencyChecks, "genesyscloud_routing_language") - - log.Printf("Reading language %s", d.Id()) - return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { - language, resp, getErr := routingApi.GetRoutingLanguage(d.Id()) - if getErr != nil { - if util.IsStatus404(resp) { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to read language %s | error: %s", d.Id(), getErr), resp)) - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to read language %s | error: %s", d.Id(), getErr), resp)) - } - - if language.State != nil && *language.State == "deleted" { - d.SetId("") - return nil - } - - d.Set("name", *language.Name) - log.Printf("Read language %s %s", d.Id(), *language.Name) - return cc.CheckState(d) - }) -} - -func deleteRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Get("name").(string) - - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingApi := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - log.Printf("Deleting language %s", name) - resp, err := routingApi.DeleteRoutingLanguage(d.Id()) - - if err != nil { - return util.BuildAPIDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Failed to delete language %s error: %s", name, err), resp) - } - - return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError { - routingLanguage, resp, err := routingApi.GetRoutingLanguage(d.Id()) - if err != nil { - if util.IsStatus404(resp) { - // Routing language deleted - log.Printf("Deleted Routing language %s", d.Id()) - return nil - } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Error deleting Routing language %s | error: %s", d.Id(), err), resp)) - } - - if routingLanguage.State != nil && *routingLanguage.State == "deleted" { - // Routing language deleted - log.Printf("Deleted Routing language %s", d.Id()) - return nil - } - - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_routing_language", fmt.Sprintf("Routing language %s still exists", d.Id()), resp)) - }) -} - -func GenerateRoutingLanguageResource( - resourceID string, - name string) string { - return fmt.Sprintf(`resource "genesyscloud_routing_language" "%s" { - name = "%s" - } - `, resourceID, name) -} diff --git a/genesyscloud/resource_genesyscloud_routing_skill_group_test.go b/genesyscloud/resource_genesyscloud_routing_skill_group_test.go index 3fdf5e580..95b1bd51d 100644 --- a/genesyscloud/resource_genesyscloud_routing_skill_group_test.go +++ b/genesyscloud/resource_genesyscloud_routing_skill_group_test.go @@ -421,9 +421,6 @@ func TestAccResourceRoutingSkillGroupMemberDivisionsUsersAssigned(t *testing.T) "genesyscloud_auth_division." + division2ResourceId + ".id", "genesyscloud_auth_division." + division3ResourceId + ".id", } - userID1 string - userID2 string - userID3 string ) routingSkillResource := GenerateRoutingSkillResource(routingSkillResourceId, routingSkillName) @@ -504,7 +501,7 @@ resource "genesyscloud_routing_skill_group" "%s" { Steps: []resource.TestStep{ { PreConfig: func() { - time.Sleep(30 * time.Second) + time.Sleep(45 * time.Second) }, Config: skillGroupResource + routingSkillResource + @@ -516,25 +513,6 @@ resource "genesyscloud_routing_skill_group" "%s" { user3Resource, Check: resource.ComposeTestCheckFunc( testVerifySkillGroupMemberCount("genesyscloud_routing_skill_group."+skillGroupResourceId, "3"), - func(s *terraform.State) error { - rs, ok := s.RootModule().Resources["genesyscloud_user."+user1ResourceId] - if !ok { - return fmt.Errorf("not found: %s", "genesyscloud_user."+user1ResourceId) - } - userID1 = rs.Primary.ID - log.Printf("User ID: %s\n", userID1) // Print user ID - rs, ok = s.RootModule().Resources["genesyscloud_user."+user2ResourceId] - if !ok { - return fmt.Errorf("not found: %s", "genesyscloud_user."+user2ResourceId) - } - userID2 = rs.Primary.ID - rs, ok = s.RootModule().Resources["genesyscloud_user."+user3ResourceId] - if !ok { - return fmt.Errorf("not found: %s", "genesyscloud_user."+user3ResourceId) - } - userID3 = rs.Primary.ID - return nil - }, ), }, { @@ -542,11 +520,7 @@ resource "genesyscloud_routing_skill_group" "%s" { ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"member_division_ids"}, - Check: resource.ComposeTestCheckFunc( - checkUserDeleted(userID1), - checkUserDeleted(userID2), - checkUserDeleted(userID3), - ), + Destroy: true, }, }, CheckDestroy: testVerifySkillGroupDestroyed, @@ -726,11 +700,8 @@ func testVerifyAllDivisionsAssigned(resourceName string, attrName string) resour func testVerifySkillGroupDestroyed(state *terraform.State) error { // Get default config to set config options - config, err := provider.AuthorizeSdk() - if err != nil { - return fmt.Errorf("unexpected error while trying to authorize client in testVerifySkillGroupDestroyed : %s", err) - } - routingAPI := platformclientv2.NewRoutingApiWithConfig(config) + + routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) apiClient := &routingAPI.Configuration.APIClient // TODO Once this code has been released into the public API we should fix this and use the SDK @@ -764,7 +735,55 @@ func testVerifySkillGroupDestroyed(state *terraform.State) error { // Success. All skills destroyed return nil } +func testVerifySkillGroupAndUsersDestroyed(state *terraform.State) error { + + routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) + apiClient := &routingAPI.Configuration.APIClient + usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + // TODO Once this code has been released into the public API we should fix this and use the SDK + + headerParams := BuildHeaderParams(routingAPI) + for _, rs := range state.RootModule().Resources { + if rs.Type == "genesyscloud_routing_skill_group" { + path := routingAPI.Configuration.BasePath + "/api/v2/routing/skillgroups/" + rs.Primary.ID + response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil) + + skillGroupPayload := make(map[string]interface{}) + + if err != nil { + if util.IsStatus404(response) { + break + } + return fmt.Errorf("Unexpected error while trying to read skillgroup: %s", err) + } + + json.Unmarshal(response.RawBody, &skillGroupPayload) + + if skillGroupPayload["id"] != nil && skillGroupPayload["id"] != "" { + return fmt.Errorf("Skill Group (%s) still exists", rs.Primary.ID) + } + } + if rs.Type == "genesyscloud_user" { + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } + user, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + if user != nil { + return fmt.Errorf("User Resource (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // User not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + } + // Success. All skills destroyed + return nil +} func getAllSkillGroupMemberDivisionIds(routingAPI *platformclientv2.RoutingApi, resourceId string) ([]string, diag.Diagnostics) { headers := BuildHeaderParams(routingAPI) apiClient := &routingAPI.Configuration.APIClient @@ -794,7 +813,7 @@ func getAllSkillGroupMemberDivisionIds(routingAPI *platformclientv2.RoutingApi, func checkUserDeleted(id string) resource.TestCheckFunc { log.Printf("Fetching user with ID: %s\n", id) return func(s *terraform.State) error { - maxAttempts := 18 + maxAttempts := 30 for i := 0; i < maxAttempts; i++ { deleted, err := isUserDeleted(id) @@ -814,7 +833,7 @@ func isUserDeleted(id string) (bool, error) { mu.Lock() defer mu.Unlock() - usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + usersAPI := platformclientv2.NewUsersApi() // Attempt to get the user _, response, err := usersAPI.GetUser(id, nil, "", "") diff --git a/genesyscloud/resource_genesyscloud_user.go b/genesyscloud/resource_genesyscloud_user.go index 0276edaae..d4210b9af 100644 --- a/genesyscloud/resource_genesyscloud_user.go +++ b/genesyscloud/resource_genesyscloud_user.go @@ -9,6 +9,7 @@ import ( routingUtilization "terraform-provider-genesyscloud/genesyscloud/routing_utilization" "terraform-provider-genesyscloud/genesyscloud/util" "terraform-provider-genesyscloud/genesyscloud/util/constants" + "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles" "terraform-provider-genesyscloud/genesyscloud/validators" "time" @@ -958,33 +959,74 @@ func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2. phoneNumber := make(map[string]interface{}) phoneNumber["media_type"] = *address.MediaType - // Strip off any parentheses from phone numbers - if address.Address != nil { - phoneNumber["number"] = strings.Trim(*address.Address, "()") - } else if address.Display != nil { - // Some numbers are only returned in Display - isNumber, isExtension := getNumbers(d, i) + if feature_toggles.NewUserAddressesLogicExists() { + log.Printf("Feature toggle %s is set. Using new User Addressing logic.", feature_toggles.NewUserAddressesLogicToggleName()) + // PHONE and SMS Addresses have four different ways they can return in the API + // We need to be able to handle them all, and strip off any parentheses that can surround + // values - if isNumber && phoneNumber["number"] != "" { - phoneNumber["number"] = strings.Trim(*address.Display, "()") + // 1.) Addresses that return an "address" field are phone numbers without extensions + if address.Address != nil { + phoneNumber["number"], _ = util.FormatAsE164Number(strings.Trim(*address.Address, "()")) } - if isExtension { + + // 2.) Addresses that return an "extension" field that matches the "display" field are + // true internal extensions that have been mapped to an extension pool + if address.Extension != nil { + if address.Display != nil { + if *address.Extension == *address.Display { + phoneNumber["extension"] = strings.Trim(*address.Extension, "()") + } + } + } + + // 3.) Addresses that include both an "extension" and "display" field, but they do not + // match indicate that this is a phone number plus an extension + if address.Extension != nil { + if address.Display != nil { + if *address.Extension != *address.Display { + phoneNumber["extension"] = *address.Extension + phoneNumber["number"], _ = util.FormatAsE164Number(strings.Trim(*address.Display, "()")) + } + } + } + + // 4.) Addresses that only include a "display" field (but not "address" or "extension") are + // considered an extension that has not been mapped to an internal extension pool yet. + if address.Address == nil && address.Extension == nil && address.Display != nil { phoneNumber["extension"] = strings.Trim(*address.Display, "()") } - if !isNumber && !isExtension { - if address.Extension == nil { - phoneNumber["extension"] = strings.Trim(*address.Display, "()") - } else if phoneNumber["number"] != "" { + } else { + + // Strip off any parentheses from phone numbers + if address.Address != nil { + phoneNumber["number"] = strings.Trim(*address.Address, "()") + } else if address.Display != nil { + // Some numbers are only returned in Display + isNumber, isExtension := getNumbers(d, i) + + if isNumber && phoneNumber["number"] != "" { phoneNumber["number"] = strings.Trim(*address.Display, "()") } + if isExtension { + phoneNumber["extension"] = strings.Trim(*address.Display, "()") + } + + if !isNumber && !isExtension { + if address.Extension == nil { + phoneNumber["extension"] = strings.Trim(*address.Display, "()") + } else if phoneNumber["number"] != "" { + phoneNumber["number"] = strings.Trim(*address.Display, "()") + } + } } - } - if address.Extension != nil { - phoneNumber["extension"] = *address.Extension - } + if address.Extension != nil { + phoneNumber["extension"] = *address.Extension + } + } if address.VarType != nil { phoneNumber["type"] = *address.VarType } diff --git a/genesyscloud/resource_genesyscloud_user_test.go b/genesyscloud/resource_genesyscloud_user_test.go index 4dd536e5f..4717f9318 100644 --- a/genesyscloud/resource_genesyscloud_user_test.go +++ b/genesyscloud/resource_genesyscloud_user_test.go @@ -6,8 +6,10 @@ import ( "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud/provider" + routinglanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingUtilization "terraform-provider-genesyscloud/genesyscloud/routing_utilization" routingUtilizationLabel "terraform-provider-genesyscloud/genesyscloud/routing_utilization_label" + extensionPool "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_extension_pool" "terraform-provider-genesyscloud/genesyscloud/util" "testing" "time" @@ -179,20 +181,35 @@ func generateUserWithCustomAttrs(resourceID string, email string, name string, a func TestAccResourceUserAddresses(t *testing.T) { t.Parallel() var ( - addrUserResource1 = "test-user-addr" - addrUserName = "Nancy Terraform" - addrEmail1 = "terraform-" + uuid.NewString() + "@user.com" - addrEmail2 = "terraform-" + uuid.NewString() + "@user.com" - addrEmail3 = "terraform-" + uuid.NewString() + "@user.com" - addrPhone1 = "+13174269078" - addrPhone2 = "+441434634996" - addrPhoneExt = "1234" - phoneMediaType = "PHONE" - smsMediaType = "SMS" - addrTypeWork = "WORK" - addrTypeHome = "HOME" + addrUserResource1 = "test-user-addr1" + addrUserResource2 = "test-user-addr2" + addrUserName1 = "Nancy Terraform" + addrUserName2 = "Oliver Tofu" + addrEmail1 = "terraform-" + uuid.NewString() + "@user.com" + addrEmail2 = "terraform-" + uuid.NewString() + "@user.com" + addrEmail3 = "terraform-" + uuid.NewString() + "@user.com" + addrPhone1 = "+13174269078" + addrPhone2 = "+441434634996" + addrPhoneExt1 = "1234" + addrPhoneExt2 = "1345" + phoneMediaType = "PHONE" + smsMediaType = "SMS" + addrTypeWork = "WORK" + addrTypeHome = "HOME" + extensionPoolResource1 = "test-extensionpool1" + uuid.NewString() + extensionPoolStartNumber1 = "1000" + extensionPoolEndNumber1 = "2000" ) + extensionPoolResource := extensionPool.ExtensionPoolStruct{ + ResourceID: extensionPoolResource1, + StartNumber: extensionPoolStartNumber1, + EndNumber: extensionPoolEndNumber1, + Description: util.NullValue, // No description + } + + extensionPool.DeleteExtensionPoolWithNumber(extensionPoolStartNumber1) + resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), @@ -202,7 +219,7 @@ func TestAccResourceUserAddresses(t *testing.T) { Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, - addrUserName, + addrUserName1, generateUserAddresses( generateUserPhoneAddress( strconv.Quote(addrPhone1), @@ -215,11 +232,13 @@ func TestAccResourceUserAddresses(t *testing.T) { strconv.Quote(addrTypeHome), ), ), - ), + fmt.Sprintf("depends_on = [%s.%s]", extensionPool.ResourceName, extensionPoolResource1), + ) + extensionPool.GenerateExtensionPoolResource(&extensionPoolResource), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "email", addrEmail1), - resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.number", addrPhone1), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.extension"), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.media_type", phoneMediaType), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.type", addrTypeWork), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.other_emails.0.address", addrEmail2), @@ -237,13 +256,13 @@ func TestAccResourceUserAddresses(t *testing.T) { Config: generateUserWithCustomAttrs( addrUserResource1, addrEmail1, - addrUserName, + addrUserName1, generateUserAddresses( generateUserPhoneAddress( strconv.Quote(addrPhone2), strconv.Quote(smsMediaType), strconv.Quote(addrTypeHome), - strconv.Quote(addrPhoneExt), + strconv.Quote(addrPhoneExt1), ), generateUserEmailAddress( strconv.Quote(addrEmail3), @@ -253,15 +272,41 @@ func TestAccResourceUserAddresses(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "email", addrEmail1), - resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.number", addrPhone2), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.media_type", smsMediaType), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.type", addrTypeHome), - resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.extension", addrPhoneExt), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.extension", addrPhoneExt1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.other_emails.0.address", addrEmail3), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.other_emails.0.type", addrTypeWork), ), }, + { + // Add a user with only extension + Config: generateUserWithCustomAttrs( + addrUserResource2, + addrEmail2, + addrUserName2, + generateUserAddresses( + generateUserPhoneAddress( + util.NullValue, + strconv.Quote(phoneMediaType), + strconv.Quote(addrTypeHome), + strconv.Quote(addrPhoneExt2), + ), + ), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource2, "email", addrEmail2), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource2, "name", addrUserName2), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource2, "addresses.0.phone_numbers.0.number"), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource2, "addresses.0.phone_numbers.0.media_type", phoneMediaType), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource2, "addresses.0.phone_numbers.0.type", addrTypeHome), + resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource2, "addresses.0.phone_numbers.0.extension", addrPhoneExt2), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource2, "addresses.0.other_emails.0.address"), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource2, "addresses.0.other_emails.0.type"), + ), + }, }, CheckDestroy: testVerifyUsersDestroyed, }) @@ -270,16 +315,28 @@ func TestAccResourceUserAddresses(t *testing.T) { func TestAccResourceUserPhone(t *testing.T) { t.Parallel() var ( - addrUserResource1 = "test-user-addr" - addrUserName = "Nancy Terraform" - addrEmail1 = "terraform-" + uuid.NewString() + "@user.com" - addrPhone1 = "+13173271898" - addrPhone2 = "+13173271899" - addrExt1 = "353" - phoneMediaType = "PHONE" - addrTypeWork = "WORK" + addrUserResource1 = "test-user-addr" + addrUserName = "Nancy Terraform" + addrEmail1 = "terraform-" + uuid.NewString() + "@user.com" + addrPhone1 = "+13173271898" + addrPhone2 = "+13173271899" + addrExt1 = "3532" + phoneMediaType = "PHONE" + addrTypeWork = "WORK" + extensionPoolResource1 = "test-extensionpool" + uuid.NewString() + extensionPoolStartNumber1 = "3000" + extensionPoolEndNumber1 = "4000" ) + extensionPoolResource := extensionPool.ExtensionPoolStruct{ + ResourceID: extensionPoolResource1, + StartNumber: extensionPoolStartNumber1, + EndNumber: extensionPoolEndNumber1, + Description: util.NullValue, // No description + } + + extensionPool.DeleteExtensionPoolWithNumber(extensionPoolStartNumber1) + resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), @@ -298,10 +355,12 @@ func TestAccResourceUserPhone(t *testing.T) { strconv.Quote(addrPhone1), // extension ), ), - ), + fmt.Sprintf("depends_on = [%s.%s]", extensionPool.ResourceName, extensionPoolResource1), + ) + extensionPool.GenerateExtensionPoolResource(&extensionPoolResource), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "email", addrEmail1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.number"), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.extension", addrPhone1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.media_type", phoneMediaType), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.type", addrTypeWork), @@ -324,6 +383,7 @@ func TestAccResourceUserPhone(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "email", addrEmail1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.number"), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.extension", addrPhone2), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.media_type", phoneMediaType), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.type", addrTypeWork), @@ -346,6 +406,7 @@ func TestAccResourceUserPhone(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "email", addrEmail1), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "name", addrUserName), + resource.TestCheckNoResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.extension"), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.number", addrPhone2), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.media_type", phoneMediaType), resource.TestCheckResourceAttr("genesyscloud_user."+addrUserResource1, "addresses.0.phone_numbers.0.type", addrTypeWork), @@ -454,6 +515,10 @@ func TestAccResourceUserSkills(t *testing.T) { ), Check: resource.ComposeTestCheckFunc( resource.TestCheckNoResourceAttr("genesyscloud_user."+userResource1, "skills.%"), + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper updation + return nil + }, ), }, }, @@ -486,7 +551,7 @@ func TestAccResourceUserLanguages(t *testing.T) { email1, userName1, generateUserRoutingLang("genesyscloud_routing_language."+langResource1+".id", proficiency1), - ) + GenerateRoutingLanguageResource(langResource1, langName1), + ) + routinglanguage.GenerateRoutingLanguageResource(langResource1, langName1), Check: resource.ComposeTestCheckFunc( validateUserLanguage("genesyscloud_user."+userResource1, "genesyscloud_routing_language."+langResource1, proficiency1), ), @@ -499,10 +564,10 @@ func TestAccResourceUserLanguages(t *testing.T) { userName1, generateUserRoutingLang("genesyscloud_routing_language."+langResource1+".id", proficiency1), generateUserRoutingLang("genesyscloud_routing_language."+langResource2+".id", proficiency2), - ) + GenerateRoutingLanguageResource( + ) + routinglanguage.GenerateRoutingLanguageResource( langResource1, langName1, - ) + GenerateRoutingLanguageResource( + ) + routinglanguage.GenerateRoutingLanguageResource( langResource2, langName2, ), @@ -518,7 +583,7 @@ func TestAccResourceUserLanguages(t *testing.T) { email1, userName1, generateUserRoutingLang("genesyscloud_routing_language."+langResource2+".id", proficiency1), - ) + GenerateRoutingLanguageResource( + ) + routinglanguage.GenerateRoutingLanguageResource( langResource2, langName2, ), @@ -1123,6 +1188,10 @@ func testVerifyUsersDestroyed(state *terraform.State) error { if rs.Type != "genesyscloud_user" { continue } + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } _, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") if err != nil { @@ -1131,7 +1200,6 @@ func testVerifyUsersDestroyed(state *terraform.State) error { } return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("Unexpected error: %s", err), resp)) } - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError("genesyscloud_user", fmt.Sprintf("User (%s) still exists", rs.Primary.ID), resp)) } return nil diff --git a/genesyscloud/routing_email_domain/data_source_genesyscloud_routing_email_domain.go b/genesyscloud/routing_email_domain/data_source_genesyscloud_routing_email_domain.go new file mode 100644 index 000000000..d828aaae2 --- /dev/null +++ b/genesyscloud/routing_email_domain/data_source_genesyscloud_routing_email_domain.go @@ -0,0 +1,34 @@ +package routing_email_domain + +import ( + "context" + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// Looks up the data for the Email Domain +func DataSourceRoutingEmailDomainRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sdkConfig := m.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingEmailDomainProxy(sdkConfig) + name := d.Get("name").(string) + + return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + domainId, resp, retryable, getErr := proxy.getRoutingEmailDomainIdByName(ctx, name) + if getErr != nil && !retryable { + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting email domain %s | error: %s", name, getErr), resp)) + } + if retryable { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting email domain %s | error: %s", name, getErr), resp)) + } + + d.SetId(domainId) + return nil + }) +} diff --git a/genesyscloud/data_source_genesyscloud_routing_email_domain_test.go b/genesyscloud/routing_email_domain/data_source_genesyscloud_routing_email_domain_test.go similarity index 97% rename from genesyscloud/data_source_genesyscloud_routing_email_domain_test.go rename to genesyscloud/routing_email_domain/data_source_genesyscloud_routing_email_domain_test.go index 5afcebfd5..eff801cc5 100644 --- a/genesyscloud/data_source_genesyscloud_routing_email_domain_test.go +++ b/genesyscloud/routing_email_domain/data_source_genesyscloud_routing_email_domain_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package routing_email_domain import ( "fmt" @@ -58,6 +58,7 @@ func generateRoutingEmailDomainDataSource( } func CleanupRoutingEmailDomains() { + sdkConfig, _ := provider.AuthorizeSdk() routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) for pageNum := 1; ; pageNum++ { diff --git a/genesyscloud/routing_email_domain/genesyscloud_routing_email_domain_init_test.go b/genesyscloud/routing_email_domain/genesyscloud_routing_email_domain_init_test.go new file mode 100644 index 000000000..ad3fa5077 --- /dev/null +++ b/genesyscloud/routing_email_domain/genesyscloud_routing_email_domain_init_test.go @@ -0,0 +1,61 @@ +package routing_email_domain + +import ( + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* + The genesyscloud_routing_email_domain_init_test.go file is used to initialize the data sources and resources + used in testing the routing_email_domain 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[resourceName] = ResourceRoutingEmailDomain() +} + +// registerTestDataSources registers all data sources used in the tests. +func (r *registerTestInstance) registerTestDataSources() { + r.datasourceMapMutex.Lock() + defer r.datasourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceRoutingEmailDomain() +} + +// initTestResources initializes all test resources and data sources. +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() + regInstance.registerTestDataSources() +} + +// TestMain is a "setup" function called by the testing framework when run the test +func TestMain(m *testing.M) { + // Run setup function before starting the test suite for the routing_email_domain package + initTestResources() + + // Run the test suite for the routing_email_domain package + m.Run() +} + diff --git a/genesyscloud/routing_email_domain/genesyscloud_routing_email_domain_proxy.go b/genesyscloud/routing_email_domain/genesyscloud_routing_email_domain_proxy.go new file mode 100644 index 000000000..372c315af --- /dev/null +++ b/genesyscloud/routing_email_domain/genesyscloud_routing_email_domain_proxy.go @@ -0,0 +1,162 @@ +package routing_email_domain + +import ( + "context" + "fmt" + "log" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +var internalProxy *routingEmailDomainProxy + +type getAllRoutingEmailDomainsFunc func(ctx context.Context, p *routingEmailDomainProxy) (*[]platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) +type createRoutingEmailDomainFunc func(ctx context.Context, p *routingEmailDomainProxy, inboundDomain *platformclientv2.Inbounddomain) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) +type getRoutingEmailDomainByIdFunc func(ctx context.Context, p *routingEmailDomainProxy, id string) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) +type getRoutingEmailDomainIdByNameFunc func(ctx context.Context, p *routingEmailDomainProxy, name string) (string, *platformclientv2.APIResponse, bool, error) +type updateRoutingEmailDomainFunc func(ctx context.Context, p *routingEmailDomainProxy, id string, inboundDomain *platformclientv2.Inbounddomainpatchrequest) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) +type deleteRoutingEmailDomainFunc func(ctx context.Context, p *routingEmailDomainProxy, id string) (*platformclientv2.APIResponse, error) + +// routingEmailDomainProxy contains all of the methods that call genesys cloud APIs. +type routingEmailDomainProxy struct { + clientConfig *platformclientv2.Configuration + routingApi *platformclientv2.RoutingApi + createRoutingEmailDomainAttr createRoutingEmailDomainFunc + getAllRoutingEmailDomainsAttr getAllRoutingEmailDomainsFunc + getRoutingEmailDomainIdByNameAttr getRoutingEmailDomainIdByNameFunc + getRoutingEmailDomainByIdAttr getRoutingEmailDomainByIdFunc + updateRoutingEmailDomainAttr updateRoutingEmailDomainFunc + deleteRoutingEmailDomainAttr deleteRoutingEmailDomainFunc + routingEmailDomainCache rc.CacheInterface[platformclientv2.Inbounddomain] +} + +// newRoutingEmailDomainProxy initializes the routing email domain proxy with all of the data needed to communicate with Genesys Cloud +func newRoutingEmailDomainProxy(clientConfig *platformclientv2.Configuration) *routingEmailDomainProxy { + api := platformclientv2.NewRoutingApiWithConfig(clientConfig) + routingEmailDomainCache := rc.NewResourceCache[platformclientv2.Inbounddomain]() + return &routingEmailDomainProxy{ + clientConfig: clientConfig, + routingApi: api, + createRoutingEmailDomainAttr: createRoutingEmailDomainFn, + getAllRoutingEmailDomainsAttr: getAllRoutingEmailDomainsFn, + getRoutingEmailDomainIdByNameAttr: getRoutingEmailDomainIdByNameFn, + getRoutingEmailDomainByIdAttr: getRoutingEmailDomainByIdFn, + updateRoutingEmailDomainAttr: updateRoutingEmailDomainFn, + deleteRoutingEmailDomainAttr: deleteRoutingEmailDomainFn, + routingEmailDomainCache: routingEmailDomainCache, + } +} + +// getRoutingEmailDomainProxy 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 getRoutingEmailDomainProxy(clientConfig *platformclientv2.Configuration) *routingEmailDomainProxy { + if internalProxy == nil { + internalProxy = newRoutingEmailDomainProxy(clientConfig) + } + return internalProxy +} + +func (p *routingEmailDomainProxy) getAllRoutingEmailDomains(ctx context.Context) (*[]platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + return p.getAllRoutingEmailDomainsAttr(ctx, p) +} + +// createRoutingEmailDomain creates a Genesys Cloud routing email domain +func (p *routingEmailDomainProxy) createRoutingEmailDomain(ctx context.Context, routingEmailDomain *platformclientv2.Inbounddomain) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + return p.createRoutingEmailDomainAttr(ctx, p, routingEmailDomain) +} + +// getRoutingEmailDomainById returns a single Genesys Cloud routing email domain by Id +func (p *routingEmailDomainProxy) getRoutingEmailDomainById(ctx context.Context, id string) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + return p.getRoutingEmailDomainByIdAttr(ctx, p, id) +} + +// getRoutingEmailDomainIdByName returns a single Genesys Cloud routing email domain by a name +func (p *routingEmailDomainProxy) getRoutingEmailDomainIdByName(ctx context.Context, name string) (string, *platformclientv2.APIResponse, bool, error) { + return p.getRoutingEmailDomainIdByNameAttr(ctx, p, name) +} + +// updateRoutingEmailDomain updates a Genesys Cloud routing email domain +func (p *routingEmailDomainProxy) updateRoutingEmailDomain(ctx context.Context, id string, routingEmailDomain *platformclientv2.Inbounddomainpatchrequest) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + return p.updateRoutingEmailDomainAttr(ctx, p, id, routingEmailDomain) +} + +// deleteRoutingEmailDomain deletes a Genesys Cloud routing email domain by Id +func (p *routingEmailDomainProxy) deleteRoutingEmailDomain(ctx context.Context, id string) (*platformclientv2.APIResponse, error) { + return p.deleteRoutingEmailDomainAttr(ctx, p, id) +} + +func getAllRoutingEmailDomainsFn(ctx context.Context, p *routingEmailDomainProxy) (*[]platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + var ( + allDomains []platformclientv2.Inbounddomain + pageSize = 100 + response *platformclientv2.APIResponse + ) + + domains, resp, err := p.routingApi.GetRoutingEmailDomains(pageSize, 1, false, "") + if err != nil { + return nil, resp, fmt.Errorf("failed to get routing email domains error: %s", err) + } + + if domains.Entities == nil || len(*domains.Entities) == 0 { + return &allDomains, resp, nil + } + allDomains = append(allDomains, *domains.Entities...) + + for pageNum := 2; pageNum <= *domains.PageCount; pageNum++ { + domains, resp, err := p.routingApi.GetRoutingEmailDomains(pageSize, pageNum, false, "") + if err != nil { + return nil, resp, fmt.Errorf("failed to get routing email domains error: %s", err) + } + + response = resp + if domains.Entities == nil || len(*domains.Entities) == 0 { + return &allDomains, resp, nil + } + allDomains = append(allDomains, *domains.Entities...) + } + + for _, domain := range allDomains { + rc.SetCache(p.routingEmailDomainCache, *domain.Id, domain) + } + return &allDomains, response, nil +} + +func createRoutingEmailDomainFn(ctx context.Context, p *routingEmailDomainProxy, routingEmailDomain *platformclientv2.Inbounddomain) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + return p.routingApi.PostRoutingEmailDomains(*routingEmailDomain) +} + +func getRoutingEmailDomainByIdFn(ctx context.Context, p *routingEmailDomainProxy, id string) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + if domain := rc.GetCacheItem(p.routingEmailDomainCache, id); domain != nil { + return domain, nil, nil + } + return p.routingApi.GetRoutingEmailDomain(id) +} + +func getRoutingEmailDomainIdByNameFn(ctx context.Context, p *routingEmailDomainProxy, name string) (string, *platformclientv2.APIResponse, bool, error) { + domains, resp, err := getAllRoutingEmailDomainsFn(ctx, p) + if err != nil { + return "", resp, false, err + } + + if domains == nil || len(*domains) == 0 { + return "", resp, true, fmt.Errorf("no routing email domain found with name %s", name) + } + + for _, domain := range *domains { + if *domain.Id == name { + log.Printf("retrieved the routing email domain id %s by name %s", *domain.Id, name) + return *domain.Id, resp, false, nil + } + } + + return "", resp, true, fmt.Errorf("unable to find routing email domain with name %s", name) +} + +func updateRoutingEmailDomainFn(ctx context.Context, p *routingEmailDomainProxy, id string, routingEmailDomainReq *platformclientv2.Inbounddomainpatchrequest) (*platformclientv2.Inbounddomain, *platformclientv2.APIResponse, error) { + return p.routingApi.PatchRoutingEmailDomain(id, *routingEmailDomainReq) +} + +func deleteRoutingEmailDomainFn(ctx context.Context, p *routingEmailDomainProxy, id string) (*platformclientv2.APIResponse, error) { + return p.routingApi.DeleteRoutingEmailDomain(id) +} diff --git a/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain.go b/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain.go new file mode 100644 index 000000000..dc869d2d2 --- /dev/null +++ b/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain.go @@ -0,0 +1,176 @@ +package routing_email_domain + +import ( + "context" + "fmt" + "log" + "strings" + "terraform-provider-genesyscloud/genesyscloud/consistency_checker" + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + "terraform-provider-genesyscloud/genesyscloud/util" + "terraform-provider-genesyscloud/genesyscloud/util/constants" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + "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" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +func getAllRoutingEmailDomains(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + resources := make(resourceExporter.ResourceIDMetaMap) + proxy := getRoutingEmailDomainProxy(clientConfig) + + domains, resp, getErr := proxy.getAllRoutingEmailDomains(ctx) + if getErr != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get routing email domains error: %s", getErr), resp) + } + + if domains == nil || len(*domains) == 0 { + return resources, nil + } + + for _, domain := range *domains { + resources[*domain.Id] = &resourceExporter.ResourceMeta{Name: *domain.Id} + } + return resources, nil +} + +func createRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingEmailDomainProxy(sdkConfig) + + domainID := d.Get("domain_id").(string) + subdomain := d.Get("subdomain").(bool) + mxRecordStatus := "VALID" + if !subdomain { + mxRecordStatus = "NOT_AVAILABLE" + } + + sdkDomain := platformclientv2.Inbounddomain{ + Id: &domainID, + SubDomain: &subdomain, + MxRecordStatus: &mxRecordStatus, + } + + log.Printf("Creating routing email domain %s", domainID) + domain, resp, err := proxy.createRoutingEmailDomain(ctx, &sdkDomain) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create routing email domain %s error: %s", domainID, err), resp) + } + + d.SetId(*domain.Id) + log.Printf("Created routing email domain %s", *domain.Id) + + // Other settings must be updated in a PATCH update + if d.HasChanges("mail_from_domain", "custom_smtp_server_id") { + return updateRoutingEmailDomain(ctx, d, meta) + } else { + return readRoutingEmailDomain(ctx, d, meta) + } +} + +func readRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingEmailDomainProxy(sdkConfig) + + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingEmailDomain(), constants.DefaultConsistencyChecks, resourceName) + + log.Printf("Reading routing email domain %s", d.Id()) + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + domain, resp, getErr := proxy.getRoutingEmailDomainById(ctx, d.Id()) + if getErr != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read routing email domain %s | error: %s", d.Id(), getErr), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read routing email domain %s | error: %s", d.Id(), getErr), resp)) + } + + resourcedata.SetNillableValue(d, "subdomain", domain.SubDomain) + resourcedata.SetNillableReference(d, "custom_smtp_server_id", domain.CustomSMTPServer) + + if domain.SubDomain != nil && *domain.SubDomain { + // Strip off the regional domain suffix added by the server + _ = d.Set("domain_id", strings.SplitN(*domain.Id, ".", 2)[0]) + } else { + _ = d.Set("domain_id", *domain.Id) + } + + if domain.MailFromSettings != nil && domain.MailFromSettings.MailFromDomain != nil { + _ = d.Set("mail_from_domain", *domain.MailFromSettings.MailFromDomain) + } else { + _ = d.Set("mail_from_domain", nil) + } + + log.Printf("Read routing email domain %s", d.Id()) + return cc.CheckState(d) + }) +} + +func updateRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingEmailDomainProxy(sdkConfig) + + customSMTPServer := d.Get("custom_smtp_server_id").(string) + mailFromDomain := d.Get("mail_from_domain").(string) + domainID := d.Get("domain_id").(string) + + if !strings.Contains(mailFromDomain, domainID) || mailFromDomain == domainID { + return util.BuildDiagnosticError(resourceName, "domain_id must be a subdomain of mail_from_domain", fmt.Errorf("domain_id must be a subdomain of mail_from_domain")) + } + + log.Printf("Updating routing email domain %s", d.Id()) + + _, resp, err := proxy.updateRoutingEmailDomain(ctx, d.Id(), &platformclientv2.Inbounddomainpatchrequest{ + MailFromSettings: &platformclientv2.Mailfromresult{ + MailFromDomain: &mailFromDomain, + }, + CustomSMTPServer: &platformclientv2.Domainentityref{ + Id: &customSMTPServer, + }, + }) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update routing email domain %s error: %s", d.Id(), err), resp) + } + + log.Printf("Updated routing email domain %s", d.Id()) + return readRoutingEmailDomain(ctx, d, meta) +} + +func deleteRoutingEmailDomain(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingEmailDomainProxy(sdkConfig) + + log.Printf("Deleting routing email domain %s", d.Id()) + resp, err := proxy.deleteRoutingEmailDomain(ctx, d.Id()) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete routing email domain %s error: %s", d.Id(), err), resp) + } + + return util.WithRetries(ctx, 90*time.Second, func() *retry.RetryError { + _, resp, err := proxy.getRoutingEmailDomainById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + log.Printf("Deleted Routing email domain %s", d.Id()) + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Routing email domain %s | error: %s", d.Id(), err), resp)) + } + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Routing email domain %s still exists", d.Id()), resp)) + }) +} + +func GenerateRoutingEmailDomainResource( + resourceID string, + domainID string, + subdomain string, + fromDomain string) string { + return fmt.Sprintf(`resource "genesyscloud_routing_email_domain" "%s" { + domain_id = "%s" + subdomain = %s + mail_from_domain = %s + } + `, resourceID, domainID, subdomain, fromDomain) +} diff --git a/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain_schema.go b/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain_schema.go new file mode 100644 index 000000000..964550e13 --- /dev/null +++ b/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain_schema.go @@ -0,0 +1,82 @@ +package routing_email_domain + +import ( + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const resourceName = "genesyscloud_routing_email_domain" + +// SetRegistrar registers all the resources and exporters in the package +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceRoutingEmailDomain()) + regInstance.RegisterDataSource(resourceName, DataSourceRoutingEmailDomain()) + regInstance.RegisterExporter(resourceName, RoutingEmailDomainExporter()) +} + +func ResourceRoutingEmailDomain() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud Routing Email Domain", + + CreateContext: provider.CreateWithPooledClient(createRoutingEmailDomain), + ReadContext: provider.ReadWithPooledClient(readRoutingEmailDomain), + UpdateContext: provider.UpdateWithPooledClient(updateRoutingEmailDomain), + DeleteContext: provider.DeleteWithPooledClient(deleteRoutingEmailDomain), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "domain_id": { + Description: "Unique Id of the domain such as: 'example.com'. If subdomain is true, the Genesys Cloud regional domain is appended. Changing the domain_id attribute will cause the routing_email_domain to be dropped and recreated with a new ID.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subdomain": { + Description: "Indicates if this a Genesys Cloud sub-domain. If true, then the appropriate DNS records are created for sending/receiving email. Changing the subdomain attribute will cause the routing_email_domain to be dropped and recreated with a new ID.", + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + "mail_from_domain": { + Description: "The custom MAIL FROM domain. This must be a subdomain of your email domain", + Type: schema.TypeString, + Optional: true, + }, + "custom_smtp_server_id": { + Description: "The ID of the custom SMTP server integration to use when sending outbound emails from this domain.", + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +// Returns the schema for the routing email domain +func DataSourceRoutingEmailDomain() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Genesys Cloud Email Domains. Select an email domain by name", + ReadContext: provider.ReadWithPooledClient(DataSourceRoutingEmailDomainRead), + Schema: map[string]*schema.Schema{ + "name": { + Description: "Email domain name.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func RoutingEmailDomainExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingEmailDomains), + UnResolvableAttributes: map[string]*schema.Schema{ + "custom_smtp_server_id": ResourceRoutingEmailDomain().Schema["custom_smtp_server_id"], + }, + } +} diff --git a/genesyscloud/resource_genesyscloud_routing_email_domain_test.go b/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain_test.go similarity index 99% rename from genesyscloud/resource_genesyscloud_routing_email_domain_test.go rename to genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain_test.go index b01929e81..832666a92 100644 --- a/genesyscloud/resource_genesyscloud_routing_email_domain_test.go +++ b/genesyscloud/routing_email_domain/resource_genesyscloud_routing_email_domain_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package routing_email_domain import ( "context" 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 index 0b66ad296..8c34e37ab 100644 --- 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 @@ -3,10 +3,10 @@ package routing_email_route import ( "fmt" "strings" - gcloud "terraform-provider-genesyscloud/genesyscloud" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -30,7 +30,7 @@ func TestAccDataSourceRoutingEmailRoute(t *testing.T) { Steps: []resource.TestStep{ { // Create email domain and basic route - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, 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 9037521ac..dd8dee2d5 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 @@ -6,6 +6,8 @@ import ( architectFlow "terraform-provider-genesyscloud/genesyscloud/architect_flow" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -33,9 +35,9 @@ func (r *registerTestInstance) registerTestResources() { defer r.resourceMapMutex.Unlock() providerResources[resourceName] = ResourceRoutingEmailRoute() - providerResources["genesyscloud_routing_email_domain"] = genesyscloud.ResourceRoutingEmailDomain() + providerResources["genesyscloud_routing_email_domain"] = routingEmailDomain.ResourceRoutingEmailDomain() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() - providerResources["genesyscloud_routing_language"] = genesyscloud.ResourceRoutingLanguage() + providerResources["genesyscloud_routing_language"] = routingLanguage.ResourceRoutingLanguage() providerResources["genesyscloud_routing_skill"] = genesyscloud.ResourceRoutingSkill() providerResources["genesyscloud_flow"] = architectFlow.ResourceArchitectFlow() providerResources["genesyscloud_routing_skill_group"] = genesyscloud.ResourceRoutingSkillGroup() 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 075bd7b7c..e8ebb5b46 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 @@ -9,6 +9,9 @@ import ( "terraform-provider-genesyscloud/genesyscloud/architect_flow" "terraform-provider-genesyscloud/genesyscloud/provider" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" + "terraform-provider-genesyscloud/genesyscloud/util" "testing" "time" @@ -56,7 +59,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { Steps: []resource.TestStep{ { // Confirm mutual exclusivity of reply_email_address and from_email - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -88,7 +91,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { }, { // Confirm mutual exclusivity of reply_email_address and auto_bcc - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -116,7 +119,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { }, { // Confirm mutual exclusivity of flow_id and queue_id - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -124,7 +127,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { ) + routingQueue.GenerateRoutingQueueResourceBasic( queueResource, queueName, - ) + gcloud.GenerateRoutingLanguageResource( + ) + routingLanguage.GenerateRoutingLanguageResource( langResource, langName, ) + gcloud.GenerateRoutingSkillResource( @@ -154,8 +157,8 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { }, CheckDestroy: testVerifyRoutingEmailRouteDestroyed, }) - - domainId = fmt.Sprintf("terraformroute.%s.com", strings.Replace(uuid.NewString(), "-", "", -1)) + CleanupRoutingEmailDomains() + domainId = fmt.Sprintf("terraformroutes.%s.com", strings.Replace(uuid.NewString(), "-", "", -1)) // Standard acceptance tests resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, @@ -163,7 +166,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { Steps: []resource.TestStep{ { // Create email domain and basic route - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -187,7 +190,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { }, { // Update email route and add a queue, language, and skill - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -196,7 +199,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { queueResource, queueName, - ) + gcloud.GenerateRoutingLanguageResource( + ) + routingLanguage.GenerateRoutingLanguageResource( langResource, langName, ) + gcloud.GenerateRoutingSkillResource( @@ -245,7 +248,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { }, { // Update email reply to true - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -253,7 +256,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { ) + routingQueue.GenerateRoutingQueueResourceBasic( queueResource, queueName, - ) + gcloud.GenerateRoutingLanguageResource( + ) + routingLanguage.GenerateRoutingLanguageResource( langResource, langName, ) + gcloud.GenerateRoutingSkillResource( @@ -307,7 +310,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { }, { // Update email reply to false and set a route id - Config: gcloud.GenerateRoutingEmailDomainResource( + Config: routingEmailDomain.GenerateRoutingEmailDomainResource( domainRes, domainId, util.FalseValue, @@ -315,7 +318,7 @@ func TestAccResourceRoutingEmailRoute(t *testing.T) { ) + routingQueue.GenerateRoutingQueueResourceBasic( queueResource, queueName, - ) + gcloud.GenerateRoutingLanguageResource( + ) + routingLanguage.GenerateRoutingLanguageResource( langResource, langName, ) + gcloud.GenerateRoutingSkillResource( diff --git a/genesyscloud/routing_language/data_source_genesyscloud_routing_language.go b/genesyscloud/routing_language/data_source_genesyscloud_routing_language.go new file mode 100644 index 000000000..fb231d336 --- /dev/null +++ b/genesyscloud/routing_language/data_source_genesyscloud_routing_language.go @@ -0,0 +1,34 @@ +package routing_language + +import ( + "context" + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/util" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceRoutingLanguageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + sdkConfig := m.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingLanguageProxy(sdkConfig) + name := d.Get("name").(string) + + // Find first non-deleted language by name. Retry in case new language is not yet indexed by search + return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { + languageId, resp, retryable, err := proxy.getRoutingLanguageIdByName(ctx, name) + if err != nil && !retryable { + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting language %s | error: %s", name, err), resp)) + } + if retryable { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error requesting language %s | error: %s", name, err), resp)) + } + + d.SetId(languageId) + return nil + }) +} diff --git a/genesyscloud/data_source_genesyscloud_routing_language_test.go b/genesyscloud/routing_language/data_source_genesyscloud_routing_language_test.go similarity index 98% rename from genesyscloud/data_source_genesyscloud_routing_language_test.go rename to genesyscloud/routing_language/data_source_genesyscloud_routing_language_test.go index 018c1d2e9..5b3a724f2 100644 --- a/genesyscloud/data_source_genesyscloud_routing_language_test.go +++ b/genesyscloud/routing_language/data_source_genesyscloud_routing_language_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package routing_language import ( "fmt" diff --git a/genesyscloud/routing_language/genesyscloud_routing_language_init_test.go b/genesyscloud/routing_language/genesyscloud_routing_language_init_test.go new file mode 100644 index 000000000..5642770cb --- /dev/null +++ b/genesyscloud/routing_language/genesyscloud_routing_language_init_test.go @@ -0,0 +1,60 @@ +package routing_language + +import ( + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* +The genesyscloud_routing_language_init_test.go file is used to initialize the data sources and resources +used in testing the routing_language 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[resourceName] = ResourceRoutingLanguage() +} + +// registerTestDataSources registers all data sources used in the tests. +func (r *registerTestInstance) registerTestDataSources() { + r.dataSourceMapMutex.Lock() + defer r.dataSourceMapMutex.Unlock() + + providerDataSources[resourceName] = DataSourceRoutingLanguage() +} + +// initTestResources initializes all test resources and data sources. +func initTestResources() { + providerDataSources = make(map[string]*schema.Resource) + providerResources = make(map[string]*schema.Resource) + + regInstance := ®isterTestInstance{} + + regInstance.registerTestResources() + regInstance.registerTestDataSources() +} + +// TestMain is a "setup" function called by the testing framework when run the test +func TestMain(m *testing.M) { + // Run setup function before starting the test suite for routing_language package + initTestResources() + + // Run the test suite for the routing_language package + m.Run() +} diff --git a/genesyscloud/routing_language/genesyscloud_routing_language_proxy.go b/genesyscloud/routing_language/genesyscloud_routing_language_proxy.go new file mode 100644 index 000000000..4e989950f --- /dev/null +++ b/genesyscloud/routing_language/genesyscloud_routing_language_proxy.go @@ -0,0 +1,154 @@ +package routing_language + +import ( + "context" + "fmt" + "log" + rc "terraform-provider-genesyscloud/genesyscloud/resource_cache" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +var internalProxy *routingLanguageProxy + +type getAllRoutingLanguagesFunc func(ctx context.Context, p *routingLanguageProxy, name string) (*[]platformclientv2.Language, *platformclientv2.APIResponse, error) +type createRoutingLanguageFunc func(ctx context.Context, p *routingLanguageProxy, language *platformclientv2.Language) (*platformclientv2.Language, *platformclientv2.APIResponse, error) +type getRoutingLanguageByIdFunc func(ctx context.Context, p *routingLanguageProxy, id string) (*platformclientv2.Language, *platformclientv2.APIResponse, error) +type getRoutingLanguageIdByNameFunc func(ctx context.Context, p *routingLanguageProxy, name string) (string, *platformclientv2.APIResponse, bool, error) +type deleteRoutingLanguageFunc func(ctx context.Context, p *routingLanguageProxy, id string) (*platformclientv2.APIResponse, error) + +// routingLanguageProxy contains all of the methods that call genesys cloud APIs. +type routingLanguageProxy struct { + clientConfig *platformclientv2.Configuration + routingApi *platformclientv2.RoutingApi + createRoutingLanguageAttr createRoutingLanguageFunc + getAllRoutingLanguagesAttr getAllRoutingLanguagesFunc + getRoutingLanguageIdByNameAttr getRoutingLanguageIdByNameFunc + getRoutingLanguageByIdAttr getRoutingLanguageByIdFunc + deleteRoutingLanguageAttr deleteRoutingLanguageFunc + routingLanguageCache rc.CacheInterface[platformclientv2.Language] +} + +// newRoutingLanguageProxy initializes the routing language proxy with all of the data needed to communicate with Genesys Cloud +func newRoutingLanguageProxy(clientConfig *platformclientv2.Configuration) *routingLanguageProxy { + api := platformclientv2.NewRoutingApiWithConfig(clientConfig) + routingLanguageCache := rc.NewResourceCache[platformclientv2.Language]() + return &routingLanguageProxy{ + clientConfig: clientConfig, + routingApi: api, + createRoutingLanguageAttr: createRoutingLanguageFn, + getAllRoutingLanguagesAttr: getAllRoutingLanguagesFn, + getRoutingLanguageIdByNameAttr: getRoutingLanguageIdByNameFn, + getRoutingLanguageByIdAttr: getRoutingLanguageByIdFn, + deleteRoutingLanguageAttr: deleteRoutingLanguageFn, + routingLanguageCache: routingLanguageCache, + } +} + +func getRoutingLanguageProxy(clientConfig *platformclientv2.Configuration) *routingLanguageProxy { + if internalProxy == nil { + internalProxy = newRoutingLanguageProxy(clientConfig) + } + return internalProxy +} + +// getRoutingLanguage retrieves all Genesys Cloud routing language +func (p *routingLanguageProxy) getAllRoutingLanguages(ctx context.Context, name string) (*[]platformclientv2.Language, *platformclientv2.APIResponse, error) { + return p.getAllRoutingLanguagesAttr(ctx, p, name) +} + +// createRoutingLanguage creates a Genesys Cloud routing language +func (p *routingLanguageProxy) createRoutingLanguage(ctx context.Context, routingLanguage *platformclientv2.Language) (*platformclientv2.Language, *platformclientv2.APIResponse, error) { + return p.createRoutingLanguageAttr(ctx, p, routingLanguage) +} + +// getRoutingLanguageById returns a single Genesys Cloud routing language by Id +func (p *routingLanguageProxy) getRoutingLanguageById(ctx context.Context, id string) (*platformclientv2.Language, *platformclientv2.APIResponse, error) { + return p.getRoutingLanguageByIdAttr(ctx, p, id) +} + +// getRoutingLanguageIdByName returns a single Genesys Cloud routing language by a name +func (p *routingLanguageProxy) getRoutingLanguageIdByName(ctx context.Context, name string) (string, *platformclientv2.APIResponse, bool, error) { + return p.getRoutingLanguageIdByNameAttr(ctx, p, name) +} + +// deleteRoutingLanguage deletes a Genesys Cloud routing language by Id +func (p *routingLanguageProxy) deleteRoutingLanguage(ctx context.Context, id string) (*platformclientv2.APIResponse, error) { + return p.deleteRoutingLanguageAttr(ctx, p, id) +} + +// getAllRoutingLanguageFn is the implementation for retrieving all routing language in Genesys Cloud +func getAllRoutingLanguagesFn(ctx context.Context, p *routingLanguageProxy, name string) (*[]platformclientv2.Language, *platformclientv2.APIResponse, error) { + var ( + allLanguages []platformclientv2.Language + response *platformclientv2.APIResponse + pageSize = 100 + ) + + languages, resp, err := p.routingApi.GetRoutingLanguages(pageSize, 1, "", name, []string{}) + if err != nil { + return nil, resp, fmt.Errorf("failed to get language: %v", err) + } + + if languages.Entities == nil || len(*languages.Entities) == 0 { + return &allLanguages, resp, nil + } + allLanguages = append(allLanguages, *languages.Entities...) + + for pageNum := 2; pageNum <= *languages.PageCount; pageNum++ { + languages, resp, err := p.routingApi.GetRoutingLanguages(pageSize, pageNum, "", name, []string{}) + if err != nil { + return nil, resp, fmt.Errorf("failed to get language: %v", err) + } + + response = resp + if languages.Entities == nil || len(*languages.Entities) == 0 { + break + } + allLanguages = append(allLanguages, *languages.Entities...) + } + + for _, language := range allLanguages { + rc.SetCache(p.routingLanguageCache, *language.Id, language) + } + + return &allLanguages, response, nil +} + +// createRoutingLanguageFn is an implementation function for creating a Genesys Cloud routing language +func createRoutingLanguageFn(ctx context.Context, p *routingLanguageProxy, routingLanguage *platformclientv2.Language) (*platformclientv2.Language, *platformclientv2.APIResponse, error) { + return p.routingApi.PostRoutingLanguages(*routingLanguage) +} + +// getRoutingLanguageByIdFn is an implementation of the function to get a Genesys Cloud routing language by Id +func getRoutingLanguageByIdFn(ctx context.Context, p *routingLanguageProxy, id string) (*platformclientv2.Language, *platformclientv2.APIResponse, error) { + if language := rc.GetCacheItem(p.routingLanguageCache, id); language != nil { + return language, nil, nil + } + return p.routingApi.GetRoutingLanguage(id) +} + +// getRoutingLanguageIdByNameFn is an implementation of the function to get a Genesys Cloud routing language by name +func getRoutingLanguageIdByNameFn(ctx context.Context, p *routingLanguageProxy, name string) (string, *platformclientv2.APIResponse, bool, error) { + languages, resp, err := getAllRoutingLanguagesFn(ctx, p, name) + if err != nil { + return "", resp, false, err + } + + if languages == nil || len(*languages) == 0 { + return "", resp, true, fmt.Errorf("no routing language found with name %s", name) + } + + for _, language := range *languages { + if *language.Name == name { + log.Printf("Retrieved the routing language id %s by name %s", *language.Id, name) + return *language.Id, resp, false, nil + } + } + return "", resp, true, fmt.Errorf("unable to find routing language with name %s", name) +} + +// deleteRoutingLanguageFn is an implementation function for deleting a Genesys Cloud routing language +func deleteRoutingLanguageFn(ctx context.Context, p *routingLanguageProxy, id string) (*platformclientv2.APIResponse, error) { + return p.routingApi.DeleteRoutingLanguage(id) +} diff --git a/genesyscloud/routing_language/resource_genesyscloud_routing_language.go b/genesyscloud/routing_language/resource_genesyscloud_routing_language.go new file mode 100644 index 000000000..3ffee932a --- /dev/null +++ b/genesyscloud/routing_language/resource_genesyscloud_routing_language.go @@ -0,0 +1,119 @@ +package routing_language + +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" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +func getAllRoutingLanguages(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { + resources := make(resourceExporter.ResourceIDMetaMap) + proxy := getRoutingLanguageProxy(clientConfig) + + languages, resp, getErr := proxy.getAllRoutingLanguages(ctx, "") + if getErr != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get page of languages: %v", getErr), resp) + } + + if languages == nil || len(*languages) == 0 { + return resources, nil + } + + for _, language := range *languages { + if language.State != nil && *language.State != "deleted" { + resources[*language.Id] = &resourceExporter.ResourceMeta{Name: *language.Name} + } + } + return resources, nil +} + +func createRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingLanguageProxy(sdkConfig) + name := d.Get("name").(string) + + log.Printf("Creating language %s", name) + + language, resp, err := proxy.createRoutingLanguage(ctx, &platformclientv2.Language{ + Name: &name, + }) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create language %s error: %s", name, err), resp) + } + + d.SetId(*language.Id) + + log.Printf("Created language %s %s", name, *language.Id) + return readRoutingLanguage(ctx, d, meta) +} + +func readRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingLanguageProxy(sdkConfig) + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingLanguage(), constants.DefaultConsistencyChecks, resourceName) + + log.Printf("Reading routing language %s", d.Id()) + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + language, resp, getErr := proxy.getRoutingLanguageById(ctx, d.Id()) + if getErr != nil { + if util.IsStatus404(resp) { + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read language %s | error: %s", d.Id(), getErr), resp)) + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read language %s | error: %s", d.Id(), getErr), resp)) + } + + if language.State != nil && *language.State == "deleted" { + d.SetId("") + return nil + } + + _ = d.Set("name", *language.Name) + log.Printf("Read routing language %s %s", d.Id(), *language.Name) + return cc.CheckState(d) + }) +} + +func deleteRoutingLanguage(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + sdkConfig := meta.(*provider.ProviderMeta).ClientConfig + proxy := getRoutingLanguageProxy(sdkConfig) + name := d.Get("name").(string) + + log.Printf("Deleting language %s", name) + resp, err := proxy.deleteRoutingLanguage(ctx, d.Id()) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete language %s error: %s", name, err), resp) + } + + return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError { + routingLanguage, resp, err := proxy.getRoutingLanguageById(ctx, d.Id()) + if err != nil { + if util.IsStatus404(resp) { + // Routing language deleted + log.Printf("Deleted Routing language %s", d.Id()) + return nil + } + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Error deleting Routing language %s | error: %s", d.Id(), err), resp)) + } + + if routingLanguage.State != nil && *routingLanguage.State == "deleted" { + // Routing language deleted + log.Printf("Deleted Routing language %s", d.Id()) + return nil + } + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Routing language %s still exists", d.Id()), resp)) + }) +} diff --git a/genesyscloud/routing_language/resource_genesyscloud_routing_language_schema.go b/genesyscloud/routing_language/resource_genesyscloud_routing_language_schema.go new file mode 100644 index 000000000..6ae346a4e --- /dev/null +++ b/genesyscloud/routing_language/resource_genesyscloud_routing_language_schema.go @@ -0,0 +1,70 @@ +package routing_language + +import ( + "fmt" + "terraform-provider-genesyscloud/genesyscloud/provider" + resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" + registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const resourceName = "genesyscloud_routing_language" + +// SetRegistrar registers all of the resources, datasources and exporters in the package +func SetRegistrar(regInstance registrar.Registrar) { + regInstance.RegisterResource(resourceName, ResourceRoutingLanguage()) + regInstance.RegisterExporter(resourceName, RoutingLanguageExporter()) + regInstance.RegisterDataSource(resourceName, DataSourceRoutingLanguage()) +} + +func ResourceRoutingLanguage() *schema.Resource { + return &schema.Resource{ + Description: "Genesys Cloud Routing Language", + + CreateContext: provider.CreateWithPooledClient(createRoutingLanguage), + ReadContext: provider.ReadWithPooledClient(readRoutingLanguage), + DeleteContext: provider.DeleteWithPooledClient(deleteRoutingLanguage), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "name": { + Description: "Language name. Changing the language_name attribute will cause the language object to be dropped and recreated with a new ID.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func DataSourceRoutingLanguage() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Genesys Cloud Routing Languages. Select a language by name.", + ReadContext: provider.ReadWithPooledClient(dataSourceRoutingLanguageRead), + Schema: map[string]*schema.Schema{ + "name": { + Description: "Language name.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func RoutingLanguageExporter() *resourceExporter.ResourceExporter { + return &resourceExporter.ResourceExporter{ + GetResourcesFunc: provider.GetAllWithPooledClient(getAllRoutingLanguages), + } +} + +func GenerateRoutingLanguageResource( + resourceID string, + name string) string { + return fmt.Sprintf(`resource "genesyscloud_routing_language" "%s" { + name = "%s" + } + `, resourceID, name) +} diff --git a/genesyscloud/resource_genesyscloud_routing_language_test.go b/genesyscloud/routing_language/resource_genesyscloud_routing_language_test.go similarity index 98% rename from genesyscloud/resource_genesyscloud_routing_language_test.go rename to genesyscloud/routing_language/resource_genesyscloud_routing_language_test.go index 5d0349c8a..b2dfbe573 100644 --- a/genesyscloud/resource_genesyscloud_routing_language_test.go +++ b/genesyscloud/routing_language/resource_genesyscloud_routing_language_test.go @@ -1,4 +1,4 @@ -package genesyscloud +package routing_language import ( "fmt" diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go index c1a359274..0d86f40fd 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go @@ -24,8 +24,7 @@ import ( ) var ( - sdkConfig *platformclientv2.Configuration - mu sync.Mutex + mu sync.Mutex ) func TestAccResourceRoutingQueueBasic(t *testing.T) { @@ -60,6 +59,7 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { testUserEmail = uuid.NewString() + "@examplestest.com" callbackHours = "7" callbackHours2 = "7" + userID string ) resource.Test(t, resource.TestCase{ @@ -119,6 +119,15 @@ 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 { + 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 + }, ), }, { @@ -182,9 +191,12 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { ResourceName: "genesyscloud_routing_queue." + queueResource1, ImportState: true, ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + checkUserDeleted(userID), + ), }, }, - CheckDestroy: testVerifyQueuesDestroyed, + CheckDestroy: testVerifyQueuesAndUsersDestroyed, }) } @@ -225,6 +237,7 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { testUserResource = "user_resource1" testUserName = "nameUser1" + uuid.NewString() testUserEmail = uuid.NewString() + "@example.com" + userID string ) resource.Test(t, resource.TestCase{ @@ -413,19 +426,74 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { validateMediaSettings(queueResource1, "media_settings_email", alertTimeout1, util.FalseValue, slPercent1, slDuration1), validateMediaSettings(queueResource1, "media_settings_message", alertTimeout1, util.FalseValue, slPercent1, slDuration1), func(s *terraform.State) error { - time.Sleep(60 * time.Second) // Wait for 60 seconds for resource 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 }, ), + + PreventPostDestroyRefresh: true, }, { + Config: GenerateRoutingQueueResource( + queueResource1, + queueName1, + queueDesc1, + util.NullValue, // MANDATORY_TIMEOUT + "200000", // acw_timeout + util.NullValue, // ALL + util.NullValue, // auto_answer_only true + util.NullValue, // No calling party name + util.NullValue, // No calling party number + util.NullValue, // enable_transcription false + util.FalseValue, // suppress_in_queue_call_recording false + util.NullValue, // enable_manual_assignment false + strconv.Quote("TimestampAndPriority"), + GenerateMediaSettings("media_settings_call", alertTimeout1, util.FalseValue, slPercent1, slDuration1), + GenerateMediaSettings("media_settings_callback", alertTimeout1, util.FalseValue, slPercent1, slDuration1), + GenerateMediaSettings("media_settings_chat", alertTimeout1, util.FalseValue, slPercent1, slDuration1), + GenerateMediaSettings("media_settings_email", alertTimeout1, util.FalseValue, slPercent1, slDuration1), + GenerateMediaSettings("media_settings_message", alertTimeout1, util.FalseValue, slPercent1, slDuration1), + GenerateConditionalGroupRoutingRules( + util.NullValue, // queue_id (queue_id in the first rule should be omitted) + conditionalGroupRouting1Operator, // operator + conditionalGroupRouting1Metric, // metric + conditionalGroupRouting1ConditionValue, // condition_value + conditionalGroupRouting1WaitSeconds, // wait_seconds + GenerateConditionalGroupRoutingRuleGroup( + "genesyscloud_routing_skill_group."+skillGroupResourceId+".id", // group_id + conditionalGroupRouting1GroupType, // group_type + ), + ), + GenerateConditionalGroupRoutingRules( + "genesyscloud_routing_queue."+queueResource2+".id", // queue_id + conditionalGroupRouting2Operator, // operator + conditionalGroupRouting2Metric, // metric + conditionalGroupRouting2ConditionValue, // condition_value + conditionalGroupRouting2WaitSeconds, // wait_seconds + GenerateConditionalGroupRoutingRuleGroup( + "genesyscloud_group."+groupResourceId+".id", // group_id + conditionalGroupRouting2GroupType, // group_type + ), + ), + "skill_groups = [genesyscloud_routing_skill_group."+skillGroupResourceId+".id]", + "groups = [genesyscloud_group."+groupResourceId+".id]", + ), // Import/Read ResourceName: "genesyscloud_routing_queue." + queueResource1, ImportState: true, ImportStateVerify: true, + Destroy: true, }, }, - CheckDestroy: testVerifyQueuesDestroyed, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyQueuesAndUsersDestroyed(state) + }, }) } @@ -692,117 +760,6 @@ func TestAccResourceRoutingQueueFlows(t *testing.T) { }) } -func TestAccResourceRoutingQueueMembers(t *testing.T) { - var ( - queueResource = "test-queue-members" - queueName = "Terraform Test Queue3-" + uuid.NewString() - queueMemberResource1 = "test-queue-user1" - queueMemberResource2 = "test-queue-user2" - queueMemberEmail1 = "terraform1-" + uuid.NewString() + "@queue.com" - queueMemberEmail2 = "terraform2-" + uuid.NewString() + "@queue.com" - queueMemberName1 = "Henry Terraform Test" - queueMemberName2 = "Amanda Terraform Test" - defaultQueueRingNum = "1" - queueRingNum = "3" - ) - resource.Test(t, resource.TestCase{ - PreCheck: func() { util.TestAccPreCheck(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: genesyscloud.GenerateBasicUserResource( - queueMemberResource1, - queueMemberEmail1, - queueMemberName1, - ) + GenerateRoutingQueueResourceBasic( - queueResource, - queueName, - GenerateMemberBlock("genesyscloud_user."+queueMemberResource1+".id", util.NullValue), - ), - Check: resource.ComposeTestCheckFunc( - validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource1, defaultQueueRingNum), - ), - }, - { - PreConfig: func() { - // Wait for a specified duration to avoid runtime error - time.Sleep(30 * time.Second) - }, - // Update with another queue member and modify rings - Config: genesyscloud.GenerateBasicUserResource( - queueMemberResource1, - queueMemberEmail1, - queueMemberName1, - ) + genesyscloud.GenerateBasicUserResource( - 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), - validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource2, queueRingNum), - ), - }, - { - // Remove a queue member - Config: GenerateRoutingQueueResourceBasic( - queueResource, - queueName, - GenerateMemberBlock("genesyscloud_user."+queueMemberResource2+".id", queueRingNum), - GenerateBullseyeSettings("10"), - GenerateBullseyeSettings("10"), - GenerateBullseyeSettings("10"), - ) + genesyscloud.GenerateBasicUserResource( - queueMemberResource1, - queueMemberEmail1, - queueMemberName1, - ) + genesyscloud.GenerateBasicUserResource( - queueMemberResource2, - queueMemberEmail2, - queueMemberName2, - ), - Check: resource.ComposeTestCheckFunc( - validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource2, queueRingNum), - ), - }, - { - // Remove all queue members - Config: GenerateRoutingQueueResourceBasic( - queueResource, - queueName, - "members = []", - GenerateBullseyeSettings("10"), - GenerateBullseyeSettings("10"), - GenerateBullseyeSettings("10"), - ), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr("genesyscloud_routing_queue."+queueResource, "members.%"), - ), - }, - { - // Import/Read - ResourceName: "genesyscloud_routing_queue." + queueResource, - ImportState: true, - ImportStateVerify: true, - }, - }, - CheckDestroy: testVerifyQueuesDestroyed, - }) -} - func TestAccResourceRoutingQueueSkillgroupMembers(t *testing.T) { var ( queueResourceId = "test-queue" @@ -901,6 +858,114 @@ func TestAccResourceRoutingQueueSkillgroupMembers(t *testing.T) { }) } +func TestAccResourceRoutingQueueMembers(t *testing.T) { + var ( + queueResource = "test-queue-members" + queueName = "Terraform Test Queue3-" + uuid.NewString() + queueMemberResource1 = "test-queue-user1" + queueMemberResource2 = "test-queue-user2" + queueMemberEmail1 = "terraform1-" + uuid.NewString() + "@queue1.com" + queueMemberEmail2 = "terraform2-" + uuid.NewString() + "@queue2.com" + queueMemberName1 = "Henry Terraform Test" + queueMemberName2 = "Amanda Terraform Test" + defaultQueueRingNum = "1" + queueRingNum = "3" + ) + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + // Create + 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() { + // Wait for a specified duration to avoid runtime error + time.Sleep(30 * time.Second) + }, + // Update with another queue member and modify rings + Config: genesyscloud.GenerateBasicUserResource( + queueMemberResource1, + queueMemberEmail1, + queueMemberName1, + ) + genesyscloud.GenerateBasicUserResource( + 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), + validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource2, queueRingNum), + ), + }, + { + // Remove a queue member + Config: genesyscloud.GenerateBasicUserResource( + queueMemberResource2, + queueMemberEmail2, + queueMemberName2, + ) + GenerateRoutingQueueResourceBasic( + queueResource, + queueName, + GenerateMemberBlock("genesyscloud_user."+queueMemberResource2+".id", queueRingNum), + GenerateBullseyeSettings("10"), + GenerateBullseyeSettings("10"), + GenerateBullseyeSettings("10"), + ), + Check: resource.ComposeTestCheckFunc( + validateMember("genesyscloud_routing_queue."+queueResource, "genesyscloud_user."+queueMemberResource2, queueRingNum), + ), + Destroy: true, + }, + { + // Remove all queue members + Config: GenerateRoutingQueueResourceBasic( + queueResource, + queueName, + "members = []", + GenerateBullseyeSettings("10"), + GenerateBullseyeSettings("10"), + GenerateBullseyeSettings("10"), + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr("genesyscloud_routing_queue."+queueResource, "members.%"), + ), + }, + { + // Import/Read + ResourceName: "genesyscloud_routing_queue." + queueResource, + ImportState: true, + ImportStateVerify: true, + Destroy: true, + }, + }, + CheckDestroy: testVerifyQueuesAndUsersDestroyed, + }) +} func TestAccResourceRoutingQueueWrapupCodes(t *testing.T) { var ( queueResource = "test-queue-wrapup" @@ -1258,6 +1323,43 @@ func testVerifyQueuesDestroyed(state *terraform.State) error { return nil } +func testVerifyQueuesAndUsersDestroyed(state *terraform.State) error { + routingAPI := platformclientv2.NewRoutingApi() + usersAPI := platformclientv2.NewUsersApi() + for _, rs := range state.RootModule().Resources { + if rs.Type == "genesyscloud_routing_queue" { + queue, resp, err := routingAPI.GetRoutingQueue(rs.Primary.ID) + if queue != nil { + return fmt.Errorf("Queue (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Queue not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + if rs.Type == "genesyscloud_user" { + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } + user, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + if user != nil { + return fmt.Errorf("User Resource (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // User not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + } + // Success. All queues destroyed + return nil +} + func validateMediaSettings(resourceName, settingsAttr, alertingTimeout, enableAutoAnswer, slPercent, slDurationMs string) resource.TestCheckFunc { return resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("genesyscloud_routing_queue."+resourceName, settingsAttr+".0.alerting_timeout_sec", alertingTimeout), @@ -1515,6 +1617,8 @@ func TestAccResourceRoutingQueueSkillGroups(t *testing.T) { Check: resource.ComposeTestCheckFunc( validateGroups("genesyscloud_routing_queue."+queueResource, "genesyscloud_routing_skill_group."+skillGroupResource, "genesyscloud_group."+groupResource), ), + + PreventPostDestroyRefresh: true, }, { // Import/Read @@ -1524,15 +1628,13 @@ 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 - }, - ), + Destroy: true, }, }, - CheckDestroy: testVerifyQueuesDestroyed, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(45 * time.Second) + return testVerifyQueuesAndUsersDestroyed(state) + }, }) } @@ -1548,9 +1650,8 @@ func generateUserWithCustomAttrs(resourceID string, email string, name string, a func checkUserDeleted(id string) resource.TestCheckFunc { log.Printf("Fetching user with ID: %s\n", id) return func(s *terraform.State) error { - maxAttempts := 18 + maxAttempts := 30 for i := 0; i < maxAttempts; i++ { - deleted, err := isUserDeleted(id) if err != nil { return err @@ -1568,7 +1669,7 @@ func isUserDeleted(id string) (bool, error) { mu.Lock() defer mu.Unlock() - usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + usersAPI := platformclientv2.NewUsersApi() // Attempt to get the user _, response, err := usersAPI.GetUser(id, nil, "", "") diff --git a/genesyscloud/routing_queue_conditional_group_routing/genesyscloud_routing_queue_conditional_group_routing_proxy.go b/genesyscloud/routing_queue_conditional_group_routing/genesyscloud_routing_queue_conditional_group_routing_proxy.go index c30bd06f0..7ae261a11 100644 --- a/genesyscloud/routing_queue_conditional_group_routing/genesyscloud_routing_queue_conditional_group_routing_proxy.go +++ b/genesyscloud/routing_queue_conditional_group_routing/genesyscloud_routing_queue_conditional_group_routing_proxy.go @@ -15,12 +15,14 @@ var internalProxy *routingQueueConditionalGroupRoutingProxy // Type definitions for each func on our proxy so we can easily mock them out later type getRoutingQueueConditionRoutingFunc func(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) type updateRoutingQueueConditionRoutingFunc func(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string, rules *[]platformclientv2.Conditionalgrouproutingrule) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) +type getRoutingQueueByIdFunc func(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, id string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) -// routingQueueConditionalGroupRoutingProxy contains all of the methods that call genesys cloud APIs. +// routingQueueConditionalGroupRoutingProxy contains all methods that call genesys cloud APIs. type routingQueueConditionalGroupRoutingProxy struct { clientConfig *platformclientv2.Configuration routingApi *platformclientv2.RoutingApi getRoutingQueueConditionRoutingAttr getRoutingQueueConditionRoutingFunc + getRoutingQueueByIdAttr getRoutingQueueByIdFunc updateRoutingQueueConditionRoutingAttr updateRoutingQueueConditionRoutingFunc routingQueueProxy *routingQueue.RoutingQueueProxy } @@ -35,6 +37,7 @@ func newRoutingQueueConditionalGroupRoutingProxy(clientConfig *platformclientv2. routingApi: api, getRoutingQueueConditionRoutingAttr: getRoutingQueueConditionRoutingFn, updateRoutingQueueConditionRoutingAttr: updateRoutingQueueConditionRoutingFn, + getRoutingQueueByIdAttr: getRoutingQueueByIdFn, routingQueueProxy: routingQueueProxy, } } @@ -48,6 +51,11 @@ func getRoutingQueueConditionalGroupRoutingProxy(clientConfig *platformclientv2. return internalProxy } +// getRoutingQueueById get a queue by ID +func (p *routingQueueConditionalGroupRoutingProxy) getRoutingQueueById(ctx context.Context, id string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.getRoutingQueueByIdAttr(ctx, p, id) +} + // getRoutingQueueConditionRouting gets the conditional group routing rules for a queue func (p *routingQueueConditionalGroupRoutingProxy) getRoutingQueueConditionRouting(ctx context.Context, queueId string) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) { return p.getRoutingQueueConditionRoutingAttr(ctx, p, queueId) @@ -68,7 +76,7 @@ func getRoutingQueueConditionRoutingFn(ctx context.Context, p *routingQueueCondi queue = rc.GetCacheItem(p.routingQueueProxy.RoutingQueueCache, queueId) if queue == nil { - queue, resp, err = p.routingApi.GetRoutingQueue(queueId) + queue, resp, err = p.getRoutingQueueById(ctx, queueId) if err != nil { return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err) } @@ -78,13 +86,13 @@ func getRoutingQueueConditionRoutingFn(ctx context.Context, p *routingQueueCondi return queue.ConditionalGroupRouting.Rules, resp, nil } - return nil, resp, fmt.Errorf("no conditional group routing rules found for queue %s", queueId) + return nil, resp, nil } // updateRoutingQueueConditionRoutingFn is an implementation function for updating the conditional group routing rules for a queue func updateRoutingQueueConditionRoutingFn(ctx context.Context, p *routingQueueConditionalGroupRoutingProxy, queueId string, rules *[]platformclientv2.Conditionalgrouproutingrule) (*[]platformclientv2.Conditionalgrouproutingrule, *platformclientv2.APIResponse, error) { // Get the routing queue the rules belong to - queue, resp, err := p.routingApi.GetRoutingQueue(queueId) + queue, resp, err := p.getRoutingQueueById(ctx, queueId) if err != nil { return nil, resp, fmt.Errorf("error when reading queue %s: %s", queueId, err) } @@ -140,5 +148,10 @@ func updateRoutingQueueConditionRoutingFn(ctx context.Context, p *routingQueueCo return queue.ConditionalGroupRouting.Rules, resp, nil } - return nil, resp, fmt.Errorf("no conditional group routing rules found for queue %s", queueId) + return nil, resp, nil +} + +// getRoutingQueueByIdFn is an implementation function for getting a queue by ID +func getRoutingQueueByIdFn(_ context.Context, p *routingQueueConditionalGroupRoutingProxy, id string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.routingApi.GetRoutingQueue(id) } diff --git a/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing.go b/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing.go index 3877f16fd..4046d93e4 100644 --- a/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing.go +++ b/genesyscloud/routing_queue_conditional_group_routing/resource_genesyscloud_routing_queue_conditional_group_routing.go @@ -70,8 +70,8 @@ func readRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.Reso cc := consistencyChecker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingQueueConditionalGroupRouting(), constants.DefaultConsistencyChecks, resourceName) queueId := strings.Split(d.Id(), "/")[0] - log.Printf("Reading routing queue %s conditional group routing rules", queueId) return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { + log.Printf("Reading routing queue %s conditional group routing rules", queueId) sdkRules, resp, getErr := proxy.getRoutingQueueConditionRouting(ctx, queueId) if getErr != nil { if util.IsStatus404(resp) { @@ -79,6 +79,7 @@ func readRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.Reso } return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to read conditional group routing for queue %s | error: %s", queueId, getErr), resp)) } + log.Printf("Read routing queue %s conditional group routing rules", queueId) _ = d.Set("queue_id", queueId) _ = d.Set("rules", flattenConditionalGroupRouting(sdkRules)) @@ -101,7 +102,7 @@ func updateRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.Re sdkRules, err := buildConditionalGroupRouting(rules) if err != nil { - return util.BuildDiagnosticError(resourceName, fmt.Sprintf("Error building conditional group routing"), err) + return util.BuildDiagnosticError(resourceName, "Error building conditional group routing", err) } log.Printf("updating conditional group routing rules for queue %s", queueId) @@ -123,29 +124,29 @@ func deleteRoutingQueueConditionalRoutingGroup(ctx context.Context, d *schema.Re log.Printf("Removing rules from queue %s", queueId) // check if routing queue still exists before trying to remove rules - _, resp, err := proxy.getRoutingQueueConditionRouting(ctx, queueId) - if err != nil { + log.Printf("Reading queue '%s' to verify it exists before trying to remove its CGR rules", queueId) + if _, resp, err := proxy.getRoutingQueueById(ctx, queueId); err != nil { if util.IsStatus404(resp) { log.Printf("conditional group routing rules parent queue %s already deleted", queueId) return nil } + log.Printf("Failed to read routing queue '%s': %v", queueId, err) } // To delete conditional group routing, update the queue with no rules + log.Printf("Updating routing queue '%s' to have no CGR rules", queueId) var newRules []platformclientv2.Conditionalgrouproutingrule - _, resp, err = proxy.updateRoutingQueueConditionRouting(ctx, queueId, &newRules) - if err != nil && !strings.Contains(err.Error(), "no conditional group routing rules found for queue") { - + if _, resp, err := proxy.updateRoutingQueueConditionRouting(ctx, queueId, &newRules); err != nil { return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to remove rules from queue %s: %s", queueId, err), resp) } // Verify there are no rules - rules, resp, err := proxy.getRoutingQueueConditionRouting(ctx, queueId) - if rules != nil { + log.Printf("Reading queue '%s' CGR rules to verify that they have been removed", queueId) + if rules, resp, err := proxy.getRoutingQueueConditionRouting(ctx, queueId); rules != nil { return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("conditional group routing rules still exist for queue %s: %s", queueId, err), resp) } - log.Printf("Removed rules from queue %s", queueId) + log.Printf("Successfully removed rules from queue %s", queueId) return nil } @@ -194,6 +195,10 @@ func buildConditionalGroupRouting(rules []interface{}) ([]platformclientv2.Condi } func flattenConditionalGroupRouting(sdkRules *[]platformclientv2.Conditionalgrouproutingrule) []interface{} { + if sdkRules == nil { + return nil + } + var rules []interface{} for i, sdkRule := range *sdkRules { rule := make(map[string]interface{}) 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 e910fb72c..259314613 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 @@ -22,8 +22,7 @@ import ( ) var ( - sdkConfig *platformclientv2.Configuration - mu sync.Mutex + mu sync.Mutex ) func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { @@ -260,17 +259,21 @@ func TestAccResourceRoutingQueueConditionalGroupRouting(t *testing.T) { return nil }, ), + + PreventPostDestroyRefresh: true, }, { // Import/Read ResourceName: "genesyscloud_routing_queue_conditional_group_routing." + conditionalGroupRoutingResource, ImportState: true, ImportStateVerify: true, - Check: resource.ComposeTestCheckFunc( - checkUserDeleted(userID), - ), + Destroy: true, }, }, + CheckDestroy: func(state *terraform.State) error { + time.Sleep(40 * time.Second) + return testVerifyGroupsAndUsersDestroyed(state) + }, }) } @@ -334,7 +337,7 @@ func generateUserWithCustomAttrs(resourceID string, email string, name string, a func checkUserDeleted(id string) resource.TestCheckFunc { log.Printf("Fetching user with ID: %s\n", id) return func(s *terraform.State) error { - maxAttempts := 18 + maxAttempts := 30 for i := 0; i < maxAttempts; i++ { deleted, err := isUserDeleted(id) @@ -354,7 +357,7 @@ func isUserDeleted(id string) (bool, error) { mu.Lock() defer mu.Unlock() - usersAPI := platformclientv2.NewUsersApiWithConfig(sdkConfig) + usersAPI := platformclientv2.NewUsersApi() // Attempt to get the user _, response, err := usersAPI.GetUser(id, nil, "", "") @@ -372,3 +375,40 @@ func isUserDeleted(id string) (bool, error) { // If user is found, it means the user is not deleted return false, nil } + +func testVerifyGroupsAndUsersDestroyed(state *terraform.State) error { + groupsAPI := platformclientv2.NewGroupsApi() + usersAPI := platformclientv2.NewUsersApi() + for _, rs := range state.RootModule().Resources { + if rs.Type == "genesyscloud_group" { + group, resp, err := groupsAPI.GetGroup(rs.Primary.ID) + if group != nil { + return fmt.Errorf("Group (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // Group not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + if rs.Type == "genesyscloud_user" { + err := checkUserDeleted(rs.Primary.ID)(state) + if err != nil { + continue + } + user, resp, err := usersAPI.GetUser(rs.Primary.ID, nil, "", "") + if user != nil { + return fmt.Errorf("User Resource (%s) still exists", rs.Primary.ID) + } else if util.IsStatus404(resp) { + // User not found as expected + continue + } else { + // Unexpected error + return fmt.Errorf("Unexpected error: %s", err) + } + } + + } + return nil +} diff --git a/genesyscloud/routing_queue_outbound_email_address/genesyscloud_routing_queue_outbound_email_address_init_test.go b/genesyscloud/routing_queue_outbound_email_address/genesyscloud_routing_queue_outbound_email_address_init_test.go index bc41e499e..f302ac073 100644 --- a/genesyscloud/routing_queue_outbound_email_address/genesyscloud_routing_queue_outbound_email_address_init_test.go +++ b/genesyscloud/routing_queue_outbound_email_address/genesyscloud_routing_queue_outbound_email_address_init_test.go @@ -3,9 +3,10 @@ package routing_queue_outbound_email_address import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "sync" - gcloud "terraform-provider-genesyscloud/genesyscloud" routingEmailRoute "terraform-provider-genesyscloud/genesyscloud/routing_email_route" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" + "testing" ) @@ -29,7 +30,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources[resourceName] = ResourceRoutingQueueOutboundEmailAddress() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() providerResources["genesyscloud_routing_email_route"] = routingEmailRoute.ResourceRoutingEmailRoute() - providerResources["genesyscloud_routing_email_domain"] = gcloud.ResourceRoutingEmailDomain() + providerResources["genesyscloud_routing_email_domain"] = routingEmailDomain.ResourceRoutingEmailDomain() } // initTestResources initializes all test resources and data sources. diff --git a/genesyscloud/routing_queue_outbound_email_address/resource_genesyscloud_routing_queue_outbound_email_address_test.go b/genesyscloud/routing_queue_outbound_email_address/resource_genesyscloud_routing_queue_outbound_email_address_test.go index 534bd35bf..8096e6388 100644 --- a/genesyscloud/routing_queue_outbound_email_address/resource_genesyscloud_routing_queue_outbound_email_address_test.go +++ b/genesyscloud/routing_queue_outbound_email_address/resource_genesyscloud_routing_queue_outbound_email_address_test.go @@ -5,8 +5,8 @@ import ( "log" "os" "strings" - gcloud "terraform-provider-genesyscloud/genesyscloud" "terraform-provider-genesyscloud/genesyscloud/provider" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" routingEmailRoute "terraform-provider-genesyscloud/genesyscloud/routing_email_route" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" "terraform-provider-genesyscloud/genesyscloud/util" @@ -72,7 +72,7 @@ func TestAccResourceRoutingQueueOutboundEmailAddress(t *testing.T) { Config: routingQueue.GenerateRoutingQueueResourceBasic( queueResource, queueName1, - ) + gcloud.GenerateRoutingEmailDomainResource( + ) + routingEmailDomain.GenerateRoutingEmailDomainResource( domainResource, domainId, util.FalseValue, diff --git a/genesyscloud/routing_sms_addresses/data_source_genesyscloud_routing_sms_addresses_test.go b/genesyscloud/routing_sms_addresses/data_source_genesyscloud_routing_sms_addresses_test.go index a17585c89..0a7a376e2 100644 --- a/genesyscloud/routing_sms_addresses/data_source_genesyscloud_routing_sms_addresses_test.go +++ b/genesyscloud/routing_sms_addresses/data_source_genesyscloud_routing_sms_addresses_test.go @@ -2,21 +2,24 @@ package genesyscloud import ( "fmt" + "os" "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 TestAccDataSourceSmsAddressProdOrg(t *testing.T) { - t.Skip("Skip this test as it will only pass in a prod org") + //this test as it will only pass in a prod org + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" { + t.Skip("This test as it will only pass in a prod org") + } var ( addressRes = "addressRes" addressData = "addressData" - name = "Address" + uuid.NewString() + name = "name-1" ) resource.Test(t, resource.TestCase{ @@ -27,11 +30,11 @@ func TestAccDataSourceSmsAddressProdOrg(t *testing.T) { Config: generateRoutingSmsAddressesResource( addressRes, name, - "Main street", - "New York", - "New York", - "AA34HH", - "US", + "street-1", + "city-1", + "region-1", + "postal-code-1", + "country-code-1", util.FalseValue, ) + generateSmsAddressDataSource( addressData, @@ -51,6 +54,9 @@ func TestAccDataSourceSmsAddressProdOrg(t *testing.T) { // If running in a prod org this test can be removed/skipped, it's only intended as a backup test for test orgs func TestAccDataSourceSmsAddressTestOrg(t *testing.T) { + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "us-east-1" { + t.Skip("Test intended only for test org") + } var ( addressRes = "addressRes" addressData = "addressData" @@ -69,8 +75,8 @@ func TestAccDataSourceSmsAddressTestOrg(t *testing.T) { "street-1", "city-1", "region-1", - "postal-code-1", - "country-code-1", + "90080", + "US", util.TrueValue, ) + generateSmsAddressDataSource( addressData, diff --git a/genesyscloud/routing_sms_addresses/resource_genesyscloud_routing_sms_addresses_test.go b/genesyscloud/routing_sms_addresses/resource_genesyscloud_routing_sms_addresses_test.go index 5ea01bb2c..629a2c316 100644 --- a/genesyscloud/routing_sms_addresses/resource_genesyscloud_routing_sms_addresses_test.go +++ b/genesyscloud/routing_sms_addresses/resource_genesyscloud_routing_sms_addresses_test.go @@ -2,27 +2,29 @@ package genesyscloud import ( "fmt" + "os" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" - "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/v133/platformclientv2" ) func TestAccResourceRoutingSmsAddressesProdOrg(t *testing.T) { - // If running in a prod org remove the below line - t.Skip("This test will only pass in a prod org") + // This test is valid only for prod + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "tca" { + t.Skip("This test is valid only for prod") + } var ( - resourceName = "sms-address1" - name = "address name-" + uuid.NewString() - street = "Main street" - city = "Galway" - region = "Galway" - postalCode = "H91DZ48" - countryCode = "US" + resourceName = "AD-123" + name = "name-1" + street = "street-1" + city = "city-1" + region = "region-1" + postalCode = "postal-code-1" + countryCode = "country-code-1" ) resource.Test(t, resource.TestCase{ @@ -50,6 +52,8 @@ func TestAccResourceRoutingSmsAddressesProdOrg(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_routing_sms_address."+resourceName, "country_code", countryCode), resource.TestCheckResourceAttr("genesyscloud_routing_sms_address."+resourceName, "auto_correct_address", util.FalseValue), ), + + PreventPostDestroyRefresh: true, }, { // Import/Read @@ -57,15 +61,19 @@ func TestAccResourceRoutingSmsAddressesProdOrg(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{"auto_correct_address"}, + Destroy: false, + //This type of org does not go out to SMS vendors. When you try and create an address in this case its trying to save it with the vendor, getting a mocked response and not storing any value. Hence cannot be deleted. }, }, - CheckDestroy: testVerifySmsAddressDestroyed, + CheckDestroy: nil, }) } // If running in a prod org this test can be removed/skipped, it's only intended as a backup test for test orgs func TestAccResourceRoutingSmsAddressesTestOrg(t *testing.T) { - t.Skip("returns empty in tca | test not needed in prod") + if v := os.Getenv("GENESYSCLOUD_REGION"); v == "us-east-1" { + t.Skip("This test will only pass in Test org") + } var ( // Due to running in a test org, a default address will be returned from the API and not the address we set. // This is because sms addresses are stored in twilio. Test orgs do not have twilio accounts so a default @@ -75,8 +83,8 @@ func TestAccResourceRoutingSmsAddressesTestOrg(t *testing.T) { street = "street-1" city = "city-1" region = "region-1" - postalCode = "postal-code-1" - countryCode = "country-code-1" + postalCode = "70090" + countryCode = "US" ) resource.Test(t, resource.TestCase{ diff --git a/genesyscloud/routing_utilization_label/resource_genesyscloud_routing_utilization_label_test.go b/genesyscloud/routing_utilization_label/resource_genesyscloud_routing_utilization_label_test.go index ca7b09718..fa0af605f 100644 --- a/genesyscloud/routing_utilization_label/resource_genesyscloud_routing_utilization_label_test.go +++ b/genesyscloud/routing_utilization_label/resource_genesyscloud_routing_utilization_label_test.go @@ -51,6 +51,7 @@ func TestAccResourceRoutingUtilizationLabelBasic(t *testing.T) { ResourceName: "genesyscloud_routing_utilization_label." + resourceName, ImportState: true, ImportStateVerify: true, + Destroy: true, }, }, CheckDestroy: validateTestLabelDestroyed, diff --git a/genesyscloud/task_management_workitem/genesyscloud_task_management_workitem_init_test.go b/genesyscloud/task_management_workitem/genesyscloud_task_management_workitem_init_test.go index d2b760c69..cad4d1e46 100644 --- a/genesyscloud/task_management_workitem/genesyscloud_task_management_workitem_init_test.go +++ b/genesyscloud/task_management_workitem/genesyscloud_task_management_workitem_init_test.go @@ -3,6 +3,7 @@ package task_management_workitem import ( "sync" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" "terraform-provider-genesyscloud/genesyscloud/user_roles" "testing" @@ -41,7 +42,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_task_management_workitem_schema"] = workitemSchema.ResourceTaskManagementWorkitemSchema() providerResources["genesyscloud_task_management_workbin"] = workbin.ResourceTaskManagementWorkbin() providerResources["genesyscloud_task_management_worktype"] = worktype.ResourceTaskManagementWorktype() - providerResources["genesyscloud_routing_language"] = gcloud.ResourceRoutingLanguage() + providerResources["genesyscloud_routing_language"] = routingLanguage.ResourceRoutingLanguage() providerResources["genesyscloud_user"] = gcloud.ResourceUser() providerResources["genesyscloud_externalcontacts_contact"] = externalContacts.ResourceExternalContact() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() diff --git a/genesyscloud/task_management_workitem/resource_genesyscloud_task_management_workitem_test.go b/genesyscloud/task_management_workitem/resource_genesyscloud_task_management_workitem_test.go index ae67f2425..697a3bcbf 100644 --- a/genesyscloud/task_management_workitem/resource_genesyscloud_task_management_workitem_test.go +++ b/genesyscloud/task_management_workitem/resource_genesyscloud_task_management_workitem_test.go @@ -6,6 +6,7 @@ import ( "strings" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" "terraform-provider-genesyscloud/genesyscloud/provider" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" "terraform-provider-genesyscloud/genesyscloud/user_roles" "terraform-provider-genesyscloud/genesyscloud/util" @@ -194,7 +195,7 @@ func TestAccResourceTaskManagementWorkitem(t *testing.T) { { Config: taskMgmtConfig + gcloud.GenerateAuthDivisionHomeDataSource(homeDivRes) + - gcloud.GenerateRoutingLanguageResource(resLang, lang) + + routingLanguage.GenerateRoutingLanguageResource(resLang, lang) + routingQueue.GenerateRoutingQueueResourceBasic(resQueue, queueName) + gcloud.GenerateRoutingSkillResource(skillResId1, skillResName1) + gcloud.GenerateBasicUserResource(userResId1, userEmail1, userName1) + diff --git a/genesyscloud/task_management_worktype/genesyscloud_task_management_worktype_init_test.go b/genesyscloud/task_management_worktype/genesyscloud_task_management_worktype_init_test.go index 897b3b335..bdb8160ee 100644 --- a/genesyscloud/task_management_worktype/genesyscloud_task_management_worktype_init_test.go +++ b/genesyscloud/task_management_worktype/genesyscloud_task_management_worktype_init_test.go @@ -6,6 +6,7 @@ import ( "testing" gcloud "terraform-provider-genesyscloud/genesyscloud" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" workbin "terraform-provider-genesyscloud/genesyscloud/task_management_workbin" workitemSchema "terraform-provider-genesyscloud/genesyscloud/task_management_workitem_schema" @@ -36,7 +37,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources[resourceName] = ResourceTaskManagementWorktype() providerResources["genesyscloud_task_management_workbin"] = workbin.ResourceTaskManagementWorkbin() providerResources["genesyscloud_task_management_workitem_schema"] = workitemSchema.ResourceTaskManagementWorkitemSchema() - providerResources["genesyscloud_routing_language"] = gcloud.ResourceRoutingLanguage() + providerResources["genesyscloud_routing_language"] = routingLanguage.ResourceRoutingLanguage() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() providerResources["genesyscloud_routing_skill"] = gcloud.ResourceRoutingSkill() } diff --git a/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_test.go b/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_test.go index 90bf04a63..12a109adb 100644 --- a/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_test.go +++ b/genesyscloud/task_management_worktype/resource_genesyscloud_task_management_worktype_test.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud/provider" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" "terraform-provider-genesyscloud/genesyscloud/util" "testing" @@ -109,7 +110,7 @@ func TestAccResourceTaskManagementWorktype(t *testing.T) { Config: workbin.GenerateWorkbinResource(wbResourceId, wbName, wbDescription, util.NullValue) + workitemSchema.GenerateWorkitemSchemaResourceBasic(wsResourceId, wsName, wsDescription) + routingQueue.GenerateRoutingQueueResourceBasic(queueResId, queueName) + - gcloud.GenerateRoutingLanguageResource(langResId, langName) + + routingLanguage.GenerateRoutingLanguageResource(langResId, langName) + gcloud.GenerateRoutingSkillResource(skillResId1, skillResName1) + gcloud.GenerateRoutingSkillResource(skillResId2, skillResName2) + generateWorktypeResource(wtRes) + diff --git a/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings_test.go b/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings_test.go index e2210b361..28bcfc7a2 100644 --- a/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings_test.go +++ b/genesyscloud/telephony/resource_genesyscloud_telephony_providers_edges_trunkbasesettings_test.go @@ -102,7 +102,7 @@ func TestAccResourceTrunkBaseSettings(t *testing.T) { } func TestAccResourceExternralTrunkBaseSettingsInboundSite(t *testing.T) { - t.Skip("Skipping because BYOC Does not exist in Org used for acceptance tests") + var ( trunkBaseSettingsRes = "trunkBaseSettings1234" name1 = "test trunk base settings " + uuid.NewString() @@ -122,7 +122,7 @@ func TestAccResourceExternralTrunkBaseSettingsInboundSite(t *testing.T) { "HQ", []string{}, gcloud.GenerateLocationEmergencyNum( - "+13100000001", + "+13100000003", util.NullValue, ), gcloud.GenerateLocationAddress( @@ -165,15 +165,13 @@ func TestAccResourceExternralTrunkBaseSettingsInboundSite(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "trunk_type", trunkType), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "managed", util.FalseValue), util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_label", name1), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_max_dial_timeout", "1m"), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_transport_sip_dscp_value", "25"), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_disconnect_on_idle_rtp", util.FalseValue), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_codec", strings.Join([]string{"audio/pcmu"}, ",")), - ), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_maxDialTimeout", "2m"), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_disconnectOnIdleRTP", util.TrueValue), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_codec", "audio/opus,audio/pcmu,audio/pcma")), }, // Update with new name, description and properties { - Config: GenerateTrunkBaseSettingsResourceWithCustomAttrs( + Config: referencedResources + GenerateTrunkBaseSettingsResourceWithCustomAttrs( trunkBaseSettingsRes, name2, description2, @@ -189,11 +187,9 @@ func TestAccResourceExternralTrunkBaseSettingsInboundSite(t *testing.T) { resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "trunk_type", trunkType), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "managed", util.FalseValue), util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_label", name2), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_max_dial_timeout", "2m"), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_transport_sip_dscp_value", "50"), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_disconnect_on_idle_rtp", util.TrueValue), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_codec", strings.Join([]string{"audio/opus"}, ",")), - ), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_maxDialTimeout", "2m"), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_disconnectOnIdleRTP", util.TrueValue), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_trunkbasesettings."+trunkBaseSettingsRes, "properties", "trunk_media_codec", "audio/opus,audio/pcmu,audio/pcma")), }, { // Import/Read @@ -201,6 +197,35 @@ func TestAccResourceExternralTrunkBaseSettingsInboundSite(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: gcloud.GenerateLocationResource( + locationResourceId, + "tf location "+uuid.NewString(), + "HQ", + []string{}, + gcloud.GenerateLocationEmergencyNum( + "+13100000003", + util.NullValue, + ), + gcloud.GenerateLocationAddress( + "7601 Interactive Way", + "Orlando", + "FL", + "US", + "32826", + ), + ) + edgeSite.GenerateSiteResourceWithCustomAttrs( + siteId, + "tf site "+uuid.NewString(), + "test description", + "genesyscloud_location."+locationResourceId+".id", + "Cloud", + false, + "[\"us-east-1\"]", + util.NullValue, + util.NullValue, + ), + }, }, CheckDestroy: testVerifyTrunkBaseSettingsDestroyed, }) diff --git a/genesyscloud/telephony_providers_edges_edge_group/resource_genesyscloud_telephony_providers_edges_edge_group_test.go b/genesyscloud/telephony_providers_edges_edge_group/resource_genesyscloud_telephony_providers_edges_edge_group_test.go index c0d722626..d05b9a5f1 100644 --- a/genesyscloud/telephony_providers_edges_edge_group/resource_genesyscloud_telephony_providers_edges_edge_group_test.go +++ b/genesyscloud/telephony_providers_edges_edge_group/resource_genesyscloud_telephony_providers_edges_edge_group_test.go @@ -14,7 +14,6 @@ import ( ) func TestAccResourceEdgeGroup(t *testing.T) { - t.Skip("Skipping this test for now because hybrid customers will not use edge groups and will only be able to modify the existing hybrid edge group. EdgeGroup will need to be refactored.") t.Parallel() var ( edgeGroupRes = "edgeGroup1234" diff --git a/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool.go b/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool.go index ed05a9a7e..cbe35300e 100644 --- a/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool.go +++ b/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool.go @@ -24,11 +24,11 @@ func dataSourceExtensionPoolRead(ctx context.Context, d *schema.ResourceData, m extensionPools, resp, getErr := extensionPoolProxy.getAllExtensionPools(ctx) if getErr != nil { - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting list of extension pools: %s", getErr), resp)) + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceName, fmt.Sprintf("error requesting list of extension pools: %s", getErr), resp)) } if extensionPools == nil || len(*extensionPools) == 0 { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no extension pools found with start phone number: %s and end phone number: %s", extensionPoolStartPhoneNumber, extensionPoolEndPhoneNumber), resp)) + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceName, fmt.Sprintf("no extension pools found with start phone number: %s and end phone number: %s", extensionPoolStartPhoneNumber, extensionPoolEndPhoneNumber), resp)) } for _, extensionPool := range *extensionPools { diff --git a/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool_test.go b/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool_test.go index db96450b4..0c2a5c5f8 100644 --- a/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool_test.go +++ b/genesyscloud/telephony_providers_edges_extension_pool/data_source_genesyscloud_telephony_providers_edges_extension_pool_test.go @@ -23,7 +23,7 @@ func TestAccDataSourceExtensionPoolBasic(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: generateExtensionPoolResource(&extensionPoolStruct{ + Config: GenerateExtensionPoolResource(&ExtensionPoolStruct{ extensionPoolRes, extensionPoolStartNumber, extensionPoolEndNumber, diff --git a/genesyscloud/telephony_providers_edges_extension_pool/genesyscloud_telephony_providers_edges_extension_pool_schema.go b/genesyscloud/telephony_providers_edges_extension_pool/genesyscloud_telephony_providers_edges_extension_pool_schema.go index 929e78bed..32d81bfec 100644 --- a/genesyscloud/telephony_providers_edges_extension_pool/genesyscloud_telephony_providers_edges_extension_pool_schema.go +++ b/genesyscloud/telephony_providers_edges_extension_pool/genesyscloud_telephony_providers_edges_extension_pool_schema.go @@ -1,15 +1,16 @@ package telephony_providers_edges_extension_pool import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "terraform-provider-genesyscloud/genesyscloud/provider" resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" registrar "terraform-provider-genesyscloud/genesyscloud/resource_register" "terraform-provider-genesyscloud/genesyscloud/validators" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) const ( - resourceName = "genesyscloud_telephony_providers_edges_extension_pool" + ResourceName = "genesyscloud_telephony_providers_edges_extension_pool" ) func ResourceTelephonyExtensionPool() *schema.Resource { @@ -76,7 +77,7 @@ func TelephonyExtensionPoolExporter() *resourceExporter.ResourceExporter { } func SetRegistrar(l registrar.Registrar) { - l.RegisterDataSource(resourceName, DataSourceExtensionPool()) - l.RegisterResource(resourceName, ResourceTelephonyExtensionPool()) - l.RegisterExporter(resourceName, TelephonyExtensionPoolExporter()) + l.RegisterDataSource(ResourceName, DataSourceExtensionPool()) + l.RegisterResource(ResourceName, ResourceTelephonyExtensionPool()) + l.RegisterExporter(ResourceName, TelephonyExtensionPoolExporter()) } diff --git a/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool.go b/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool.go index 49c20d856..e48d2d79e 100644 --- a/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool.go +++ b/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool.go @@ -25,7 +25,7 @@ func getAllExtensionPools(ctx context.Context, clientConfig *platformclientv2.Co extensionPoolProxy := getExtensionPoolProxy(clientConfig) extensionPools, resp, err := extensionPoolProxy.getAllExtensionPools(ctx) if err != nil { - return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get extension pools error: %s", err), resp) + return nil, util.BuildAPIDiagnosticError(ResourceName, fmt.Sprintf("Failed to get extension pools error: %s", err), resp) } if extensionPools != nil { for _, extensionPool := range *extensionPools { @@ -49,7 +49,7 @@ func createExtensionPool(ctx context.Context, d *schema.ResourceData, meta inter Description: &description, }) if err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create extension pool %s error: %s", startNumber, err), resp) + return util.BuildAPIDiagnosticError(ResourceName, fmt.Sprintf("Failed to create extension pool %s error: %s", startNumber, err), resp) } d.SetId(*extensionPool.Id) @@ -60,16 +60,16 @@ func createExtensionPool(ctx context.Context, d *schema.ResourceData, meta inter func readExtensionPool(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { sdkConfig := meta.(*provider.ProviderMeta).ClientConfig extensionPoolProxy := getExtensionPoolProxy(sdkConfig) - cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTelephonyExtensionPool(), constants.DefaultConsistencyChecks, resourceName) + cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTelephonyExtensionPool(), constants.DefaultConsistencyChecks, ResourceName) log.Printf("Reading Extension pool %s", d.Id()) return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { extensionPool, resp, getErr := extensionPoolProxy.getExtensionPool(ctx, d.Id()) if getErr != nil { if util.IsStatus404(resp) { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Extension pool %s | error: %s", d.Id(), getErr), resp)) + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceName, fmt.Sprintf("Failed to read Extension pool %s | error: %s", d.Id(), getErr), resp)) } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Extension pool %s | error: %s", d.Id(), getErr), resp)) + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceName, fmt.Sprintf("Failed to read Extension pool %s | error: %s", d.Id(), getErr), resp)) } if extensionPool.State != nil && *extensionPool.State == "deleted" { @@ -105,7 +105,7 @@ func updateExtensionPool(ctx context.Context, d *schema.ResourceData, meta inter } log.Printf("Updating Extension pool %s", d.Id()) if _, resp, err := extensionPoolProxy.updateExtensionPool(ctx, d.Id(), extensionPoolBody); err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update extension pool %s error: %s", startNumber, err), resp) + return util.BuildAPIDiagnosticError(ResourceName, fmt.Sprintf("Failed to update extension pool %s error: %s", startNumber, err), resp) } log.Printf("Updated Extension pool %s", d.Id()) return readExtensionPool(ctx, d, meta) @@ -117,7 +117,7 @@ func deleteExtensionPool(ctx context.Context, d *schema.ResourceData, meta inter extensionPoolProxy := getExtensionPoolProxy(sdkConfig) log.Printf("Deleting Extension pool with starting number %s", startNumber) if resp, err := extensionPoolProxy.deleteExtensionPool(ctx, d.Id()); err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete extension pool %s error: %s", startNumber, err), resp) + return util.BuildAPIDiagnosticError(ResourceName, fmt.Sprintf("Failed to delete extension pool %s error: %s", startNumber, err), resp) } return util.WithRetries(ctx, 30*time.Second, func() *retry.RetryError { extensionPool, resp, err := extensionPoolProxy.getExtensionPool(ctx, d.Id()) @@ -127,13 +127,13 @@ func deleteExtensionPool(ctx context.Context, d *schema.ResourceData, meta inter log.Printf("Deleted Extension pool %s", d.Id()) return nil } - return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error deleting Extension pool %s | error: %s", d.Id(), err), resp)) + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceName, fmt.Sprintf("error deleting Extension pool %s | error: %s", d.Id(), err), resp)) } if extensionPool.State != nil && *extensionPool.State == "deleted" { // Extension pool deleted log.Printf("Deleted Extension pool %s", d.Id()) return nil } - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("extension pool %s still exists", d.Id()), resp)) + return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceName, fmt.Sprintf("extension pool %s still exists", d.Id()), resp)) }) } diff --git a/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_test.go b/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_test.go index 80d602e94..21cbe29f6 100644 --- a/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_test.go +++ b/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_test.go @@ -2,25 +2,16 @@ package telephony_providers_edges_extension_pool import ( "fmt" - "log" "strconv" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" "testing" - "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" ) -type extensionPoolStruct struct { - resourceID string - startNumber string - endNumber string - description string -} - func TestAccResourceExtensionPoolBasic(t *testing.T) { t.Parallel() extensionPoolResource1 := "test-extensionpool1" @@ -30,8 +21,8 @@ func TestAccResourceExtensionPoolBasic(t *testing.T) { if err != nil { t.Fatal(err) } - deleteExtensionPoolWithNumber(extensionPoolStartNumber1) - deleteExtensionPoolWithNumber(extensionPoolEndNumber1) + DeleteExtensionPoolWithNumber(extensionPoolStartNumber1) + DeleteExtensionPoolWithNumber(extensionPoolEndNumber1) extensionPoolDescription1 := "Test description" resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, @@ -39,7 +30,7 @@ func TestAccResourceExtensionPoolBasic(t *testing.T) { Steps: []resource.TestStep{ { // Create - Config: generateExtensionPoolResource(&extensionPoolStruct{ + Config: GenerateExtensionPoolResource(&ExtensionPoolStruct{ extensionPoolResource1, extensionPoolStartNumber1, extensionPoolEndNumber1, @@ -53,7 +44,7 @@ func TestAccResourceExtensionPoolBasic(t *testing.T) { }, { // Update - Config: generateExtensionPoolResource(&extensionPoolStruct{ + Config: GenerateExtensionPoolResource(&ExtensionPoolStruct{ extensionPoolResource1, extensionPoolStartNumber1, extensionPoolEndNumber1, @@ -76,47 +67,6 @@ func TestAccResourceExtensionPoolBasic(t *testing.T) { }) } -func deleteExtensionPoolWithNumber(startNumber string) error { - sdkConfig, err := provider.AuthorizeSdk() - if err != nil { - log.Fatal(err) - } - edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig) - - for pageNum := 1; ; pageNum++ { - extensionPools, _, getErr := edgesAPI.GetTelephonyProvidersEdgesExtensionpools(100, pageNum, "", "") - if getErr != nil { - return getErr - } - - if extensionPools.Entities == nil || len(*extensionPools.Entities) == 0 { - break - } - - for _, extensionPool := range *extensionPools.Entities { - if extensionPool.StartNumber != nil && *extensionPool.StartNumber == startNumber { - _, err := edgesAPI.DeleteTelephonyProvidersEdgesExtensionpool(*extensionPool.Id) - time.Sleep(20 * time.Second) - return err - } - } - } - - return nil -} - -func generateExtensionPoolResource(extensionPool *extensionPoolStruct) string { - return fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_extension_pool" "%s" { - start_number = "%s" - end_number = "%s" - description = %s - } - `, extensionPool.resourceID, - extensionPool.startNumber, - extensionPool.endNumber, - extensionPool.description) -} - func testVerifyExtensionPoolsDestroyed(state *terraform.State) error { telephonyAPI := platformclientv2.NewTelephonyProvidersEdgeApi() for _, rs := range state.RootModule().Resources { diff --git a/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_utils.go b/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_utils.go new file mode 100644 index 000000000..0dd0f71aa --- /dev/null +++ b/genesyscloud/telephony_providers_edges_extension_pool/resource_genesyscloud_telephony_providers_edges_extension_pool_utils.go @@ -0,0 +1,58 @@ +package telephony_providers_edges_extension_pool + +import ( + "fmt" + "log" + "terraform-provider-genesyscloud/genesyscloud/provider" + "time" + + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +type ExtensionPoolStruct struct { + ResourceID string + StartNumber string + EndNumber string + Description string +} + +func GenerateExtensionPoolResource(extensionPool *ExtensionPoolStruct) string { + return fmt.Sprintf(`resource "genesyscloud_telephony_providers_edges_extension_pool" "%s" { + start_number = "%s" + end_number = "%s" + description = %s + } + `, extensionPool.ResourceID, + extensionPool.StartNumber, + extensionPool.EndNumber, + extensionPool.Description) +} + +func DeleteExtensionPoolWithNumber(startNumber string) error { + sdkConfig, err := provider.AuthorizeSdk() + if err != nil { + log.Fatal(err) + } + edgesAPI := platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdkConfig) + + for pageNum := 1; ; pageNum++ { + extensionPools, _, getErr := edgesAPI.GetTelephonyProvidersEdgesExtensionpools(100, pageNum, "", "") + if getErr != nil { + return getErr + } + + if extensionPools.Entities == nil || len(*extensionPools.Entities) == 0 { + break + } + + for _, extensionPool := range *extensionPools.Entities { + if extensionPool.StartNumber != nil && *extensionPool.StartNumber == startNumber { + _, err := edgesAPI.DeleteTelephonyProvidersEdgesExtensionpool(*extensionPool.Id) + time.Sleep(20 * time.Second) + return err + } + } + } + + return nil +} 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 f9955feea..4e8421b64 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 @@ -184,26 +184,38 @@ func TestAccResourcePhoneBasic(t *testing.T) { }) } -func TestAccResourcePhoneStandalone(t *testing.T) { - lineAddresses := "+12005538112" - deleteDidPoolWithNumber(lineAddresses) - didPoolResource1 := "test-didpool1" - phoneRes := "phone_standalone1234" - name1 := "test-phone-standalone_" + uuid.NewString() +func TestAccResourceHardPhoneStandalone(t *testing.T) { + number := "+13172128941" + phoneMac := "AB12CD34" + phoneMacUpdated := "BANANAS" + // 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) + } + }() + + phoneRes := "phone_standalone987" + name := "test-phone-standalone_" + uuid.NewString() + stateActive := "active" - phoneBaseSettingsRes := "phoneBaseSettings1234" + phoneBaseSettingsRes := "phoneBaseSettings987" phoneBaseSettingsName := "phoneBaseSettings " + uuid.NewString() - locationRes := "test-location" + locationRes := "test-location-test111" - emergencyNumber := "+13173114121" - if err := edgeSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { + emergencyNumber := "+13293100121" + if err = edgeSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { t.Skipf("failed to delete location with number %s: %v", emergencyNumber, err) } locationConfig := gcloud.GenerateLocationResource( locationRes, - "Terraform location"+uuid.NewString(), + "Terraform-location"+uuid.NewString(), "HQ1", []string{}, gcloud.GenerateLocationEmergencyNum( @@ -214,7 +226,7 @@ func TestAccResourcePhoneStandalone(t *testing.T) { "Indianapolis", "IN", "US", - "46279", + "41119", )) siteRes := "test-site" @@ -233,61 +245,71 @@ func TestAccResourcePhoneStandalone(t *testing.T) { ) capabilities := generatePhoneCapabilities( - false, true, true, true, true, false, true, + false, "mac", - []string{}, + []string{strconv.Quote("audio/opus"), strconv.Quote("audio/pcmu"), strconv.Quote("audio/pcma")}, + ) + config := locationConfig + siteConfig + phoneBaseSettings.GeneratePhoneBaseSettingsResourceWithCustomAttrs( + phoneBaseSettingsRes, + phoneBaseSettingsName, + "phoneBaseSettings description", + "audiocodes_400hd.json", ) + phone1 := GeneratePhoneResourceWithCustomAttrs(&PhoneConfig{ + phoneRes, + name, + stateActive, + "genesyscloud_telephony_providers_edges_site." + siteRes + ".id", + "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", + "", // no web rtc user + "", // no Depends On + }, capabilities, generatePhoneProperties(phoneMac)) + + //only mac is updated here, same resource as phone 1 + phone2 := GeneratePhoneResourceWithCustomAttrs(&PhoneConfig{ + phoneRes, + name, + stateActive, + "genesyscloud_telephony_providers_edges_site." + siteRes + ".id", + "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", + "", // no web rtc user + "", // no Depends On + }, capabilities, generatePhoneProperties(phoneMacUpdated)) resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - PreConfig: func() { - time.Sleep(30 * time.Second) - }, - Config: didPool.GenerateDidPoolResource(&didPool.DidPoolStruct{ - ResourceID: didPoolResource1, - StartPhoneNumber: lineAddresses, - EndPhoneNumber: lineAddresses, - Description: util.NullValue, // No description - Comments: util.NullValue, // No comments - PoolProvider: util.NullValue, // No provider - }) + locationConfig + siteConfig + phoneBaseSettings.GeneratePhoneBaseSettingsResourceWithCustomAttrs( - phoneBaseSettingsRes, - phoneBaseSettingsName, - "phoneBaseSettings description", - "generic_sip.json", - ) + GeneratePhoneResourceWithCustomAttrs(&PhoneConfig{ - phoneRes, - name1, - stateActive, - "genesyscloud_telephony_providers_edges_site." + siteRes + ".id", - "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", - "", // no web rtc user - "genesyscloud_telephony_providers_edges_did_pool." + didPoolResource1, - }, capabilities, generateLineProperties(strconv.Quote(lineAddresses), ""), generatePhoneProperties(uuid.NewString())), + + Config: config + phone1, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "name", name1), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "name", name), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "state", stateActive), resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_phone."+phoneRes, "site_id", "genesyscloud_telephony_providers_edges_site."+siteRes, "id"), resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_phone."+phoneRes, "line_base_settings_id", "genesyscloud_telephony_providers_edges_phonebasesettings."+phoneBaseSettingsRes, "line_base_settings_id"), resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_phone."+phoneRes, "phone_base_settings_id", "genesyscloud_telephony_providers_edges_phonebasesettings."+phoneBaseSettingsRes, "id"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "line_properties.0.line_address.0", lineAddresses), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.provisions", util.FalseValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.provisions", util.TrueValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.registers", util.TrueValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.dual_registers", util.TrueValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.allow_reboot", util.TrueValue), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_rebalance", util.TrueValue), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_cloud_provisioning", util.FalseValue), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.cdm", util.TrueValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_rebalance", util.FalseValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_cloud_provisioning", util.TrueValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.cdm", util.FalseValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.hardware_id_type", "mac"), + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "properties", "phone_hardwareId", phoneMac), + ), + }, + { + Config: config + phone2, + Check: resource.ComposeTestCheckFunc( + util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "properties", "phone_hardwareId", phoneMacUpdated), ), }, { @@ -301,38 +323,26 @@ func TestAccResourcePhoneStandalone(t *testing.T) { }) } -func TestAccResourceHardPhoneStandalone(t *testing.T) { - number := "+13172128941" - phoneMac := "AB12CD34" - phoneMacUpdated := "BANANAS" - // 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) - } - }() - - phoneRes := "phone_standalone987" - name := "test-phone-standalone_" + uuid.NewString() - +func TestAccResourcePhoneStandalone(t *testing.T) { + lineAddresses := "+12005537112" + deleteDidPoolWithNumber(lineAddresses) + didPoolResource1 := "test-didpool1" + phoneRes := "phone_standalone1234" + name1 := "test-phone-standalone_" + uuid.NewString() stateActive := "active" - phoneBaseSettingsRes := "phoneBaseSettings987" + phoneBaseSettingsRes := "phoneBaseSettings1234" phoneBaseSettingsName := "phoneBaseSettings " + uuid.NewString() + fullResourceId := "genesyscloud_telephony_providers_edges_did_pool" + "." + didPoolResource1 + locationRes := "test-location" - locationRes := "test-location-test111" - - emergencyNumber := "+13293100121" - if err = edgeSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { + emergencyNumber := "+13173114121" + if err := edgeSite.DeleteLocationWithNumber(emergencyNumber, sdkConfig); err != nil { t.Skipf("failed to delete location with number %s: %v", emergencyNumber, err) } locationConfig := gcloud.GenerateLocationResource( locationRes, - "Terraform-location"+uuid.NewString(), + "Terraform location"+uuid.NewString(), "HQ1", []string{}, gcloud.GenerateLocationEmergencyNum( @@ -343,7 +353,7 @@ func TestAccResourceHardPhoneStandalone(t *testing.T) { "Indianapolis", "IN", "US", - "41119", + "46279", )) siteRes := "test-site" @@ -354,7 +364,7 @@ func TestAccResourceHardPhoneStandalone(t *testing.T) { "genesyscloud_location."+locationRes+".id", "Premises", false, - `["us-east-1"]`, + util.AssignRegion(), util.NullValue, util.NullValue, "primary_sites = []", @@ -362,70 +372,74 @@ func TestAccResourceHardPhoneStandalone(t *testing.T) { ) capabilities := generatePhoneCapabilities( + false, true, true, true, true, false, true, - false, "mac", - []string{strconv.Quote("audio/opus"), strconv.Quote("audio/pcmu"), strconv.Quote("audio/pcma")}, - ) - config := locationConfig + siteConfig + phoneBaseSettings.GeneratePhoneBaseSettingsResourceWithCustomAttrs( - phoneBaseSettingsRes, - phoneBaseSettingsName, - "phoneBaseSettings description", - "audiocodes_400hd.json", + []string{}, ) - phone1 := GeneratePhoneResourceWithCustomAttrs(&PhoneConfig{ - phoneRes, - name, - stateActive, - "genesyscloud_telephony_providers_edges_site." + siteRes + ".id", - "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", - "", // no web rtc user - "", // no Depends On - }, capabilities, generatePhoneProperties(phoneMac)) - - //only mac is updated here, same resource as phone 1 - phone2 := GeneratePhoneResourceWithCustomAttrs(&PhoneConfig{ - phoneRes, - name, - stateActive, - "genesyscloud_telephony_providers_edges_site." + siteRes + ".id", - "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", - "", // no web rtc user - "", // no Depends On - }, capabilities, generatePhoneProperties(phoneMacUpdated)) resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - Config: config + phone1, + Config: didPool.GenerateDidPoolResource(&didPool.DidPoolStruct{ + ResourceID: didPoolResource1, + StartPhoneNumber: lineAddresses, + EndPhoneNumber: lineAddresses, + Description: util.NullValue, // No description + Comments: util.NullValue, // No comments + PoolProvider: util.NullValue, // No provider + }), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "name", name), + func(s *terraform.State) error { + time.Sleep(30 * time.Second) // Wait for 30 seconds for proper updation + return nil + }, + resource.TestCheckResourceAttr(fullResourceId, "start_phone_number", lineAddresses)), + }, + { + Config: didPool.GenerateDidPoolResource(&didPool.DidPoolStruct{ + ResourceID: didPoolResource1, + StartPhoneNumber: lineAddresses, + EndPhoneNumber: lineAddresses, + Description: util.NullValue, // No description + Comments: util.NullValue, // No comments + PoolProvider: util.NullValue, // No provider + }) + locationConfig + siteConfig + phoneBaseSettings.GeneratePhoneBaseSettingsResourceWithCustomAttrs( + phoneBaseSettingsRes, + phoneBaseSettingsName, + "phoneBaseSettings description", + "generic_sip.json", + ) + GeneratePhoneResourceWithCustomAttrs(&PhoneConfig{ + phoneRes, + name1, + stateActive, + "genesyscloud_telephony_providers_edges_site." + siteRes + ".id", + "genesyscloud_telephony_providers_edges_phonebasesettings." + phoneBaseSettingsRes + ".id", + "", // no web rtc user + "genesyscloud_telephony_providers_edges_did_pool." + didPoolResource1, + }, capabilities, generateLineProperties(strconv.Quote(lineAddresses), ""), generatePhoneProperties(uuid.NewString())), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "name", name1), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "state", stateActive), resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_phone."+phoneRes, "site_id", "genesyscloud_telephony_providers_edges_site."+siteRes, "id"), resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_phone."+phoneRes, "line_base_settings_id", "genesyscloud_telephony_providers_edges_phonebasesettings."+phoneBaseSettingsRes, "line_base_settings_id"), resource.TestCheckResourceAttrPair("genesyscloud_telephony_providers_edges_phone."+phoneRes, "phone_base_settings_id", "genesyscloud_telephony_providers_edges_phonebasesettings."+phoneBaseSettingsRes, "id"), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.provisions", util.TrueValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "line_properties.0.line_address.0", lineAddresses), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.provisions", util.FalseValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.registers", util.TrueValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.dual_registers", util.TrueValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.allow_reboot", util.TrueValue), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_rebalance", util.FalseValue), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_cloud_provisioning", util.TrueValue), - resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.cdm", util.FalseValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_rebalance", util.TrueValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.no_cloud_provisioning", util.FalseValue), + resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.cdm", util.TrueValue), resource.TestCheckResourceAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "capabilities.0.hardware_id_type", "mac"), - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "properties", "phone_hardwareId", phoneMac), - ), - }, - { - Config: config + phone2, - Check: resource.ComposeTestCheckFunc( - util.ValidateValueInJsonPropertiesAttr("genesyscloud_telephony_providers_edges_phone."+phoneRes, "properties", "phone_hardwareId", phoneMacUpdated), ), }, { diff --git a/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site.go b/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site.go index ff8114fee..95a677573 100644 --- a/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site.go +++ b/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site.go @@ -18,10 +18,9 @@ func dataSourceSiteRead(ctx context.Context, d *schema.ResourceData, m interface sp := GetSiteProxy(sdkConfig) name := d.Get("name").(string) - managed := d.Get("managed").(bool) return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - siteId, retryable, resp, err := sp.getSiteIdByName(ctx, name, managed) + siteId, retryable, resp, err := sp.getSiteIdByName(ctx, name) if err != nil { if retryable { return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("failed to get site %s", name), resp)) diff --git a/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site_test.go b/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site_test.go index de32d841f..6db8871a1 100644 --- a/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site_test.go +++ b/genesyscloud/telephony_providers_edges_site/data_source_genesyscloud_telephony_providers_edges_site_test.go @@ -67,8 +67,7 @@ func TestAccDataSourceSite(t *testing.T) { strconv.Quote("Wilco plumbing")) + location + generateSiteDataSource( siteDataRes, name, - "genesyscloud_telephony_providers_edges_site."+siteRes, - false), + "genesyscloud_telephony_providers_edges_site."+siteRes), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair("data.genesyscloud_telephony_providers_edges_site."+siteDataRes, "id", "genesyscloud_telephony_providers_edges_site."+siteRes, "id"), ), @@ -101,7 +100,6 @@ func TestAccDataSourceSiteManaged(t *testing.T) { siteDataRes, name, "", - true, ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.genesyscloud_telephony_providers_edges_site."+siteDataRes, "id", siteId), @@ -117,13 +115,12 @@ func generateSiteDataSource( // Must explicitly use depends_on in terraform v0.13 when a data source references a resource // Fixed in v0.14 https://github.com/hashicorp/terraform/pull/26284 dependsOnResource string, - managed bool) string { +) string { return fmt.Sprintf(`data "genesyscloud_telephony_providers_edges_site" "%s" { name = "%s" - managed = %t depends_on=[%s] } - `, resourceID, name, managed, dependsOnResource) + `, resourceID, name, dependsOnResource) } func getSiteIdByName(name string) (string, error) { diff --git a/genesyscloud/telephony_providers_edges_site/genesyscloud_telephony_providers_edges_site_proxy.go b/genesyscloud/telephony_providers_edges_site/genesyscloud_telephony_providers_edges_site_proxy.go index 12ec0e4d8..45f74f72b 100644 --- a/genesyscloud/telephony_providers_edges_site/genesyscloud_telephony_providers_edges_site_proxy.go +++ b/genesyscloud/telephony_providers_edges_site/genesyscloud_telephony_providers_edges_site_proxy.go @@ -37,7 +37,7 @@ type getAllSitesFunc func(ctx context.Context, p *SiteProxy, managed bool) (*[]p type createSiteFunc func(ctx context.Context, p *SiteProxy, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error) type deleteSiteFunc func(ctx context.Context, p *SiteProxy, siteId string) (*platformclientv2.APIResponse, error) type getSiteByIdFunc func(ctx context.Context, p *SiteProxy, siteId string) (site *platformclientv2.Site, resp *platformclientv2.APIResponse, err error) -type getSiteIdByNameFunc func(ctx context.Context, p *SiteProxy, siteName string, managed bool) (siteId string, retryable bool, resp *platformclientv2.APIResponse, err error) +type getSiteIdByNameFunc func(ctx context.Context, p *SiteProxy, siteName string) (siteId string, retryable bool, resp *platformclientv2.APIResponse, err error) type updateSiteFunc func(ctx context.Context, p *SiteProxy, siteId string, site *platformclientv2.Site) (*platformclientv2.Site, *platformclientv2.APIResponse, error) type createSiteOutboundRouteFunc func(ctx context.Context, p *SiteProxy, siteId string, outboundRoute *platformclientv2.Outboundroutebase) (*platformclientv2.Outboundroutebase, *platformclientv2.APIResponse, error) @@ -157,8 +157,8 @@ func (p *SiteProxy) getSiteById(ctx context.Context, siteId string) (site *platf } // getSiteIdByNameFunc returns a single Genesys Cloud Site by Name -func (p *SiteProxy) getSiteIdByName(ctx context.Context, siteName string, managed bool) (siteId string, retryable bool, resp *platformclientv2.APIResponse, err error) { - return p.getSiteIdByNameAttr(ctx, p, siteName, managed) +func (p *SiteProxy) getSiteIdByName(ctx context.Context, siteName string) (siteId string, retryable bool, resp *platformclientv2.APIResponse, err error) { + return p.getSiteIdByNameAttr(ctx, p, siteName) } // updateSiteFunc updates a Genesys Cloud Site @@ -321,20 +321,32 @@ func getSiteByIdFn(ctx context.Context, p *SiteProxy, siteId string) (*platformc } // getSiteIdByNameFn is an implementation function for retrieving a Genesys Cloud Site by name -func getSiteIdByNameFn(ctx context.Context, p *SiteProxy, siteName string, managed bool) (string, bool, *platformclientv2.APIResponse, error) { - sites, resp, err := getAllSitesFn(ctx, p, managed) +func getSiteIdByNameFn(ctx context.Context, p *SiteProxy, siteName string) (string, bool, *platformclientv2.APIResponse, error) { + managed, resp, err := getAllSitesFn(ctx, p, true) if err != nil { return "", false, resp, err } - if sites == nil || len(*sites) == 0 { - return "", true, resp, fmt.Errorf("no sites found with name %s", siteName) - } - for _, site := range *sites { - if (site.Name != nil && *site.Name == siteName) && (site.State != nil && *site.State != "deleted") { - return *site.Id, false, resp, nil + + if managed != nil { + for _, site := range *managed { + if (site.Name != nil && *site.Name == siteName) && (site.State != nil && *site.State != "deleted") { + return *site.Id, false, resp, nil + } } } + unmanaged, resp, err := getAllSitesFn(ctx, p, false) + if err != nil { + return "", false, resp, err + } + + if unmanaged != nil { + for _, site := range *unmanaged { + if (site.Name != nil && *site.Name == siteName) && (site.State != nil && *site.State != "deleted") { + return *site.Id, false, resp, nil + } + } + } return "", true, resp, fmt.Errorf("no sites found with name %s", siteName) } diff --git a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site.go b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site.go index 23208cad9..483bd87da 100644 --- a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site.go +++ b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "terraform-provider-genesyscloud/genesyscloud/provider" + "terraform-provider-genesyscloud/genesyscloud/tfexporter_state" "terraform-provider-genesyscloud/genesyscloud/util" "terraform-provider-genesyscloud/genesyscloud/util/constants" featureToggles "terraform-provider-genesyscloud/genesyscloud/util/feature_toggles" @@ -43,8 +44,12 @@ func getAllSites(ctx context.Context, sdkConfig *platformclientv2.Configuration) } for _, managedSite := range *managedSites { resources[*managedSite.Id] = &resourceExporter.ResourceMeta{Name: *managedSite.Name} + // When exporting managed sites, they must automatically be exported as data source + // Managed sites are added to the ExportAsData []string in resource_exporter + if tfexporter_state.IsExporterActive() { + resourceExporter.AddDataSourceItems(resourceName, *managedSite.Name) + } } - return resources, nil } diff --git a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_schema.go b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_schema.go index 55b4e51b1..7587c3427 100644 --- a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_schema.go +++ b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_schema.go @@ -304,12 +304,6 @@ func DataSourceSite() *schema.Resource { Type: schema.TypeString, Required: true, }, - "managed": { - Description: "Return entities that are managed by Genesys Cloud.", - Type: schema.TypeBool, - Optional: true, - Default: false, - }, }, } } 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 e44edd2d8..c571840b5 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 @@ -2,8 +2,8 @@ package telephony_providers_edges_site import ( "fmt" - "log" "os" + "log" "strconv" "strings" "terraform-provider-genesyscloud/genesyscloud/provider" diff --git a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_utils.go b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_utils.go index 2fbbae030..77a1b5193 100644 --- a/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_utils.go +++ b/genesyscloud/telephony_providers_edges_site/resource_genesyscloud_telephony_providers_edges_site_utils.go @@ -535,6 +535,23 @@ func GenerateSiteResourceWithCustomAttrs( `, siteRes, name, description, locationId, mediaModel, mediaRegionsUseLatencyBased, mediaRegions, callerId, callerName, strings.Join(otherAttrs, "\n")) } +func CheckForDefaultSite(siteName string) error { + var ( + sdk, _ = provider.AuthorizeSdk() + siteAPI = platformclientv2.NewTelephonyProvidersEdgeApiWithConfig(sdk) + pageSize = 100 + ) + + sites, _, getErr := siteAPI.GetTelephonyProvidersEdgesSites(pageSize, 1, "", "", siteName, "", true, []string{}) + if getErr != nil { + return getErr + } + if sites == nil { + return fmt.Errorf("no default site found with name %s", siteName) + } + return nil +} + // DeleteLocationWithNumber is a test utility function to delete site and location with the provided emergency number func DeleteLocationWithNumber(emergencyNumber string, config *platformclientv2.Configuration) error { var ( diff --git a/genesyscloud/telephony_providers_edges_trunk/genesyscloud_telephony_providers_edges_trunk_init_test.go b/genesyscloud/telephony_providers_edges_trunk/genesyscloud_telephony_providers_edges_trunk_init_test.go index bdff55d86..b7dd1260b 100644 --- a/genesyscloud/telephony_providers_edges_trunk/genesyscloud_telephony_providers_edges_trunk_init_test.go +++ b/genesyscloud/telephony_providers_edges_trunk/genesyscloud_telephony_providers_edges_trunk_init_test.go @@ -3,8 +3,8 @@ package telephony_providers_edges_trunk import ( "sync" gcloud "terraform-provider-genesyscloud/genesyscloud" - telephony "terraform-provider-genesyscloud/genesyscloud/telephony" + edgeGroup "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_edge_group" edgeSite "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_site" "testing" @@ -31,6 +31,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_telephony_providers_edges_site"] = edgeSite.ResourceSite() providerResources["genesyscloud_location"] = gcloud.ResourceLocation() + providerResources["genesyscloud_telephony_providers_edges_edge_group"] = edgeGroup.ResourceEdgeGroup() } @@ -43,6 +44,7 @@ func (r *registerTestInstance) registerTestDataSources() { providerDataSources[resourceName] = DataSourceTrunk() // external package dependencies providerDataSources["genesyscloud_telephony_providers_edges_site"] = edgeSite.DataSourceSite() + providerDataSources["genesyscloud_telephony_providers_edges_edge_group"] = edgeGroup.DataSourceEdgeGroup() } diff --git a/genesyscloud/tfexporter/genesyscloud_resource_exporter.go b/genesyscloud/tfexporter/genesyscloud_resource_exporter.go index c4bb43660..a9ebbeb4a 100644 --- a/genesyscloud/tfexporter/genesyscloud_resource_exporter.go +++ b/genesyscloud/tfexporter/genesyscloud_resource_exporter.go @@ -1019,6 +1019,7 @@ func (g *GenesysCloudResourceExporter) getResourcesForType(resType string, provi // will block until it can acquire a pooled client config object. instanceState, err := getResourceState(ctx, res, id, resMeta, meta) + resourceType := "" if g.isDataSource(resType, resMeta.Name) { g.exMutex.Lock() res = provider.DataSourcesMap[resType] @@ -1038,6 +1039,7 @@ func (g *GenesysCloudResourceExporter) getResourcesForType(resType string, provi } } instanceState.Attributes = attributes + resourceType = "data." } if err != nil { @@ -1052,10 +1054,11 @@ func (g *GenesysCloudResourceExporter) getResourcesForType(resType string, provi } resourceChan <- resourceExporter.ResourceInfo{ - State: instanceState, - Name: resMeta.Name, - Type: resType, - CtyType: ctyType, + State: instanceState, + Name: resMeta.Name, + Type: resType, + CtyType: ctyType, + ResourceType: resourceType, } return nil @@ -1605,7 +1608,12 @@ func (g *GenesysCloudResourceExporter) resourceIdExists(refID string, existingRe } func (g *GenesysCloudResourceExporter) isDataSource(resType string, name string) bool { - for _, element := range g.replaceWithDatasource { + return g.containsElement(resourceExporter.ExportAsData, resType, name) || g.containsElement(g.replaceWithDatasource, resType, name) +} + +func (g *GenesysCloudResourceExporter) containsElement(elements []string, resType, name string) bool { + + for _, element := range elements { if element == resType+"::"+name || fetchByRegex(element, resType, name) { return true } diff --git a/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go b/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go index 98febed09..5d189152c 100644 --- a/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go +++ b/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go @@ -21,6 +21,7 @@ import ( "terraform-provider-genesyscloud/genesyscloud/provider" resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" + telephonyProvidersEdgesSite "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_site" "terraform-provider-genesyscloud/genesyscloud/util" "testing" "time" @@ -318,71 +319,73 @@ func TestAccResourceTfExportExcludeFilterResourcesByRegExExclusiveToResource(t * }) } -// TestAccResourceTfExportExcludeFilterResourcesByRegExExclusiveToResourceAndSanitizedNames will exclude any test resources that match a -// regular expression provided for the resource. In this test we check against both sanitized and unsanitized names. -func TestAccResourceTfExportExcludeFilterResourcesByRegExExclusiveToResourceAndSanitizedNames(t *testing.T) { +// TestAccResourceTfExportSplitFilesAsJSON will create 2 queues, 2 wrap up codes, and 2 users. +// The exporter will be run in split mode so 3 resource tf.jsons should be created as well as a provider.tf.json +func TestAccResourceTfExportSplitFilesAsJSON(t *testing.T) { var ( - exportTestDir = "../.terraformExclude" + uuid.NewString() - exportResource = "test-export6_1" + exportTestDir = "../.terraform" + uuid.NewString() + exportResource = "test-export-split" + uniquePostfix = randString(7) + expectedFilesPath = []string{ + filepath.Join(exportTestDir, "genesyscloud_routing_queue.tf.json"), + filepath.Join(exportTestDir, "genesyscloud_user.tf.json"), + filepath.Join(exportTestDir, "genesyscloud_routing_wrapupcode.tf.json"), + filepath.Join(exportTestDir, "provider.tf.json"), + } queueResources = []QueueExport{ - {ResourceName: "test-queue-test-1", Name: "exclude filter - exclude me", Description: "This is an excluded bar test resource", AcwTimeoutMs: 200000}, - {ResourceName: "test-queue-test-2", Name: "exclude filter - foo - bar me", Description: "This is a foo bar test resource", AcwTimeoutMs: 200000}, - {ResourceName: "test-queue-test-3", Name: "exclude filter - fu - barre you", Description: "This is a foo bar test resource", AcwTimeoutMs: 200000}, + {ResourceName: "test-queue-1", Name: "test-queue-1-" + uuid.NewString() + uniquePostfix, Description: "This is a test queue", AcwTimeoutMs: 200000}, + {ResourceName: "test-queue-2", Name: "test-queue-1-" + uuid.NewString() + uniquePostfix, Description: "This is a test queue too", AcwTimeoutMs: 200000}, + } + + userResources = []UserExport{ + {ResourceName: "test-user-1", Name: "test-user-1", Email: "test-user-1" + uuid.NewString() + "@test.com" + uniquePostfix, State: "active"}, + {ResourceName: "test-user-2", Name: "test-user-2", Email: "test-user-2" + uuid.NewString() + "@test.com" + uniquePostfix, State: "active"}, } wrapupCodeResources = []WrapupcodeExport{ - {ResourceName: "test-wrapupcode-prod", Name: "exclude me"}, - {ResourceName: "test-wrapupcode-test", Name: "foo + bar me"}, - {ResourceName: "test-wrapupcode-dev", Name: "fu - barre you"}, + {ResourceName: "test-wrapupcode-1", Name: "test-wrapupcode-1-" + uuid.NewString() + uniquePostfix}, + {ResourceName: "test-wrapupcode-2", Name: "test-wrapupcode-2-" + uuid.NewString() + uniquePostfix}, } ) defer os.RemoveAll(exportTestDir) queueResourceDef := buildQueueResources(queueResources) + userResourcesDef := buildUserResources(userResources) wrapupcodeResourceDef := buildWrapupcodeResources(wrapupCodeResources) - config := queueResourceDef + wrapupcodeResourceDef + - generateTfExportByExcludeFilterResources( + config := queueResourceDef + wrapupcodeResourceDef + userResourcesDef + + generateTfExportByIncludeFilterResources( exportResource, exportTestDir, util.TrueValue, []string{ - strconv.Quote("genesyscloud_routing_queue::exclude filter - foo - bar me"), - strconv.Quote("genesyscloud_routing_queue::exclude_filter_-_fu_-_barre_you"), - strconv.Quote("genesyscloud_outbound_ruleset"), - strconv.Quote("genesyscloud_user"), - strconv.Quote("genesyscloud_user_roles"), - strconv.Quote("genesyscloud_flow"), + strconv.Quote("genesyscloud_routing_queue::" + uniquePostfix + "$"), + strconv.Quote("genesyscloud_user::" + uniquePostfix + "$"), + strconv.Quote("genesyscloud_routing_wrapupcode::" + uniquePostfix + "$"), }, util.FalseValue, - util.FalseValue, + util.TrueValue, []string{ strconv.Quote("genesyscloud_routing_queue." + queueResources[0].ResourceName), strconv.Quote("genesyscloud_routing_queue." + queueResources[1].ResourceName), - strconv.Quote("genesyscloud_routing_queue." + queueResources[2].ResourceName), + strconv.Quote("genesyscloud_user." + userResources[0].ResourceName), + strconv.Quote("genesyscloud_user." + userResources[1].ResourceName), strconv.Quote("genesyscloud_routing_wrapupcode." + wrapupCodeResources[0].ResourceName), strconv.Quote("genesyscloud_routing_wrapupcode." + wrapupCodeResources[1].ResourceName), - strconv.Quote("genesyscloud_routing_wrapupcode." + wrapupCodeResources[2].ResourceName), }, ) - sanitizer := resourceExporter.NewSanitizerProvider() resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { - PreConfig: func() { - time.Sleep(30 * time.Second) - }, - // Generate a queue as well and export it Config: config, Check: resource.ComposeTestCheckFunc( - testQueueExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_queue", sanitizer.S.SanitizeResourceName(queueResources[0].Name), queueResources[0]), - testWrapupcodeExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_wrapupcode", sanitizer.S.SanitizeResourceName(wrapupCodeResources[0].Name), wrapupCodeResources[0]), - testWrapupcodeExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_wrapupcode", sanitizer.S.SanitizeResourceName(wrapupCodeResources[1].Name), wrapupCodeResources[1]), - testWrapupcodeExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_wrapupcode", sanitizer.S.SanitizeResourceName(wrapupCodeResources[2].Name), wrapupCodeResources[2]), - testQueueExportExcludesRegEx(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_queue", "(foo|fu)"), + validateFileCreated(expectedFilesPath[0]), + validateFileCreated(expectedFilesPath[1]), + validateFileCreated(expectedFilesPath[2]), + validateFileCreated(expectedFilesPath[3]), ), }, }, @@ -390,73 +393,71 @@ func TestAccResourceTfExportExcludeFilterResourcesByRegExExclusiveToResourceAndS }) } -// TestAccResourceTfExportSplitFilesAsJSON will create 2 queues, 2 wrap up codes, and 2 users. -// The exporter will be run in split mode so 3 resource tf.jsons should be created as well as a provider.tf.json -func TestAccResourceTfExportSplitFilesAsJSON(t *testing.T) { +// TestAccResourceTfExportExcludeFilterResourcesByRegExExclusiveToResourceAndSanitizedNames will exclude any test resources that match a +// regular expression provided for the resource. In this test we check against both sanitized and unsanitized names. +func TestAccResourceTfExportExcludeFilterResourcesByRegExExclusiveToResourceAndSanitizedNames(t *testing.T) { var ( - exportTestDir = "../.terraform" + uuid.NewString() - exportResource = "test-export-split" - uniquePostfix = randString(7) - expectedFilesPath = []string{ - filepath.Join(exportTestDir, "genesyscloud_routing_queue.tf.json"), - filepath.Join(exportTestDir, "genesyscloud_user.tf.json"), - filepath.Join(exportTestDir, "genesyscloud_routing_wrapupcode.tf.json"), - filepath.Join(exportTestDir, "provider.tf.json"), - } + exportTestDir = "../.terraformExclude" + uuid.NewString() + exportResource = "test-export6_1" queueResources = []QueueExport{ - {ResourceName: "test-queue-1", Name: "test-queue-1-" + uuid.NewString() + uniquePostfix, Description: "This is a test queue", AcwTimeoutMs: 200000}, - {ResourceName: "test-queue-2", Name: "test-queue-1-" + uuid.NewString() + uniquePostfix, Description: "This is a test queue too", AcwTimeoutMs: 200000}, - } - - userResources = []UserExport{ - {ResourceName: "test-user-1", Name: "test-user-1", Email: "test-user-1" + uuid.NewString() + "@test.com" + uniquePostfix, State: "active"}, - {ResourceName: "test-user-2", Name: "test-user-2", Email: "test-user-2" + uuid.NewString() + "@test.com" + uniquePostfix, State: "active"}, + {ResourceName: "test-queue-test-1", Name: "exclude filter - exclude me", Description: "This is an excluded bar test resource", AcwTimeoutMs: 200000}, + {ResourceName: "test-queue-test-2", Name: "exclude filter - foo - bar me", Description: "This is a foo bar test resource", AcwTimeoutMs: 200000}, + {ResourceName: "test-queue-test-3", Name: "exclude filter - fu - barre you", Description: "This is a foo bar test resource", AcwTimeoutMs: 200000}, } wrapupCodeResources = []WrapupcodeExport{ - {ResourceName: "test-wrapupcode-1", Name: "test-wrapupcode-1-" + uuid.NewString() + uniquePostfix}, - {ResourceName: "test-wrapupcode-2", Name: "test-wrapupcode-2-" + uuid.NewString() + uniquePostfix}, + {ResourceName: "test-wrapupcode-prod", Name: "exclude me"}, + {ResourceName: "test-wrapupcode-test", Name: "foo + bar me"}, + {ResourceName: "test-wrapupcode-dev", Name: "fu - barre you"}, } ) defer os.RemoveAll(exportTestDir) queueResourceDef := buildQueueResources(queueResources) - userResourcesDef := buildUserResources(userResources) wrapupcodeResourceDef := buildWrapupcodeResources(wrapupCodeResources) - config := queueResourceDef + wrapupcodeResourceDef + userResourcesDef + - generateTfExportByIncludeFilterResources( + config := queueResourceDef + wrapupcodeResourceDef + + generateTfExportByExcludeFilterResources( exportResource, exportTestDir, util.TrueValue, []string{ - strconv.Quote("genesyscloud_routing_queue::" + uniquePostfix + "$"), - strconv.Quote("genesyscloud_user::" + uniquePostfix + "$"), - strconv.Quote("genesyscloud_routing_wrapupcode::" + uniquePostfix + "$"), + strconv.Quote("genesyscloud_routing_queue::exclude filter - foo - bar me"), + strconv.Quote("genesyscloud_routing_queue::exclude_filter_-_fu_-_barre_you"), + strconv.Quote("genesyscloud_outbound_ruleset"), + strconv.Quote("genesyscloud_user"), + strconv.Quote("genesyscloud_user_roles"), + strconv.Quote("genesyscloud_flow"), }, util.FalseValue, - util.TrueValue, + util.FalseValue, []string{ strconv.Quote("genesyscloud_routing_queue." + queueResources[0].ResourceName), strconv.Quote("genesyscloud_routing_queue." + queueResources[1].ResourceName), - strconv.Quote("genesyscloud_user." + userResources[0].ResourceName), - strconv.Quote("genesyscloud_user." + userResources[1].ResourceName), + strconv.Quote("genesyscloud_routing_queue." + queueResources[2].ResourceName), strconv.Quote("genesyscloud_routing_wrapupcode." + wrapupCodeResources[0].ResourceName), strconv.Quote("genesyscloud_routing_wrapupcode." + wrapupCodeResources[1].ResourceName), + strconv.Quote("genesyscloud_routing_wrapupcode." + wrapupCodeResources[2].ResourceName), }, ) + sanitizer := resourceExporter.NewSanitizerProvider() resource.Test(t, resource.TestCase{ PreCheck: func() { util.TestAccPreCheck(t) }, ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), Steps: []resource.TestStep{ { + PreConfig: func() { + time.Sleep(30 * time.Second) + }, + // Generate a queue as well and export it Config: config, Check: resource.ComposeTestCheckFunc( - validateFileCreated(expectedFilesPath[0]), - validateFileCreated(expectedFilesPath[1]), - validateFileCreated(expectedFilesPath[2]), - validateFileCreated(expectedFilesPath[3]), + testQueueExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_queue", sanitizer.S.SanitizeResourceName(queueResources[0].Name), queueResources[0]), + testWrapupcodeExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_wrapupcode", sanitizer.S.SanitizeResourceName(wrapupCodeResources[0].Name), wrapupCodeResources[0]), + testWrapupcodeExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_wrapupcode", sanitizer.S.SanitizeResourceName(wrapupCodeResources[1].Name), wrapupCodeResources[1]), + testWrapupcodeExportEqual(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_wrapupcode", sanitizer.S.SanitizeResourceName(wrapupCodeResources[2].Name), wrapupCodeResources[2]), + testQueueExportExcludesRegEx(exportTestDir+"/"+defaultTfJSONFile, "genesyscloud_routing_queue", "(foo|fu)"), ), }, }, @@ -1447,6 +1448,96 @@ func TestAccResourceTfExportSplitFilesAsHCL(t *testing.T) { }) } +// TestAccResourceExportManagedSitesAsData checks that during an export, managed sites are exported as data source +// Managed can't be set on sites, therefore the default managed site is checked during the test that it is exported as data +func TestAccResourceExportManagedSitesAsData(t *testing.T) { + var ( + exportTestDir = filepath.Join("..", "..", ".terraform"+uuid.NewString()) + resourceID = "export" + configPath = filepath.Join(exportTestDir, defaultTfJSONFile) + siteName = "PureCloud Voice - AWS" + ) + + if err := telephonyProvidersEdgesSite.CheckForDefaultSite(siteName); err != nil { + t.Skipf("failed to get default site %v", err) + } + + defer func(path string) { + if err := os.RemoveAll(path); err != nil { + t.Logf("failed to remove dir %s: %s", path, err) + } + }(exportTestDir) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { util.TestAccPreCheck(t) }, + ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources), + Steps: []resource.TestStep{ + { + Config: generateTfExportByIncludeFilterResources( + resourceID, + exportTestDir, + util.FalseValue, // include_state_file + []string{ // include_filter_resources + strconv.Quote("genesyscloud_telephony_providers_edges_site"), + }, + util.FalseValue, // export_as_hcl + util.FalseValue, + []string{}, + ), + Check: resource.ComposeTestCheckFunc( + validateExportManagedSitesAsData(configPath, siteName), + ), + }, + }, + }) +} + +// validateExportManagedSitesAsData verifies that the default managed site 'PureCloud Voice - AWS' is exported as a data source +func validateExportManagedSitesAsData(filename, siteName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + // Check if the file exists + _, err := os.Stat(filename) + if err != nil { + return fmt.Errorf("failed to find file %s", filename) + } + + // Load the JSON content of the export file + log.Println("Loading export config into map variable") + exportData, err := loadJsonFileToMap(filename) + if err != nil { + return err + } + log.Println("Successfully loaded export config into map variable ") + + // Check if data sources exist in the exported data + if data, ok := exportData["data"].(map[string]interface{}); ok { + sites, ok := data["genesyscloud_telephony_providers_edges_site"].(map[string]interface{}) + if !ok { + return fmt.Errorf("no data sources exported for genesyscloud_telephony_providers_edges_site") + } + + log.Printf("checking that managed site with name %s is exported as data source", siteName) + + // Validate each site's name + for siteID, site := range sites { + siteAttributes, ok := site.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected structure for site %s", siteID) + } + + name, _ := siteAttributes["name"].(string) + if name == siteName { + log.Printf("Site %s with name '%s' is correctly exported as data source", siteID, siteName) + return nil + } + } + return fmt.Errorf("site with name '%s' was not exported as data source", siteName) + } else { + return fmt.Errorf("no data sources found in exported data") + } + } +} + // TestAccResourceTfExportCampaignScriptIdReferences exports two campaigns and ensures that the custom revolver OutboundCampaignAgentScriptResolver // is working properly i.e. script_id should reference a data source pointing to the Default Outbound Script under particular circumstances func TestAccResourceTfExportCampaignScriptIdReferences(t *testing.T) { diff --git a/genesyscloud/tfexporter/tf_exporter_resource_test.go b/genesyscloud/tfexporter/tf_exporter_resource_test.go index a0e4d44aa..19364f3ef 100644 --- a/genesyscloud/tfexporter/tf_exporter_resource_test.go +++ b/genesyscloud/tfexporter/tf_exporter_resource_test.go @@ -12,6 +12,7 @@ import ( architectSchedulegroups "terraform-provider-genesyscloud/genesyscloud/architect_schedulegroups" architectSchedules "terraform-provider-genesyscloud/genesyscloud/architect_schedules" authRole "terraform-provider-genesyscloud/genesyscloud/auth_role" + cMessagingSettings "terraform-provider-genesyscloud/genesyscloud/conversations_messaging_settings" employeeperformanceExternalmetricsDefinition "terraform-provider-genesyscloud/genesyscloud/employeeperformance_externalmetrics_definitions" flowLogLevel "terraform-provider-genesyscloud/genesyscloud/flow_loglevel" flowMilestone "terraform-provider-genesyscloud/genesyscloud/flow_milestone" @@ -39,6 +40,7 @@ import ( obCampaignRule "terraform-provider-genesyscloud/genesyscloud/outbound_campaignrule" outboundContactList "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list" outboundContactListContact "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list_contact" + outboundContactListTemplate "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list_template" obContactListFilter "terraform-provider-genesyscloud/genesyscloud/outbound_contactlistfilter" obDncList "terraform-provider-genesyscloud/genesyscloud/outbound_dnclist" obfst "terraform-provider-genesyscloud/genesyscloud/outbound_filespecificationtemplate" @@ -52,7 +54,9 @@ import ( respmanagementLibrary "terraform-provider-genesyscloud/genesyscloud/responsemanagement_library" responsemanagementResponse "terraform-provider-genesyscloud/genesyscloud/responsemanagement_response" respManagementRespAsset "terraform-provider-genesyscloud/genesyscloud/responsemanagement_responseasset" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" routingEmailRoute "terraform-provider-genesyscloud/genesyscloud/routing_email_route" + routinglanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" routingQueueConditionalGroupRouting "terraform-provider-genesyscloud/genesyscloud/routing_queue_conditional_group_routing" routingQueueOutboundEmailAddress "terraform-provider-genesyscloud/genesyscloud/routing_queue_outbound_email_address" @@ -74,7 +78,6 @@ import ( userRoles "terraform-provider-genesyscloud/genesyscloud/user_roles" webdeployConfig "terraform-provider-genesyscloud/genesyscloud/webdeployments_configuration" webdeployDeploy "terraform-provider-genesyscloud/genesyscloud/webdeployments_deployment" - "testing" edgePhone "terraform-provider-genesyscloud/genesyscloud/telephony_providers_edges_phone" @@ -151,9 +154,9 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_user"] = gcloud.ResourceUser() providerResources["genesyscloud_responsemanagement_library"] = respmanagementLibrary.ResourceResponsemanagementLibrary() providerResources["genesyscloud_responsemanagement_responseasset"] = respManagementRespAsset.ResourceResponseManagementResponseAsset() - providerResources["genesyscloud_routing_email_domain"] = gcloud.ResourceRoutingEmailDomain() + providerResources["genesyscloud_routing_email_domain"] = routingEmailDomain.ResourceRoutingEmailDomain() providerResources["genesyscloud_routing_email_route"] = routingEmailRoute.ResourceRoutingEmailRoute() - providerResources["genesyscloud_routing_language"] = gcloud.ResourceRoutingLanguage() + providerResources["genesyscloud_routing_language"] = routinglanguage.ResourceRoutingLanguage() providerResources["genesyscloud_routing_queue"] = routingQueue.ResourceRoutingQueue() providerResources["genesyscloud_routing_queue_conditional_group_routing"] = routingQueueConditionalGroupRouting.ResourceRoutingQueueConditionalGroupRouting() providerResources["genesyscloud_routing_queue_outbound_email_address"] = routingQueueOutboundEmailAddress.ResourceRoutingQueueOutboundEmailAddress() @@ -182,6 +185,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_outbound_callabletimeset"] = obCallableTimeset.ResourceOutboundCallabletimeset() providerResources["genesyscloud_outbound_campaign"] = obCampaign.ResourceOutboundCampaign() providerResources["genesyscloud_outbound_contact_list"] = outboundContactList.ResourceOutboundContactList() + providerResources["genesyscloud_outbound_contact_list_template"] = outboundContactListTemplate.ResourceOutboundContactListTemplate() providerResources["genesyscloud_outbound_contact_list_contact"] = outboundContactListContact.ResourceOutboundContactListContact() providerResources["genesyscloud_outbound_contactlistfilter"] = obContactListFilter.ResourceOutboundContactlistfilter() providerResources["genesyscloud_outbound_messagingcampaign"] = ob.ResourceOutboundMessagingCampaign() @@ -200,7 +204,7 @@ func (r *registerTestInstance) registerTestResources() { providerResources["genesyscloud_task_management_workbin"] = workbin.ResourceTaskManagementWorkbin() providerResources["genesyscloud_task_management_workitem_schema"] = workitemSchema.ResourceTaskManagementWorkitemSchema() providerResources["genesyscloud_task_management_worktype"] = worktype.ResourceTaskManagementWorktype() - + providerResources["genesyscloud_conversations_messaging_settings"] = cMessagingSettings.ResourceConversationsMessagingSettings() providerResources["genesyscloud_tf_export"] = ResourceTfExport() } @@ -248,6 +252,7 @@ func (r *registerTestInstance) registerTestExporters() { RegisterExporter("genesyscloud_outbound_callabletimeset", obCallableTimeset.OutboundCallableTimesetExporter()) RegisterExporter("genesyscloud_outbound_campaign", obCampaign.OutboundCampaignExporter()) RegisterExporter("genesyscloud_outbound_contact_list", outboundContactList.OutboundContactListExporter()) + RegisterExporter("genesyscloud_outbound_contact_list_template", outboundContactListTemplate.OutboundContactListTemplateExporter()) RegisterExporter("genesyscloud_outbound_contact_list_contact", outboundContactListContact.ContactExporter()) RegisterExporter("genesyscloud_outbound_contactlistfilter", obContactListFilter.OutboundContactlistfilterExporter()) RegisterExporter("genesyscloud_outbound_messagingcampaign", ob.OutboundMessagingcampaignExporter()) @@ -263,9 +268,9 @@ func (r *registerTestInstance) registerTestExporters() { RegisterExporter("genesyscloud_responsemanagement_library", respmanagementLibrary.ResponsemanagementLibraryExporter()) RegisterExporter("genesyscloud_responsemanagement_response", responsemanagementResponse.ResponsemanagementResponseExporter()) RegisterExporter("genesyscloud_responsemanagement_responseasset", respManagementRespAsset.ExporterResponseManagementResponseAsset()) - RegisterExporter("genesyscloud_routing_email_domain", gcloud.RoutingEmailDomainExporter()) + RegisterExporter("genesyscloud_routing_email_domain", routingEmailDomain.RoutingEmailDomainExporter()) RegisterExporter("genesyscloud_routing_email_route", routingEmailRoute.RoutingEmailRouteExporter()) - RegisterExporter("genesyscloud_routing_language", gcloud.RoutingLanguageExporter()) + RegisterExporter("genesyscloud_routing_language", routinglanguage.RoutingLanguageExporter()) RegisterExporter("genesyscloud_routing_queue", routingQueue.RoutingQueueExporter()) RegisterExporter("genesyscloud_routing_queue_conditional_group_routing", routingQueueConditionalGroupRouting.RoutingQueueConditionalGroupRoutingExporter()) RegisterExporter("genesyscloud_routing_queue_outbound_email_address", routingQueueOutboundEmailAddress.OutboundRoutingQueueOutboundEmailAddressExporter()) @@ -300,6 +305,7 @@ func (r *registerTestInstance) registerTestExporters() { RegisterExporter("genesyscloud_task_management_workbin", workbin.TaskManagementWorkbinExporter()) RegisterExporter("genesyscloud_task_management_workitem_schema", workbin.TaskManagementWorkbinExporter()) RegisterExporter("genesyscloud_task_management_worktype", worktype.TaskManagementWorktypeExporter()) + RegisterExporter("genesyscloud_conversations_messaging_settings", cMessagingSettings.ConversationsMessagingSettingsExporter()) RegisterExporter("genesyscloud_script", scripts.ExporterScript()) @@ -309,6 +315,8 @@ func (r *registerTestInstance) registerTestExporters() { func (r *registerTestInstance) registerTestDataSources() { providerDataSources["genesyscloud_auth_division_home"] = gcloud.DataSourceAuthDivisionHome() providerDataSources["genesyscloud_script"] = scripts.DataSourceScript() + providerDataSources["genesyscloud_telephony_providers_edges_site"] = edgeSite.DataSourceSite() + providerDataSources["genesyscloud_conversations_messaging_settings"] = cMessagingSettings.DataSourceConversationsMessagingSettings() } func RegisterExporter(exporterName string, resourceExporter *resourceExporter.ResourceExporter) { diff --git a/genesyscloud/tfexporter/tfstate_exporter.go b/genesyscloud/tfexporter/tfstate_exporter.go index 1e5e16aba..d7653ee77 100644 --- a/genesyscloud/tfexporter/tfstate_exporter.go +++ b/genesyscloud/tfexporter/tfstate_exporter.go @@ -52,7 +52,7 @@ func (t *TFStateFileWriter) writeTfState() diag.Diagnostics { Primary: resource.State, Provider: "provider.genesyscloud", } - tfstate.RootModule().Resources[resource.Type+"."+resource.Name] = resourceState + tfstate.RootModule().Resources[resource.ResourceType+resource.Type+"."+resource.Name] = resourceState } data, err := json.MarshalIndent(tfstate, "", " ") diff --git a/genesyscloud/util/feature_toggles/user_addresses.go b/genesyscloud/util/feature_toggles/user_addresses.go new file mode 100644 index 000000000..161ee6468 --- /dev/null +++ b/genesyscloud/util/feature_toggles/user_addresses.go @@ -0,0 +1,15 @@ +package feature_toggles + +import "os" + +const newUserAddressesLogicEnvToggle = "USE_NEW_USER_ADDRESS_LOGIC" + +func NewUserAddressesLogicToggleName() string { + return newUserAddressesLogicEnvToggle +} + +func NewUserAddressesLogicExists() bool { + var exists bool + _, exists = os.LookupEnv(newUserAddressesLogicEnvToggle) + return exists +} diff --git a/genesyscloud/util/util_e164.go b/genesyscloud/util/util_e164.go new file mode 100644 index 000000000..0a0c064d4 --- /dev/null +++ b/genesyscloud/util/util_e164.go @@ -0,0 +1,15 @@ +package util + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/nyaruka/phonenumbers" +) + +func FormatAsE164Number(number string) (string, diag.Diagnostics) { + phoneNumber, err := phonenumbers.Parse(number, "US") + if err != nil { + return "", diag.Errorf("Failed to format phone number %s: %s", number, err) + } + formattedNum := phonenumbers.Format(phoneNumber, phonenumbers.E164) + return formattedNum, nil +} diff --git a/genesyscloud/validators/validators.go b/genesyscloud/validators/validators.go index 66f89f83a..23983498e 100644 --- a/genesyscloud/validators/validators.go +++ b/genesyscloud/validators/validators.go @@ -8,6 +8,7 @@ import ( "strings" + "terraform-provider-genesyscloud/genesyscloud/util" "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" files "terraform-provider-genesyscloud/genesyscloud/util/files" @@ -16,17 +17,15 @@ import ( "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/nyaruka/phonenumbers" ) func ValidatePhoneNumber(number interface{}, _ cty.Path) diag.Diagnostics { if numberStr, ok := number.(string); ok { - phoneNumber, err := phonenumbers.Parse(numberStr, "US") + + formattedNum, err := util.FormatAsE164Number(numberStr) if err != nil { - return diag.Errorf("Failed to validate phone number %s: %s", numberStr, err) + return err } - - formattedNum := phonenumbers.Format(phoneNumber, phonenumbers.E164) if formattedNum != numberStr { return diag.Errorf("Failed to parse number in an E.164 format. Passed %s and expected: %s", numberStr, formattedNum) } diff --git a/genesyscloud/webdeployments_configuration/resource_genesyscloud_webdeployments_configuration_test.go b/genesyscloud/webdeployments_configuration/resource_genesyscloud_webdeployments_configuration_test.go index 8e4abe831..90d56d53e 100644 --- a/genesyscloud/webdeployments_configuration/resource_genesyscloud_webdeployments_configuration_test.go +++ b/genesyscloud/webdeployments_configuration/resource_genesyscloud_webdeployments_configuration_test.go @@ -1048,7 +1048,7 @@ func complexConfigurationResource(name, description, kbId string, nestedBlocks . `, name, description, kbId, strings.Join(nestedBlocks, "\n")) } -func generateWebDeploymentConfigCobrowseSettings(cbEnabled, cbAllowAgentControl string, cbAllowAgentNavigation string, cbChannels []string, cbMaskSelectors []string, cbReadonlySelectors []string, pauseCriteriaBlocks ...string,) string { +func generateWebDeploymentConfigCobrowseSettings(cbEnabled, cbAllowAgentControl string, cbAllowAgentNavigation string, cbChannels []string, cbMaskSelectors []string, cbReadonlySelectors []string, pauseCriteriaBlocks ...string) string { return fmt.Sprintf(` cobrowse { diff --git a/genesyscloud/webdeployments_deployment/resource_genesyscloud_webdeployments_deployment_test.go b/genesyscloud/webdeployments_deployment/resource_genesyscloud_webdeployments_deployment_test.go index 6582e2602..1379c8137 100644 --- a/genesyscloud/webdeployments_deployment/resource_genesyscloud_webdeployments_deployment_test.go +++ b/genesyscloud/webdeployments_deployment/resource_genesyscloud_webdeployments_deployment_test.go @@ -70,7 +70,7 @@ func TestAccResourceWebDeploymentsDeployment_AllowedDomains(t *testing.T) { Config: deploymentResourceWithAllowedDomains(t, deploymentName, firstDomain), Check: resource.ComposeTestCheckFunc( func(s *terraform.State) error { - time.Sleep(30 * time.Second) // Wait for 30 seconds for status to become active + time.Sleep(45 * time.Second) // Wait for 30 seconds for status to become active return nil }, resource.TestCheckResourceAttr(fullResourceName, "name", deploymentName), diff --git a/go.mod b/go.mod index debecdddd..0736603f0 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mypurecloud/platform-client-sdk-go/v133 v133.0.0 - github.com/nyaruka/phonenumbers v1.3.6 + github.com/nyaruka/phonenumbers v1.4.0 github.com/rjNemo/underscore v0.6.1 - github.com/zclconf/go-cty v1.14.4 + github.com/zclconf/go-cty v1.15.0 gonum.org/v1/gonum v0.15.0 ) @@ -34,7 +34,7 @@ require ( github.com/yuin/goldmark v1.7.1 // indirect 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/exp v0.0.0-20240525044651-4c93da0ed11d // 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 @@ -101,7 +101,7 @@ require ( 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 + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.51.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 7f2f226e0..933973a4e 100644 --- a/go.sum +++ b/go.sum @@ -259,8 +259,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mypurecloud/platform-client-sdk-go/v133 v133.0.0 h1:QXLhk65lm4ObgEZLm49bJauEtigMLV/Xo61wF3t3eDE= github.com/mypurecloud/platform-client-sdk-go/v133 v133.0.0/go.mod h1:xzHvr5pv/axk+ieUu4gpvZEvMcqFTFcR53IzPxz55OU= -github.com/nyaruka/phonenumbers v1.3.6 h1:33owXWp4d1U+Tyaj9fpci6PbvaQZcXBUO2FybeKeLwQ= -github.com/nyaruka/phonenumbers v1.3.6/go.mod h1:Ut+eFwikULbmCenH6InMKL9csUNLyxHuBLyfkpum11s= +github.com/nyaruka/phonenumbers v1.4.0 h1:ddhWiHnHCIX3n6ETDA58Zq5dkxkjlvgrDWM2OHHPCzU= +github.com/nyaruka/phonenumbers v1.4.0/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -344,8 +344,8 @@ github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= @@ -369,8 +369,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= -golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -512,8 +512,8 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/jenkins/tests/Jenkinsfile b/jenkins/tests/Jenkinsfile index 3884e0d17..3c47db540 100644 --- a/jenkins/tests/Jenkinsfile +++ b/jenkins/tests/Jenkinsfile @@ -76,9 +76,9 @@ pipeline { sh "gotestsum --junitfile unit.xml --format standard-verbose ./genesyscloud/... -run 'Test|TestUnit' -skip 'TestAcc' -v -timeout 30m -count=1 -cover -coverprofile=coverageUnit.out" } - } } - } + } + } stage('Architect Tests') { environment { @@ -94,7 +94,7 @@ pipeline { } } } - } + } stage('Idp Tests') { environment { @@ -198,6 +198,9 @@ pipeline { environment { TF_ACC=1 TF_LOG="DEBUG" + TEST_DNC_GRYPHON_LICENSE_KEY="D7CE-E914-E4D4-4121-9428-36BD-07D8-9A41" + TEST_DNC_GRYPHON_PROD_LICENSE_KEY="4ADA-9A3B-5DAD-4FAD-95A7-4C31-425B-8594" + TEST_DNCCOM_LICENSE_KEY="96CAAC02650543056DF1ADA796A0082ED152561EDEE1" } steps { catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { @@ -517,201 +520,194 @@ pipeline { writeFile file: 'coverageReport.html', text: updatedHtmlFile - // Generate manual report - def xmlContent = readFile 'test-results.xml' - - // Extract test case information using regular expressions - def testCases = [] - def testCasePattern = /]*>([\s\S]*?)<\/failure>/ - def skippedPattern = /]*>([\s\S]*?)<\/skipped>/ - - def matcher = xmlContent =~ testCasePattern - while (matcher.find()) { - def classname = matcher.group(1) - def name = matcher.group(2) - def time = matcher.group(3) - def remainingText = xmlContent.substring(matcher.end()) - - def endIndex = remainingText.indexOf('') - def details = endIndex != -1 ? remainingText.substring(0, endIndex) : "" - - def result = [ - name: name, - classname: classname, - time: time, - status: 'Passed', // Default to Passed - reason: '', - log: details - ] - - // Check for failure or skipped - def failureMatcher = details =~ failurePattern - def skippedMatcher = details =~ skippedPattern - if (failureMatcher.find()) { - result.status = 'Failed' - result.log = failureMatcher.group(1).trim() - } else if (skippedMatcher.find()) { - result.status = 'Skipped' - result.log = skippedMatcher.group(1).trim() - } - - // Extract reason - if (result.status == 'Failed') { - result.reason = extractReason(result.log) - } else if (result.status == 'Skipped') { - result.reason = extractReason(result.log) - } - - testCases << result - } +def xmlContent = readFile 'test-results.xml' + +// Extract test case information using regular expressions +def testCases = [] +def testCasePattern = /]*>([\s\S]*?)<\/failure>/ +def skippedPattern = /') + def details = endIndex != -1 ? remainingText.substring(0, endIndex) : "" + + def result = [ + name: name, + classname: classname, + time: time, + status: 'Passed', // Default to Passed + reason: '', + log: details + ] + + // Check for failure or skipped + def failureMatcher = details =~ failurePattern + def skippedMatcher = details =~ skippedPattern + if (failureMatcher.find()) { + result.status = 'Failed' + result.log = failureMatcher.group(1).trim() + result.reason = extractReason(result.log) + } else if (skippedMatcher.find()) { + result.status = 'Skipped' + result.reason = skippedMatcher.group(1).trim() // Get the skipped reason directly + } - // Generate HTML report - def html = new StringBuilder() - html.append(""" - - - - - -

Test Results

-
-
Total Tests: ${testCases.size()}
-
Total Passed: ${testCases.count { it.status == 'Passed' }}
-
Total Failed: ${testCases.count { it.status == 'Failed' }}
-
Total Skipped: ${testCases.count { it.status == 'Skipped' }}
-
- -

Failed Tests

- - - - - - - - """) - - testCases.findAll { it.status == 'Failed' }.each { result -> - html.append(""" - - - - - - - """) - } + testCases << result +} - html.append(""" -
StatusClass NameTest NameDuration (s)
${result.status}${result.classname} - ${result.name} - ${result.time}
- -

Skipped Tests

- - - - - - - """) - - testCases.findAll { it.status == 'Skipped' }.each { result -> - html.append(""" - - - - - - """) - } +// Generate HTML report +def html = new StringBuilder() +html.append(""" + + + + + +

Test Results

+
+
Total Tests: ${testCases.size()}
+
Total Passed: ${testCases.count { it.status == 'Passed' }}
+
Total Failed: ${testCases.count { it.status == 'Failed' }}
+
Total Skipped: ${testCases.count { it.status == 'Skipped' }}
+
+ +

Failed Tests

+
StatusClass NameTest Name
${result.status}${result.classname}${result.name}
+ + + + + + +""") + +testCases.findAll { it.status == 'Failed' }.each { result -> + html.append(""" + + + + + + + """) +} - html.append(""" -
StatusClass NameTest NameDuration (s)
${result.status}${result.classname} + ${result.name} + ${result.time}
- -

Passed Tests

- - - - - - - - """) - - testCases.findAll { it.status == 'Passed' }.each { result -> - html.append(""" - - - - - - - """) - } +html.append(""" +
StatusClass NameTest NameDuration (s)
${result.status}${result.classname}${result.name}${result.time}
+ +

Skipped Tests

+ + + + + + + +""") + +testCases.findAll { it.status == 'Skipped' }.each { result -> + html.append(""" + + + + + + + """) +} - html.append(""" -
StatusClass NameTest NameReason
${result.status}${result.classname}${result.name}${result.reason}
- -

Test Details

- """) - - testCases.findAll { it.status == 'Failed' }.each { result -> - html.append(""" -
-
-

${result.classname}.${result.name}

-

Status: ${result.status}

-

Duration: ${result.time}s

-
- ${result.log} -
-
-
- """) - } +html.append(""" + + +

Passed Tests

+ + + + + + +""") + +testCases.findAll { it.status == 'Passed' }.each { result -> + html.append(""" + + + + + + + """) +} + +html.append(""" +
StatusClass NameTest NameDuration (s)
${result.status}${result.classname}${result.name}${result.time}
+ +

Test Details

+""") + +testCases.findAll { it.status == 'Failed' }.each { result -> + html.append(""" +
+
+

${result.classname}.${result.name}

+

Status: ${result.status}

+

Duration: ${result.time}s

+
+ ${result.log} +
+
+
+ """) +} - html.append(""" - - - """) +html.append(""" + + +""") - // Save the HTML report to a file - writeFile file: 'test-report.html', text: html.toString() +// Save the HTML report to a file +writeFile file: 'test-report.html', text: html.toString() - // Optionally, print the location of the HTML report + + // Optionally, print the location of the HTML report echo "HTML report generated: \${env.WORKSPACE}/test-report.html" - } +} archiveArtifacts artifacts: 'coverageReport.html,test-report.html,test-results.xml', allowEmptyArchive: true } } } } - def extractReason(String log) { - // Extract reason from the log +// Function to extract reason from the log for failed tests +def extractReason(String log) { def reason = "" - def reasonPattern = /message="([^"]*)">/ + // Extract the relevant part of the failed message + def reasonPattern = /_genesyscloud_.*?\.go:\d+: (.*?) ---/ def matcher = log =~ reasonPattern if (matcher.find()) { reason = matcher.group(1).trim() } return reason -} - - - +} \ No newline at end of file diff --git a/main.go b/main.go index ad44a2841..5174c193f 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ import ( obCampaignRule "terraform-provider-genesyscloud/genesyscloud/outbound_campaignrule" obContactList "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list" outboundContactListContact "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list_contact" + obContactListTemplate "terraform-provider-genesyscloud/genesyscloud/outbound_contact_list_template" obContactListFilter "terraform-provider-genesyscloud/genesyscloud/outbound_contactlistfilter" obDncList "terraform-provider-genesyscloud/genesyscloud/outbound_dnclist" obfst "terraform-provider-genesyscloud/genesyscloud/outbound_filespecificationtemplate" @@ -62,14 +63,14 @@ import ( respmanagementLibrary "terraform-provider-genesyscloud/genesyscloud/responsemanagement_library" responsemanagementResponse "terraform-provider-genesyscloud/genesyscloud/responsemanagement_response" responsemanagementResponseasset "terraform-provider-genesyscloud/genesyscloud/responsemanagement_responseasset" + routingEmailDomain "terraform-provider-genesyscloud/genesyscloud/routing_email_domain" routingEmailRoute "terraform-provider-genesyscloud/genesyscloud/routing_email_route" + routingLanguage "terraform-provider-genesyscloud/genesyscloud/routing_language" routingQueue "terraform-provider-genesyscloud/genesyscloud/routing_queue" routingUtilization "terraform-provider-genesyscloud/genesyscloud/routing_utilization" routingUtilizationLabel "terraform-provider-genesyscloud/genesyscloud/routing_utilization_label" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - + cMessageSettings "terraform-provider-genesyscloud/genesyscloud/conversations_messaging_settings" routingQueueConditionalGroupRouting "terraform-provider-genesyscloud/genesyscloud/routing_queue_conditional_group_routing" routingQueueOutboundEmailAddress "terraform-provider-genesyscloud/genesyscloud/routing_queue_outbound_email_address" routingSettings "terraform-provider-genesyscloud/genesyscloud/routing_settings" @@ -96,6 +97,9 @@ import ( userRoles "terraform-provider-genesyscloud/genesyscloud/user_roles" webDeployConfig "terraform-provider-genesyscloud/genesyscloud/webdeployments_configuration" webDeployDeploy "terraform-provider-genesyscloud/genesyscloud/webdeployments_deployment" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" ) // Run "go generate" to format example terraform files and generate the docs for the registry/website @@ -182,6 +186,7 @@ func registerResources() { obCampaign.SetRegistrar(regInstance) //Registering outbound campaign obContactList.SetRegistrar(regInstance) //Registering outbound contact list obContactListFilter.SetRegistrar(regInstance) //Registering outbound contact list filter + obContactListTemplate.SetRegistrar(regInstance) //Registering outbound contact list template obSequence.SetRegistrar(regInstance) //Registering outbound sequence obCampaignRule.SetRegistrar(regInstance) //Registering outbound campaignrule obSettings.SetRegistrar(regInstance) //Registering outbound settings @@ -236,10 +241,12 @@ func registerResources() { routingQueueOutboundEmailAddress.SetRegistrar(regInstance) //Registering routing queue outbound email address outboundContactListContact.SetRegistrar(regInstance) //Registering outbound contact list contact routingSettings.SetRegistrar(regInstance) //Registering routing Settings - routingUtilization.SetRegistrar(regInstance) // Registering routing utilization - routingUtilizationLabel.SetRegistrar(regInstance) // Registering routing utilization label + routingUtilization.SetRegistrar(regInstance) //Registering routing utilization + routingUtilizationLabel.SetRegistrar(regInstance) //Registering routing utilization label journeyViews.SetRegistrar(regInstance) //Registering journey views - + routingLanguage.SetRegistrar(regInstance) //Registering Routing Language + routingEmailDomain.SetRegistrar(regInstance) //Registering Routing Email Domain + cMessageSettings.SetRegistrar(regInstance) // Registering conversations messaging settings // setting resources for Use cases like TF export where provider is used in resource classes. tfexp.SetRegistrar(regInstance) //Registering tf exporter registrar.SetResources(providerResources, providerDataSources) diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/01_create.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/01_create.tf index c1296e27c..7441d8767 100644 --- a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/01_create.tf +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/01_create.tf @@ -14,6 +14,11 @@ resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id maximum_probability = 0.333 } + # optional + trigger_with_outcome_quantile_conditions { + outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id + max_quantile_threshold = 0.333 + } page_url_conditions { values = ["some_value"] operator = "containsAll" diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/02_update_all_optional_changed.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/02_update_all_optional_changed.tf index dd73fb053..589c2a26d 100644 --- a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/02_update_all_optional_changed.tf +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/02_update_all_optional_changed.tf @@ -15,6 +15,12 @@ resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { maximum_probability = 0.666 probability = 0.125 } + # optional + trigger_with_outcome_quantile_conditions { + outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id + max_quantile_threshold = 0.666 + fallback_quantile_threshold = 0.125 + } page_url_conditions { values = ["some_other_value", "some_other_value_2"] operator = "containsAny" diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/03_update_remove_trigger_with_outcome_probability_conditions.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/03_update_remove_trigger_with_outcome_probability_conditions.tf index b4f800610..48eb3f539 100644 --- a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/03_update_remove_trigger_with_outcome_probability_conditions.tf +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/03_update_remove_trigger_with_outcome_probability_conditions.tf @@ -10,14 +10,18 @@ resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { } start_date = "2022-07-04T12:00:00.000000" # optional + trigger_with_outcome_quantile_conditions { + outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id + max_quantile_threshold = 0.666 + fallback_quantile_threshold = 0.125 + } + # optional page_url_conditions { values = ["some_other_value", "some_other_value_2"] operator = "containsAny" } ignore_frequency_cap = true end_date = "2022-08-01T10:30:00.999000" - - depends_on = [genesyscloud_journey_segment.terraform_test_-TEST-CASE-_action_map_dependency] } resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_dependency" { @@ -39,3 +43,23 @@ resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_d } } } + +resource "genesyscloud_journey_outcome" "terraform_test_-TEST-CASE-_action_map_dependency" { + is_active = true + display_name = "terraform_test_-TEST-CASE-_action_map_dependency" + description = "test description of journey outcome" + is_positive = true + journey { + patterns { + criteria { + key = "page.title" + values = ["Title"] + operator = "notEqual" + should_ignore_case = true + } + count = 1 + stream_type = "Web" + session_type = "web" + } + } +} diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/04_update_remove_page_url_conditions.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/04_update_remove_page_url_conditions.tf index 7cf97033c..a681a255f 100644 --- a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/04_update_remove_page_url_conditions.tf +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/04_update_remove_page_url_conditions.tf @@ -10,10 +10,14 @@ resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { } start_date = "2022-07-04T12:00:00.000000" # optional + trigger_with_outcome_quantile_conditions { + outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id + max_quantile_threshold = 0.666 + fallback_quantile_threshold = 0.125 + } + # optional ignore_frequency_cap = true end_date = "2022-08-01T10:30:00.999000" - - depends_on = [genesyscloud_journey_segment.terraform_test_-TEST-CASE-_action_map_dependency] } resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_dependency" { @@ -35,3 +39,23 @@ resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_d } } } + +resource "genesyscloud_journey_outcome" "terraform_test_-TEST-CASE-_action_map_dependency" { + is_active = true + display_name = "terraform_test_-TEST-CASE-_action_map_dependency" + description = "test description of journey outcome" + is_positive = true + journey { + patterns { + criteria { + key = "page.title" + values = ["Title"] + operator = "notEqual" + should_ignore_case = true + } + count = 1 + stream_type = "Web" + session_type = "web" + } + } +} diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/05_update_remove_ignore_frequency_cap.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/05_update_remove_ignore_frequency_cap.tf index a62c9d674..7527266fa 100644 --- a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/05_update_remove_ignore_frequency_cap.tf +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/05_update_remove_ignore_frequency_cap.tf @@ -10,9 +10,13 @@ resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { } start_date = "2022-07-04T12:00:00.000000" # optional + trigger_with_outcome_quantile_conditions { + outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id + max_quantile_threshold = 0.666 + fallback_quantile_threshold = 0.125 + } + # optional end_date = "2022-08-01T10:30:00.999000" - - depends_on = [genesyscloud_journey_segment.terraform_test_-TEST-CASE-_action_map_dependency] } resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_dependency" { @@ -34,3 +38,23 @@ resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_d } } } + +resource "genesyscloud_journey_outcome" "terraform_test_-TEST-CASE-_action_map_dependency" { + is_active = true + display_name = "terraform_test_-TEST-CASE-_action_map_dependency" + description = "test description of journey outcome" + is_positive = true + journey { + patterns { + criteria { + key = "page.title" + values = ["Title"] + operator = "notEqual" + should_ignore_case = true + } + count = 1 + stream_type = "Web" + session_type = "web" + } + } +} diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/06_update_remove_end_date.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/06_update_remove_end_date.tf index e2f3c347b..c448a30c1 100644 --- a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/06_update_remove_end_date.tf +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/06_update_remove_end_date.tf @@ -10,8 +10,11 @@ resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { } start_date = "2022-07-04T12:00:00.000000" # optional - - depends_on = [genesyscloud_journey_segment.terraform_test_-TEST-CASE-_action_map_dependency] + trigger_with_outcome_quantile_conditions { + outcome_id = genesyscloud_journey_outcome.terraform_test_-TEST-CASE-_action_map_dependency.id + max_quantile_threshold = 0.666 + fallback_quantile_threshold = 0.125 + } } resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_dependency" { @@ -33,3 +36,23 @@ resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_d } } } + +resource "genesyscloud_journey_outcome" "terraform_test_-TEST-CASE-_action_map_dependency" { + is_active = true + display_name = "terraform_test_-TEST-CASE-_action_map_dependency" + description = "test description of journey outcome" + is_positive = true + journey { + patterns { + criteria { + key = "page.title" + values = ["Title"] + operator = "notEqual" + should_ignore_case = true + } + count = 1 + stream_type = "Web" + session_type = "web" + } + } +} diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/07_update_remove_trigger_with_outcome_quantile_conditions.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/07_update_remove_trigger_with_outcome_quantile_conditions.tf new file mode 100644 index 000000000..b4f800610 --- /dev/null +++ b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/07_update_remove_trigger_with_outcome_quantile_conditions.tf @@ -0,0 +1,41 @@ +resource "genesyscloud_journey_action_map" "terraform_test_-TEST-CASE-" { + # required + display_name = "terraform_test_-TEST-CASE-_updated" + trigger_with_segments = [genesyscloud_journey_segment.terraform_test_-TEST-CASE-_action_map_dependency.id] + activation { + type = "immediate" + } + action { + media_type = "webMessagingOffer" + } + start_date = "2022-07-04T12:00:00.000000" + # optional + page_url_conditions { + values = ["some_other_value", "some_other_value_2"] + operator = "containsAny" + } + ignore_frequency_cap = true + end_date = "2022-08-01T10:30:00.999000" + + depends_on = [genesyscloud_journey_segment.terraform_test_-TEST-CASE-_action_map_dependency] +} + +resource "genesyscloud_journey_segment" "terraform_test_-TEST-CASE-_action_map_dependency" { + display_name = "terraform_test_-TEST-CASE-_action_map_dependency" + color = "#008000" + scope = "Session" + should_display_to_agent = false + journey { + patterns { + criteria { + key = "page.title" + values = ["Title"] + operator = "notEqual" + should_ignore_case = true + } + count = 1 + stream_type = "Web" + session_type = "web" + } + } +} diff --git a/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/07_update_remove_trigger_with_segments.tf b/test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/08_update_remove_trigger_with_segments.tf similarity index 100% rename from test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/07_update_remove_trigger_with_segments.tf rename to test/data/resource/genesyscloud_journey_action_map/basic_optional_attributes/08_update_remove_trigger_with_segments.tf