Skip to content

Commit

Permalink
Inspired by DEVTOOLING-741 - Hardening E.164 handling (#1178)
Browse files Browse the repository at this point in the history
* Closes E.164 number formatting inconsistencies that could theoretically occur by ensuring all instances that we validate for an E.164 formatted number from user config (which are applied on create/update) are also returned (via read) as an E.164 formatted number to maintain consistency in number handling. This likely means we could remove the custom validate export handling for E164 that was inconsistently applied and used custom logic.

* Add unit test cases for Format E164

* Fixes for acceptance test updates

* Fix PR comments

* Update the E.164 utilities to be able to pass in the organization's Default Country Code so it handles parsing and appending the correct international phone code to phone numbers. For example, if the default country code is JP, the default prefix is +81 instead of +1 for US.
  • Loading branch information
bbbco authored Jul 30, 2024
1 parent b437ad7 commit 9ceb1ad
Show file tree
Hide file tree
Showing 18 changed files with 639 additions and 27 deletions.
4 changes: 2 additions & 2 deletions docs/data-sources/telephony_providers_edges_did.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
page_title: "genesyscloud_telephony_providers_edges_did Data Source - terraform-provider-genesyscloud"
subcategory: ""
description: |-
Data source for Genesys Cloud DID. The identifier is the E-164 phone number.
Data source for Genesys Cloud DID. The identifier is the E.164 phone number.
---

# genesyscloud_telephony_providers_edges_did (Data Source)

Data source for Genesys Cloud DID. The identifier is the E-164 phone number.
Data source for Genesys Cloud DID. The identifier is the E.164 phone number.

## Example Usage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,13 @@ func readIvrConfig(ctx context.Context, d *schema.ResourceData, meta interface{}
}

_ = d.Set("name", *ivrConfig.Name)
_ = d.Set("dnis", lists.StringListToSetOrNil(ivrConfig.Dnis))
if ivrConfig.Dnis == nil || *ivrConfig.Dnis == nil {
_ = d.Set("dnis", nil)
} else {
utilE164 := util.NewUtilE164Service()
dnis := lists.Map(*ivrConfig.Dnis, utilE164.FormatAsCalculatedE164Number)
_ = d.Set("dnis", lists.StringListToSetOrNil(&dnis))
}

resourcedata.SetNillableValue(d, "description", ivrConfig.Description)
resourcedata.SetNillableReference(d, "open_hours_flow_id", ivrConfig.OpenHoursFlow)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package external_contacts

