Skip to content

Commit

Permalink
Fixes DEVTOOLING-497 self-referential reply_email_address.route_id ge…
Browse files Browse the repository at this point in the history
…ts exported in the genesyscloud_routing_email_route resource. Also adds mutual exclusivity to reply_email_address block and the from_email and auto_bcc fields as per the API and UI behaviors (#929)
  • Loading branch information
bbbco authored Mar 25, 2024
1 parent e1d2ef5 commit fd5620c
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 116 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ website/node_modules
terraform-provider-genesyscloud
.env
.vscode

**/true
website/vendor
vendor/

Expand Down
8 changes: 4 additions & 4 deletions docs/resources/routing_email_route.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,18 @@ resource "genesyscloud_routing_email_route" "support-route" {
### Required

- `domain_id` (String) ID of the routing domain such as: 'example.com'. Changing the domain_id attribute will cause the email_route object to be dropped and recreated with a new ID.
- `from_email` (String) The sender email to use for outgoing replies.
- `from_name` (String) The sender name to use for outgoing replies.
- `pattern` (String) The search pattern that the mailbox name should match.

### Optional

- `auto_bcc` (Block Set) The recipients that should be automatically blind copied on outbound emails associated with this route. (see [below for nested schema](#nestedblock--auto_bcc))
- `auto_bcc` (Block Set) The recipients that should be automatically blind copied on outbound emails associated with this route. This should not be set if reply_email_address is specified. (see [below for nested schema](#nestedblock--auto_bcc))
- `flow_id` (String) The flow to use for processing the email. This should not be set if a queue_id is specified.
- `from_email` (String) The sender email to use for outgoing replies. This should not be set if reply_email_address is specified.
- `language_id` (String) The language to use for routing.
- `priority` (Number) The priority to use for routing.
- `queue_id` (String) The queue to route the emails to. This should not be set if a flow_id is specified.
- `reply_email_address` (Block List, Max: 1) The route to use for email replies. (see [below for nested schema](#nestedblock--reply_email_address))
- `reply_email_address` (Block List, Max: 1) The route to use for email replies. This should not be set if from_email or auto_bcc are specified. (see [below for nested schema](#nestedblock--reply_email_address))
- `skill_ids` (Set of String) The skills to use for routing.
- `spam_flow_id` (String) The flow to use for processing inbound emails that have been marked as spam.

Expand Down Expand Up @@ -89,6 +89,6 @@ Required:
Optional:

- `route_id` (String) ID of the route.
- `self_reference_route` (Boolean) Use this route as the reply email address. If true you will use the route id for this resource as the reply and you
- `self_reference_route` (Boolean) Use this route as the reply email address. If true you will use the route id for this resource as the reply and you
can not set a route. If you set this value to false (or leave the attribute off)you must set a route id. Defaults to `false`.

12 changes: 7 additions & 5 deletions genesyscloud/resource_exporter/resource_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package resource_exporter

import (
"context"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"regexp"
"strings"
"sync"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

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

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)

var resourceExporters map[string]*ResourceExporter
Expand Down Expand Up @@ -51,7 +53,7 @@ type ResourceInfo struct {

// Allows the definition of a custom resolver for an exporter.
type RefAttrCustomResolver struct {
ResolverFunc func(map[string]interface{}, map[string]*ResourceExporter) error
ResolverFunc func(map[string]interface{}, map[string]*ResourceExporter, string) error
}

// Allows the definition of a custom resolver for an exporter.
Expand Down
18 changes: 14 additions & 4 deletions genesyscloud/resource_exporter/resource_exporter_custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This causes problems with the exporter because our export process expects id to
This customer custom router will look at the member_group_type and resolve whether it is SKILLGROUP, GROUP type. It will then
find the appropriate resource out of the exporters and build a reference appropriately.
*/
func MemberGroupsResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter) error {
func MemberGroupsResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {

memberGroupType := configMap["member_group_type"]
memberGroupID := configMap["member_group_id"].(string)
Expand Down Expand Up @@ -51,7 +51,7 @@ by the export process. Example: properties = {"contact.Attempts" = ""}.
During the export process the value associated with the key is set to nil.
This custom exporter checks if a key has a value of nil and if it does sets it to an empty string so it is exported.
*/
func RuleSetPropertyResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter) error {
func RuleSetPropertyResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
if properties, ok := configMap["properties"].(map[string]interface{}); ok {
for key, value := range properties {
if value == nil {
Expand All @@ -70,7 +70,7 @@ and we have an array of attributes wrapped in a string.
This customer custom router will look at the skills array if present and resolve each string id find the appropriate resource out of the exporters and build a reference appropriately.
*/
func RuleSetSkillPropertyResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter) error {
func RuleSetSkillPropertyResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {

if exporter, ok := exporters["genesyscloud_routing_skill"]; ok {
skillIDs := configMap["skills"].(string)
Expand Down Expand Up @@ -117,10 +117,20 @@ func FileContentHashResolver(configMap map[string]interface{}, filepath string)
return nil
}

func CampaignStatusResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter) error {
func CampaignStatusResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
if configMap["campaign_status"] != "off" && configMap["campaign_status"] != "on" {
configMap["campaign_status"] = "off"
}

return nil
}

func ReplyEmailAddressSelfReferenceRouteExporterResolver(configMap map[string]interface{}, exporters map[string]*ResourceExporter, resourceName string) error {
routeId := configMap["route_id"].(string)
currentRouteReference := fmt.Sprintf("${genesyscloud_routing_email_route.%s.id}", resourceName)
if routeId == currentRouteReference {
configMap["self_reference_route"] = true
configMap["route_id"] = nil
}
return nil
}
14 changes: 7 additions & 7 deletions genesyscloud/resource_exporter/resource_exporter_custom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ This is a unit test because it is just testing this single function without any
func TestAccExporterCustomMemberGroup(t *testing.T) {
teamID := uuid.NewString()
testResults := []*customMemberGroupTest{
&customMemberGroupTest{MemberGroupID: uuid.NewString(), MemberGroupType: "SKILLGROUP", GroupName: "test_skill_group_name", ExporterResourceType: "genesyscloud_routing_skill_group", ExpectedResult: "${genesyscloud_routing_skill_group.test_skill_group_name.id}"},
&customMemberGroupTest{MemberGroupID: uuid.NewString(), MemberGroupType: "GROUP", GroupName: "test_group_name", ExporterResourceType: "genesyscloud_group", ExpectedResult: "${genesyscloud_group.test_group_name.id}"},
&customMemberGroupTest{MemberGroupID: teamID, MemberGroupType: "TEAM", GroupName: "test_team_name", ExporterResourceType: "genesyscloud_team_NA", ExpectedResult: teamID},
{MemberGroupID: uuid.NewString(), MemberGroupType: "SKILLGROUP", GroupName: "test_skill_group_name", ExporterResourceType: "genesyscloud_routing_skill_group", ExpectedResult: "${genesyscloud_routing_skill_group.test_skill_group_name.id}"},
{MemberGroupID: uuid.NewString(), MemberGroupType: "GROUP", GroupName: "test_group_name", ExporterResourceType: "genesyscloud_group", ExpectedResult: "${genesyscloud_group.test_group_name.id}"},
{MemberGroupID: teamID, MemberGroupType: "TEAM", GroupName: "test_team_name", ExporterResourceType: "genesyscloud_team_NA", ExpectedResult: teamID},
}

for _, testResult := range testResults {
Expand All @@ -60,15 +60,15 @@ func TestAccExporterCustomMemberGroup(t *testing.T) {
}

//Invoke the resolver
err := MemberGroupsResolver(configMap, exporters)
err := MemberGroupsResolver(configMap, exporters, testResult.ExporterResourceType)

if err != nil && testResult.MemberGroupType != "TEAM" {
t.Errorf("Received an unexpected error while calling MemberGroupResolver: %v", err)
}

//The member_group_id should now be replaced by the expected out put with th
if configMap["member_group_id"].(string) != testResult.ExpectedResult {
t.Errorf("The member_group_id set in the config map was %v,but wanted %v", configMap["member_group_id"], testResult.ExpectedResult)
t.Errorf("The member_group_id set in the config map was %v, but wanted %v", configMap["member_group_id"], testResult.ExpectedResult)
}
}

Expand All @@ -85,7 +85,7 @@ func TestRuleSetPropertyGroup(t *testing.T) {
jsonString := string(jsonData)

testResults := []*propertyGroupTest{
&propertyGroupTest{Skills: jsonString, SkillName: "test_skill_name", ExporterResourceType: "genesyscloud_routing_skill", ExpectedResult: "[\"${genesyscloud_routing_skill.test_skill_name.id}\"]"},
{Skills: jsonString, SkillName: "test_skill_name", ExporterResourceType: "genesyscloud_routing_skill", ExpectedResult: "[\"${genesyscloud_routing_skill.test_skill_name.id}\"]"},
}

for _, testResult := range testResults {
Expand All @@ -110,7 +110,7 @@ func TestRuleSetPropertyGroup(t *testing.T) {
}

//Invoke the resolver
err := RuleSetSkillPropertyResolver(configMap, exporters)
err := RuleSetSkillPropertyResolver(configMap, exporters, testResult.ExporterResourceType)

if err != nil {
t.Errorf("Received an unexpected error while calling RuleSetSkillPropertyResolver: %v", err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package routing_email_route

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"sync"
"terraform-provider-genesyscloud/genesyscloud"
"terraform-provider-genesyscloud/genesyscloud/architect_flow"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

/*
Expand All @@ -30,6 +32,7 @@ func (r *registerTestInstance) registerTestResources() {
providerResources["genesyscloud_routing_queue"] = genesyscloud.ResourceRoutingQueue()
providerResources["genesyscloud_routing_language"] = genesyscloud.ResourceRoutingLanguage()
providerResources["genesyscloud_routing_skill"] = genesyscloud.ResourceRoutingSkill()
providerResources["genesyscloud_flow"] = architect_flow.ResourceArchitectFlow()
}

// initTestResources initializes all test resources and data sources.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package routing_email_route
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
"terraform-provider-genesyscloud/genesyscloud/util"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v125/platformclientv2"

"terraform-provider-genesyscloud/genesyscloud/consistency_checker"

"terraform-provider-genesyscloud/genesyscloud/util/resourcedata"
Expand Down Expand Up @@ -53,14 +54,16 @@ func createRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta i

routingEmailRoute := getRoutingEmailRouteFromResourceData(d)

replyEmail, err := validateSdkReplyEmailAddress(d)
// Checking the self_reference_route flag and routeId rules
if err := validateSdkReplyEmailAddress(d); err != nil {
if err != nil {
return diag.Errorf("Error occurred while validating the reply email address when creating the record: %s", err)
}

replyDomainID, replyRouteID, _ := extractReplyEmailAddressValue(d)

// If the isSelfReferenceRoute() is set to false, we use the route id provided by the terraform script
if !isSelfReferenceRouteSet(d) {
if replyEmail && !isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, replyRouteID)
}

Expand All @@ -74,7 +77,7 @@ func createRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta i
log.Printf("Created routing email route %s", *inboundRoute.Id)

// If the isSelfReferenceRoute() is set to true we need grab the route id for the route and reapply the reply address,
if isSelfReferenceRouteSet(d) {
if replyEmail && isSelfReferenceRouteSet(d) {
inboundRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, *inboundRoute.Id)
_, resp, err = proxy.updateRoutingEmailRoute(ctx, *inboundRoute.Id, domainId, inboundRoute)

Expand Down Expand Up @@ -102,9 +105,9 @@ func readRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta int
if getErr != nil {
if util.IsStatus404(respCode) {
d.SetId("")
return retry.RetryableError(fmt.Errorf("Failed to read routing email route %s: %s", d.Id(), getErr))
return retry.RetryableError(fmt.Errorf("failed to read routing email route %s: %s", d.Id(), getErr))
}
return retry.NonRetryableError(fmt.Errorf("Failed to read routing email route %s: %s", d.Id(), getErr))
return retry.NonRetryableError(fmt.Errorf("failed to read routing email route %s: %s", d.Id(), getErr))
}

for _, inboundRoutes := range *inboundRoutesMap {
Expand Down Expand Up @@ -169,15 +172,19 @@ func updateRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta i
routingEmailRoute := getRoutingEmailRouteFromResourceData(d)

//Checking the self_reference_route flag and routeId rules
if err := validateSdkReplyEmailAddress(d); err != nil {
replyEmail, err := validateSdkReplyEmailAddress(d)
if err != nil {
return diag.Errorf("Error occurred while validating the reply email address while trying to update the record: %s", err)
}

replyDomainID, replyRouteID, _ := extractReplyEmailAddressValue(d)

if isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, d.Id())
} else if !isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, replyRouteID)
if replyEmail {
if isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, d.Id())
} else if !isSelfReferenceRouteSet(d) {
routingEmailRoute.ReplyEmailAddress = buildReplyEmailAddress(replyDomainID, replyRouteID)
}
}

log.Printf("Updating routing email route %s", d.Id())
Expand Down Expand Up @@ -208,7 +215,7 @@ func deleteRoutingEmailRoute(ctx context.Context, d *schema.ResourceData, meta i
log.Printf("Deleted routing email route %s", d.Id())
return nil
}
return retry.NonRetryableError(fmt.Errorf("Error deleting routing email route %s: %s", d.Id(), err))
return retry.NonRetryableError(fmt.Errorf("error deleting routing email route %s: %s", d.Id(), err))
}
return retry.RetryableError(fmt.Errorf("routing email route %s still exists", d.Id()))
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package routing_email_route

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"terraform-provider-genesyscloud/genesyscloud/provider"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

resourceExporter "terraform-provider-genesyscloud/genesyscloud/resource_exporter"
registrar "terraform-provider-genesyscloud/genesyscloud/resource_register"
)
Expand Down Expand Up @@ -71,14 +72,16 @@ func ResourceRoutingEmailRoute() *schema.Resource {
Required: true,
},
"from_email": {
Description: "The sender email to use for outgoing replies.",
Type: schema.TypeString,
Required: true,
Description: "The sender email to use for outgoing replies. This should not be set if reply_email_address is specified.",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"reply_email_address"},
},
"queue_id": {
Description: "The queue to route the emails to. This should not be set if a flow_id is specified.",
Type: schema.TypeString,
Optional: true,
Description: "The queue to route the emails to. This should not be set if a flow_id is specified.",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"flow_id"},
},
"priority": {
Description: "The priority to use for routing.",
Expand All @@ -97,15 +100,17 @@ func ResourceRoutingEmailRoute() *schema.Resource {
Optional: true,
},
"flow_id": {
Description: "The flow to use for processing the email. This should not be set if a queue_id is specified.",
Type: schema.TypeString,
Optional: true,
Description: "The flow to use for processing the email. This should not be set if a queue_id is specified.",
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"queue_id"},
},
"reply_email_address": {
Description: "The route to use for email replies.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Description: "The route to use for email replies. This should not be set if from_email or auto_bcc are specified.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ConflictsWith: []string{"from_email"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"domain_id": {
Expand All @@ -120,7 +125,7 @@ func ResourceRoutingEmailRoute() *schema.Resource {
Optional: true,
},
"self_reference_route": {
Description: `Use this route as the reply email address. If true you will use the route id for this resource as the reply and you
Description: `Use this route as the reply email address. If true you will use the route id for this resource as the reply and you
can not set a route. If you set this value to false (or leave the attribute off)you must set a route id.`,
Type: schema.TypeBool,
Required: false,
Expand All @@ -131,10 +136,11 @@ func ResourceRoutingEmailRoute() *schema.Resource {
},
},
"auto_bcc": {
Description: "The recipients that should be automatically blind copied on outbound emails associated with this route.",
Type: schema.TypeSet,
Optional: true,
Elem: bccEmailResource,
Description: "The recipients that should be automatically blind copied on outbound emails associated with this route. This should not be set if reply_email_address is specified.",
Type: schema.TypeSet,
Optional: true,
Elem: bccEmailResource,
ConflictsWith: []string{"reply_email_address"},
},
"spam_flow_id": {
Description: "The flow to use for processing inbound emails that have been marked as spam.",
Expand All @@ -160,8 +166,10 @@ func RoutingEmailRouteExporter() *resourceExporter.ResourceExporter {
"reply_email_address.route_id": {RefType: "genesyscloud_routing_email_route"},
},
RemoveIfMissing: map[string][]string{
"reply_email_address": {"route_id"},
"reply_email_address": {"route_id", "self_reference_route"},
},
CustomAttributeResolver: map[string]*resourceExporter.RefAttrCustomResolver{
"reply_email_address.self_reference_route": {ResolverFunc: resourceExporter.ReplyEmailAddressSelfReferenceRouteExporterResolver},
},
AllowZeroValues: []string{"from_email"},
}
}
Loading

0 comments on commit fd5620c

Please sign in to comment.