import (
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -89,7 +90,12 @@ func flattenPhoneNumber(phonenumber *platformclientv2.Phonenumber) []interface{}
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "display", phonenumber.Display)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "extension", phonenumber.Extension)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "accepts_sms", phonenumber.AcceptsSMS)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "e164", phonenumber.E164)
var phoneNumberE164 string
if phonenumber != nil && phonenumber.E164 != nil && *phonenumber.E164 != "" {
utilE164 := util.NewUtilE164Service()
phoneNumberE164 = utilE164.FormatAsCalculatedE164Number(*phonenumber.E164)
}
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "e164", &phoneNumberE164)
resourcedata.SetMapValueIfNotNil(phonenumberInterface, "country_code", phonenumber.CountryCode)
return []interface{}{phonenumberInterface}
}
Expand Down
9 changes: 5 additions & 4 deletions genesyscloud/group/resource_genesyscloud_group_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ func validateAddressesMap(m map[string]interface{}) error {

func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2.Groupcontact) []interface{} {
addressSlice := make([]interface{}, 0)
utilE164 := util.NewUtilE164Service()
for _, address := range *addresses {
if address.MediaType != nil {
if *address.MediaType == groupPhoneType {
phoneNumber := make(map[string]interface{})

// Strip off any parentheses from phone numbers
if address.Address != nil {
phoneNumber["number"], _ = util.FormatAsE164Number(strings.Trim(*address.Address, "()"))
phoneNumber["number"] = utilE164.FormatAsCalculatedE164Number(strings.Trim(*address.Address, "()"))
}

resourcedata.SetMapValueIfNotNil(phoneNumber, "extension", address.Extension)
Expand All @@ -44,7 +45,7 @@ func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2
if address.Address == nil &&
address.Extension == nil &&
address.Display != nil {
setExtensionOrNumberBasedOnDisplay(d, phoneNumber, &address)
setExtensionOrNumberBasedOnDisplay(d, phoneNumber, &address, utilE164)
}

addressSlice = append(addressSlice, phoneNumber)
Expand All @@ -62,7 +63,7 @@ func flattenGroupAddresses(d *schema.ResourceData, addresses *[]platformclientv2
* This function establishes which field was set in the schema data (`extension` or `address`)
* and then sets that field in the map to the value that came back in `display`
*/
func setExtensionOrNumberBasedOnDisplay(d *schema.ResourceData, addressMap map[string]interface{}, address *platformclientv2.Groupcontact) {
func setExtensionOrNumberBasedOnDisplay(d *schema.ResourceData, addressMap map[string]interface{}, address *platformclientv2.Groupcontact, utilE164 *util.UtilE164Service) {
display := strings.Trim(*address.Display, "()")
schemaAddresses := d.Get("addresses").([]interface{})
for _, a := range schemaAddresses {
Expand All @@ -77,7 +78,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"], _ = util.FormatAsE164Number(display)
addressMap["number"] = utilE164.FormatAsCalculatedE164Number(display)
}
}
}
Expand Down
23 changes: 22 additions & 1 deletion genesyscloud/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2"
)

var orgDefaultCountryCode string

func init() {
// Set descriptions to support markdown syntax, this will be used in document generation
// and the language server.
Expand Down Expand Up @@ -171,6 +173,7 @@ type ProviderMeta struct {
Version string
ClientConfig *platformclientv2.Configuration
Domain string
Organization *platformclientv2.Organization
}

func configure(version string) schema.ConfigureContextFunc {
Expand All @@ -194,14 +197,32 @@ func configure(version string) schema.ConfigureContextFunc {
return nil, err
}
}

defaultConfig := platformclientv2.GetDefaultConfiguration()

currentOrg, err := getOrganizationMe(defaultConfig)
if err != nil {
return nil, err
}
orgDefaultCountryCode = *currentOrg.DefaultCountryCode
return &ProviderMeta{
Version: version,
ClientConfig: platformclientv2.GetDefaultConfiguration(),
ClientConfig: defaultConfig,
Domain: getRegionDomain(data.Get("aws_region").(string)),
Organization: currentOrg,
}, nil
}
}

func getOrganizationMe(defaultConfig *platformclientv2.Configuration) (*platformclientv2.Organization, diag.Diagnostics) {
orgApiClient := platformclientv2.NewOrganizationApiWithConfig(defaultConfig)
me, _, err := orgApiClient.GetOrganizationsMe()
if err != nil {
return nil, diag.FromErr(err)
}
return me, nil
}

func getRegionMap() map[string]string {
return map[string]string{
"dca": "inindca.com",
Expand Down
5 changes: 5 additions & 0 deletions genesyscloud/provider/provider_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package provider

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
Expand Down Expand Up @@ -42,3 +43,7 @@ func TestDefaultHomeDivision(resource string) resource.TestCheckFunc {
return nil
}
}

func GetOrgDefaultCountryCode() string {
return orgDefaultCountryCode
}
3 changes: 2 additions & 1 deletion genesyscloud/resource_genesyscloud_location.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@ func flattenLocationEmergencyNumber(numberConfig *platformclientv2.Locationemerg
}
numberSettings := make(map[string]interface{})
if numberConfig.Number != nil {
numberSettings["number"], _ = util.FormatAsE164Number(*numberConfig.Number)
utilE164 := util.NewUtilE164Service()
numberSettings["number"] = utilE164.FormatAsCalculatedE164Number(*numberConfig.Number)
}
if numberConfig.VarType != nil {
numberSettings["type"] = *numberConfig.VarType
Expand Down
10 changes: 6 additions & 4 deletions genesyscloud/resource_genesyscloud_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,8 @@ func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2.
emailSet := schema.NewSet(schema.HashResource(otherEmailResource), []interface{}{})
phoneNumSet := schema.NewSet(phoneNumberHash, []interface{}{})

utilE164 := util.NewUtilE164Service()

for i, address := range *addresses {
if address.MediaType != nil {
if *address.MediaType == "SMS" || *address.MediaType == "PHONE" {
Expand All @@ -967,7 +969,7 @@ func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2.

// 1.) Addresses that return an "address" field are phone numbers without extensions
if address.Address != nil {
phoneNumber["number"], _ = util.FormatAsE164Number(strings.Trim(*address.Address, "()"))
phoneNumber["number"] = utilE164.FormatAsCalculatedE164Number(strings.Trim(*address.Address, "()"))
}

// 2.) Addresses that return an "extension" field that matches the "display" field are
Expand All @@ -986,7 +988,7 @@ func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2.
if address.Display != nil {
if *address.Extension != *address.Display {
phoneNumber["extension"] = *address.Extension
phoneNumber["number"], _ = util.FormatAsE164Number(strings.Trim(*address.Display, "()"))
phoneNumber["number"] = utilE164.FormatAsCalculatedE164Number(strings.Trim(*address.Display, "()"))
}
}
}
Expand All @@ -1007,7 +1009,7 @@ func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2.
isNumber, isExtension := getNumbers(d, i)

if isNumber && phoneNumber["number"] != "" {
phoneNumber["number"] = strings.Trim(*address.Display, "()")
phoneNumber["number"] = utilE164.FormatAsCalculatedE164Number(strings.Trim(*address.Display, "()"))
}
if isExtension {
phoneNumber["extension"] = strings.Trim(*address.Display, "()")
Expand All @@ -1017,7 +1019,7 @@ func flattenUserAddresses(d *schema.ResourceData, addresses *[]platformclientv2.
if address.Extension == nil {
phoneNumber["extension"] = strings.Trim(*address.Display, "()")
} else if phoneNumber["number"] != "" {
phoneNumber["number"] = strings.Trim(*address.Display, "()")
phoneNumber["number"] = utilE164.FormatAsCalculatedE164Number(strings.Trim(*address.Display, "()"))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package telephony_providers_edges_did
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"

"github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2"
)
Expand Down Expand Up @@ -67,6 +68,7 @@ func (t *telephonyProvidersEdgesDidProxy) getTelephonyProvidersEdgesDidIdByDid(c

// getTelephonyProvidersEdgesDidIdByDidFn is an implementation function for getting a telephony DID ID by DID number.
func getTelephonyProvidersEdgesDidIdByDidFn(_ context.Context, t *telephonyProvidersEdgesDidProxy, did string) (string, bool, *platformclientv2.APIResponse, error) {
utilE164 := util.NewUtilE164Service()
const pageSize = 100

pageNum := 1
Expand All @@ -84,7 +86,7 @@ func getTelephonyProvidersEdgesDidIdByDidFn(_ context.Context, t *telephonyProvi
break
}
for _, entity := range *dids.Entities {
if *entity.PhoneNumber == did {
if utilE164.FormatAsCalculatedE164Number(*entity.PhoneNumber) == did {
return *entity.Id, false, resp, nil
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package telephony_providers_edges_did

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"
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_did"
Expand All @@ -17,7 +18,7 @@ func SetRegistrar(l registrar.Registrar) {
// DataSourceDid registers the genesyscloud_telephony_providers_edges_did data source
func DataSourceDid() *schema.Resource {
return &schema.Resource{
Description: "Data source for Genesys Cloud DID. The identifier is the E-164 phone number.",
Description: "Data source for Genesys Cloud DID. The identifier is the E.164 phone number.",
ReadContext: provider.ReadWithPooledClient(dataSourceDidRead),
Schema: map[string]*schema.Schema{
"phone_number": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package telephony_providers_edges_did_pool
import (
"context"
"fmt"
"terraform-provider-genesyscloud/genesyscloud/util"

"github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2"
)
Expand Down Expand Up @@ -178,13 +179,14 @@ func getAllTelephonyDidPoolsFn(_ context.Context, t *telephonyDidPoolProxy) (*[]

// getTelephonyDidPoolIdByStartAndEndNumberFn is an implementation function for finding a Genesys Cloud did pool using the start and end number
func getTelephonyDidPoolIdByStartAndEndNumberFn(ctx context.Context, t *telephonyDidPoolProxy, start, end string) (string, bool, *platformclientv2.APIResponse, error) {
utilE164 := util.NewUtilE164Service()
allDidPools, resp, err := getAllTelephonyDidPoolsFn(ctx, t)
if err != nil {
return "", false, resp, fmt.Errorf("failed to read did pools: %v", err)
}
for _, didPool := range *allDidPools {
if didPool.StartPhoneNumber != nil && *didPool.StartPhoneNumber == start &&
didPool.EndPhoneNumber != nil && *didPool.EndPhoneNumber == end &&
if didPool.StartPhoneNumber != nil && utilE164.FormatAsCalculatedE164Number(*didPool.StartPhoneNumber) == start &&
didPool.EndPhoneNumber != nil && utilE164.FormatAsCalculatedE164Number(*didPool.EndPhoneNumber) == end &&
didPool.State != nil && *didPool.State != "deleted" {
return *didPool.Id, false, resp, nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func readDidPool(ctx context.Context, d *schema.ResourceData, meta interface{})
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getTelephonyDidPoolProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceTelephonyDidPool(), constants.DefaultConsistencyChecks, resourceName)
utilE164 := util.NewUtilE164Service()

log.Printf("Reading DID pool %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
Expand All @@ -90,8 +91,8 @@ func readDidPool(ctx context.Context, d *schema.ResourceData, meta interface{})
return nil
}

_ = d.Set("start_phone_number", *didPool.StartPhoneNumber)
_ = d.Set("end_phone_number", *didPool.EndPhoneNumber)
_ = d.Set("start_phone_number", utilE164.FormatAsCalculatedE164Number(*didPool.StartPhoneNumber))
_ = d.Set("end_phone_number", utilE164.FormatAsCalculatedE164Number(*didPool.EndPhoneNumber))

resourcedata.SetNillableValue(d, "description", didPool.Description)
resourcedata.SetNillableValue(d, "comments", didPool.Comments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"terraform-provider-genesyscloud/genesyscloud/util/lists"
"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -321,7 +322,9 @@ func flattenLines(phoneLines *[]platformclientv2.Line) []interface{} {
}

if len(lineAddressList) > 0 {
resourcedata.SetMapValueIfNotNil(linePropertiesMap, "line_address", &lineAddressList)
utilE164 := util.NewUtilE164Service()
formattedLineAddresses := lists.Map(lineAddressList, utilE164.FormatAsCalculatedE164Number)
resourcedata.SetMapValueIfNotNil(linePropertiesMap, "line_address", &formattedLineAddresses)
}
if len(remoteAddressList) > 0 {
resourcedata.SetMapValueIfNotNil(linePropertiesMap, "remote_address", &remoteAddressList)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ 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
// 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)
Expand Down Expand Up @@ -149,6 +149,7 @@ func readSite(ctx context.Context, d *schema.ResourceData, meta interface{}) dia
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
sp := GetSiteProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceSite(), constants.DefaultConsistencyChecks, resourceName)
utilE164 := util.NewUtilE164Service()

log.Printf("Reading site %s", d.Id())
return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
Expand All @@ -172,7 +173,10 @@ func readSite(ctx context.Context, d *schema.ResourceData, meta interface{}) dia
resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "edge_auto_update_config", currentSite.EdgeAutoUpdateConfig, flattenSdkEdgeAutoUpdateConfig)
resourcedata.SetNillableValue(d, "media_regions", currentSite.MediaRegions)

_ = d.Set("caller_id", currentSite.CallerId)
d.Set("caller_id", nil)
if currentSite.CallerId != nil && *currentSite.CallerId != "" {
_ = d.Set("caller_id", utilE164.FormatAsCalculatedE164Number(*currentSite.CallerId))
}
_ = d.Set("caller_name", currentSite.CallerName)

if currentSite.PrimarySites != nil {
Expand Down
9 changes: 9 additions & 0 deletions genesyscloud/util/lists/util_lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,12 @@ func ConvertMapStringAnyToMapStringString(m map[string]any) map[string]string {
}
return sm
}

// Generic function to apply a function for each item over a list
func Map[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {
result[i] = fn(t)
}
return result
}
Loading

0 comments on commit 9ceb1ad

Please sign in to comment.