CX as Code ---- genesyscloud_routing_queue

Hello,

We have an issue on managing terraform states on Genesys CX. We are currently trying to manage queue's into terraform but having issues with queue's that has diff objects on it.
For example, outbound call queues has phone numbers attached to it while, other queue's don't have like email. While email queue's have outbound email address while call queue's don't have it.

The file we have extracted from Terraform(using terraform export) did not provide any null data for those objects, it just skip those if those were not set-up. My questions would be, how do you guys manage those queue's that has diff objects inside it ? Is it even possible?
How can we omit attributes which did not exist on the code?
I also have provide my code as reference.

variable.tf

variable "bullseye_rings" {
  type = list(object({
    timeout_seconds = optional(number)
    skills = optional(list(string))
  }))
  default = []
}
variable "media_settings_email" {
  type = object({
    alerting_timeout_sec = optional(number)
    service_level_duration_ms = optional(number)
    service_level_percentage = optional(number)
  })
}
variable "media_settings_call" {
  type = object({
    alerting_timeout_sec = optional(number)
    service_level_duration_ms = optional(number)
    service_level_percentage = optional(number)
  })

}
variable "routing_rules" {
  type = object({
     operator = optional(string)
     wait_seconds = optional(number)
  }
  )
}
variable "media_settings_chat" {
  type = object({
    alerting_timeout_sec = optional(number)
    service_level_duration_ms = optional(number)
    service_level_percentage = optional(number)
  })
}
variable "media_settings_callback" {
  type = object({
    alerting_timeout_sec = optional(number)
    service_level_duration_ms = optional(number)
    service_level_percentage = optional(number)
  })
}
variable "media_settings_message" {
  type = object({
    alerting_timeout_sec = optional(number)
    service_level_duration_ms = optional(number)
    service_level_percentage = optional(number)
  })
}
variable "outbound_email_address" {
  type = object({
    domain_id = optional(string)
    route_id = optional(string)
  })
}
variable "default_script_ids" {
 type = object({
   CALL = optional(string)
   EMAIL = optional(string)
 })
}
variable "queue_name"{
  type = string
}
variable "wrapup_codes"{
  type = list(string)
}
variable "calling_party_name" {
  type = string
}
variable "calling_party_number" {
  type = string
}
variable "division_id" {
  type = string
}
variable "enable_transcription" {
  type = bool
}
variable "skill_evaluation_method" {
  type = string
}
variable "acw_wrapup_prompt" {
  type = string
}
variable "auto_answer_only" {
  type = bool
}
variable "acw_timeout_ms" {
  type = number
}
variable "enable_manual_assignment" {
  type = bool
}

main.tf

terraform {
  experiments = [module_variable_optional_attrs]
  required_providers {
    genesyscloud = {
      source  = "mypurecloud/genesyscloud"
    }
  }
}
resource "genesyscloud_routing_queue" "this" {
  name                     = var.queue_name
  wrapup_codes             = try(var.wrapup_codes, null)
  calling_party_name       = try(var.calling_party_name, null)
  calling_party_number     = try(var.calling_party_number, null)
  division_id              = try(var.division_id, null)
  enable_transcription     = try(var.enable_transcription, null)
  skill_evaluation_method  = try(var.skill_evaluation_method, null)
  acw_wrapup_prompt        = try(var.acw_wrapup_prompt, null)
  auto_answer_only         = try(var.auto_answer_only, null)
  acw_timeout_ms           = try(var.acw_timeout_ms, null)
  enable_manual_assignment = try(var.enable_manual_assignment, null)

  media_settings_email {
    alerting_timeout_sec      = try(var.media_settings_email.alerting_timeout_sec, null)
    service_level_duration_ms = try(var.media_settings_email.service_level_duration_ms, null)
    service_level_percentage  = try(var.media_settings_email.service_level_percentage, null)
  }

  media_settings_call {
    alerting_timeout_sec      = try(var.media_settings_call.alerting_timeout_sec, null)
    service_level_duration_ms = try(var.media_settings_call.service_level_duration_ms, null)
    service_level_percentage  = try(var.media_settings_call.service_level_percentage, null)
  }

  media_settings_chat {
    alerting_timeout_sec      = try(var.media_settings_chat.alerting_timeout_sec, null)
    service_level_duration_ms = try(var.media_settings_chat.service_level_duration_ms, null)
    service_level_percentage  = try(var.media_settings_chat.service_level_percentage, null)
  }

  media_settings_callback {
    alerting_timeout_sec      = try(var.media_settings_callback.alerting_timeout_sec, null)
    service_level_duration_ms = try(var.media_settings_callback.service_level_duration_ms, null)
    service_level_percentage  = try(var.media_settings_callback.service_level_percentage, null)
  }

  media_settings_message {
    alerting_timeout_sec      = try(var.media_settings_message.alerting_timeout_sec, null)
    service_level_duration_ms = try(var.media_settings_message.service_level_duration_ms, null)
    service_level_percentage  = try(var.media_settings_message.service_level_percentage, null)
  }

  default_script_ids = {
    CALL  = try(var.default_script_ids.CALL, null)
    EMAIL = try(var.default_script_ids.EMAIL, null)
  }

dynamic "outbound_email_address" {
  for_each = [for g in var.outbound_email_address:{
    domain_id = g.domain_id
    route_id = g.route_id
  }]
  content {
    domain_id = can(outbound_email_address.value.domain_id) ? outbound_email_address.value.domain_id : null
    route_id  = can(outbound_email_address.value.route_id) ? outbound_email_address.value.route_id : null
  }
}
dynamic "bullseye_rings" {
  for_each = [for g in var.bullseye_rings:{
    expansion_timeout_seconds = g.expansion_timeout_seconds
    skills_to_remove = g.skills_to_remove
  }]
  content {
    expansion_timeout_seconds = can(bullseye_rings.value.expansion_timeout_seconds) ? bullseye_rings.value.expansion_timeout_seconds : null
    skills_to_remove          = can(bullseye_rings.value.skills_to_remove) ? bullseye_rings.value.skills_to_remove : null
  }
}

}

Sample Data

module "queue"{
     source = "./modules/queue"
  for_each = {
    SAMPLE_Chat_1st = {
      name                 = "SAMPLE_DE_Chat_1st"
      division_id          = genesyscloud_auth_division.this["SAMPLE_"].id
      enable_transcription = false
      media_settings_email = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 31231
        service_level_percentage  = 0.3
      }
      skill_evaluation_method = "BEST"
      acw_wrapup_prompt       = "MANDATORY_TIMEOUT"
      media_settings_call = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      wrapup_codes = [genesyscloud_routing_wrapupcode.this["SAMPLE"].id]
      routing_rules = {
        operator     = "ANY"
        wait_seconds = 2
      }
      auto_answer_only = false
      media_settings_chat = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      media_settings_callback = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      media_settings_message = {
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
      }
      acw_timeout_ms = 90000
      bullseye_rings = [{
        expansion_timeout_seconds = 2
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_TG1_Chat_1st"].id, genesyscloud_routing_skill.this["SAMPLE_TG2_Chat_1st"].id]
      },
      {
        expansion_timeout_seconds = 120
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_1st_Nightshift"].id]
      },
      {
        expansion_timeout_seconds = 780
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Chat_1st"].id]
      },
      {
        expansion_timeout_seconds = 5
      }]
      calling_party_number = "+3"
      calling_party_name   = "SAMPLE"
      default_script_ids = {
        CALL = "SAMPLE"
      }
      enable_manual_assignment = false
    },

    SAMPLE_Voice_1st_callback = {
      media_settings_chat = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      skill_evaluation_method = "BEST"
      wrapup_codes            = [genesyscloud_routing_wrapupcode.this["SAMPLE"].id]
      calling_party_number    = "+1"
      division_id             = genesyscloud_auth_division.this["SAMPLE_US_Master"].id
      media_settings_call = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      calling_party_name = "SAMPLE"
      default_script_ids = {
        CALL = "SAMPLE"
      }
      name             = "SAMPLE_Voice_1st_callback"
      auto_answer_only = false
      bullseye_rings = [{
        expansion_timeout_seconds = 600
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_USVoice_1st"].id]
      },
      {
        expansion_timeout_seconds = 900
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_US_ServerVoice_1st"].id]
      },
      {
        expansion_timeout_seconds = 5
      }]
      media_settings_callback = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      media_settings_message = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      enable_transcription = false
      media_settings_email = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 21600000
        service_level_percentage  = 1
      }
      routing_rules = {
        operator     = "ANY"
        wait_seconds = 900
      }
      acw_wrapup_prompt        = "MANDATORY_TIMEOUT"
      enable_manual_assignment = false
      acw_timeout_ms           = 1231
    },

    SAMPLE_Chat_2nd = {
      media_settings_email = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 21600000
        service_level_percentage  = 1
      }
      acw_wrapup_prompt = "MANDATORY_TIMEOUT"
      media_settings_callback = {
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
      }
      skill_evaluation_method  = "BEST"
      enable_manual_assignment = false
      media_settings_call = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      routing_rules = {
        operator     = "ANY"
        wait_seconds = 2
      }
      calling_party_name = "SAMPLE"
      default_script_ids = {
        CALL = "SAMPLE"
      }
      acw_timeout_ms = 1231
      media_settings_chat = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      media_settings_message = {
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
      }
      auto_answer_only = false
      bullseye_rings = [{
        expansion_timeout_seconds = 2
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Chat_1st1"].id]
      },
      {
        expansion_timeout_seconds = 900
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE__Chat_1st"].id]
      },
      {
        expansion_timeout_seconds = 5
      }]
      division_id          = genesyscloud_auth_division.this["SAMPLE_"].id
      name                 = "SAMPLE_Chat_1st"
      calling_party_number = "+0"
      enable_transcription = false
      wrapup_codes         = [genesyscloud_routing_wrapupcode.this["SAMPLE"].id]
    },

    SAMPLE_MX_Voice_1st = {
      auto_answer_only   = false
      calling_party_name = "SAMPLE"
      media_settings_callback = {
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
      }
      default_script_ids = {
        CALL = "SAMPLE"
      }
      name = "SAMPLE_MX_SMB_PSA_Partner_Voice_1st"
      bullseye_rings = [{
        expansion_timeout_seconds = 2
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Voice_1st"].id]
      },
      {
        expansion_timeout_seconds = 120
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Nightshift"].id]
      },
      {
        expansion_timeout_seconds = 780
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Voice_2st"].id]
      },
      {
        expansion_timeout_seconds = 5
      }]
      media_settings_message = {
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
      }
      skill_evaluation_method = "BEST"
      acw_wrapup_prompt       = "MANDATORY_TIMEOUT"
      media_settings_email = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 31231
        service_level_percentage  = 0.3
      }
      enable_manual_assignment = false
      acw_timeout_ms           = 1231
      division_id              = genesyscloud_auth_division.this["SAMPLE_"].id
      media_settings_chat = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      media_settings_call = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      wrapup_codes         = [genesyscloud_routing_wrapupcode.this["SAMPLE"].id]
      calling_party_number = "+8394"
      enable_transcription = false
      routing_rules = {
        operator     = "ANY"
        wait_seconds = 2
      }
    },

    SAMPLE__Chat_3rd = {
      media_settings_email = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 31231
        service_level_percentage  = 0.3
      }
      division_id          = genesyscloud_auth_division.this["SAMPLE"].id
      enable_transcription = false
      media_settings_call = {
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
      }
      acw_timeout_ms       = 1231
      acw_wrapup_prompt    = "MANDATORY_TIMEOUT"
      calling_party_number = "+94"
      media_settings_chat = {
        service_level_percentage  = 0.3
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
      }
      wrapup_codes = [genesyscloud_routing_wrapupcode.this["Sample"].id]
      default_script_ids = {
        CALL = "SAMPLE"
      }
      enable_manual_assignment = false
      name                     = "SAMPLE__Chat_3rd"
      skill_evaluation_method  = "BEST"
      auto_answer_only         = false
      media_settings_callback = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
      bullseye_rings = [
      {
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Chat_1st"].id]
        expansion_timeout_seconds = 30
      },
      {
        expansion_timeout_seconds = 120
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_Chat_1st_Nightshift"].id]
      },
      {
        expansion_timeout_seconds = 780
        skills_to_remove          = [genesyscloud_routing_skill.this["SAMPLE_General_Chat_1st_OF"].id]
      },
      {
        expansion_timeout_seconds = 5
      }
      ]
      calling_party_name = "SAMPLE"
      media_settings_message = {
        alerting_timeout_sec      = 12
        service_level_duration_ms = 1231
        service_level_percentage  = 0.3
      }
    },

  }

  queue_name               = each.value.name
  wrapup_codes             = try(each.value.wrapup_codes,null)
  bullseye_rings           = try(each.value.bullseye_rings, null)
  calling_party_name       = try(each.value.calling_party_name,null)
  calling_party_number     = terraform.workspace == "production" ? try(each.value.calling_party_number,null): "+995"
  division_id              = each.value.division_id
  enable_transcription     = each.value.enable_transcription
  media_settings_email     = try(each.value.media_settings_email,null)
  skill_evaluation_method  = try(each.value.skill_evaluation_method,null)
  acw_wrapup_prompt        = try(each.value.acw_wrapup_prompt,null)
  media_settings_call      = try(each.value.media_settings_call,{})
  routing_rules            = try(each.value.routing_rules,{})
  auto_answer_only         = try(each.value.auto_answer_only,null)
  media_settings_chat      = try(each.value.media_settings_chat,{})
  media_settings_callback  = try(each.value.media_settings_callback,{})
  media_settings_message   = try(each.value.media_settings_message,{})
  acw_timeout_ms           = try(each.value.acw_timeout_ms,null)
  enable_manual_assignment = try(each.value.enable_manual_assignment,null)
  outbound_email_address   = terraform.workspace == "production" ? try(each.value.outbound_email_address,{}) : {}
  default_script_ids       = terraform.workspace == "production" ? try(each.value.default_script_ids,{}) : {}

  depends_on = [
    genesyscloud_routing_wrapupcode.this,
    genesyscloud_auth_division.this,
    genesyscloud_routing_skill.this,
  ]
}

Sample Error Message

│ Error: Unsupported attribute

│ on modules/queue/main.tf line 71, in resource "genesyscloud_routing_queue" "this":
│ 71: skills_to_remove = g.skills_to_remove

│ This object does not have an attribute named "skills_to_remove".

Hi Basilio,

I am not sure if you are asking for my help with the error message or just general approach or both. I can tell you that you are getting the error message because you have defined the variable bullseye ring as an object called skills. You are trying to reference an attribute from that object called g.skills_to_remove.

As for the empty objects on a queue resource. If the object attribute is not present, we remove it from the exported object. The export is supposed to represent the state of the object and we want to put the exported HCL as ready as we can to be applied not only against the org that the export ran against but also potentially being applied against another org. Adding unused attributes would clutter up the file and would have been ignored.

I hope that makes sense. BTW: Pretty cool stuff you are doing in your Terraform file. I have never used complex objects or the try and can functions so it forced me to learn something new.

Please let me know if you need further clarification.

Thanks,
John Carnell
Director, Developer Engagement

Hello John,

As of the moment, we wanted to have 1 file for both, for our dev-org and prod-org. The goal is to have 1 synced environment for dev and prod which is managed by terraform. In order to attain it, we changed the exported HCL to be more flexible.

*Changing ID's to names
*Adding conditional argument on the file
(referring to sample code
codedefault_script_ids = terraform.workspace == "production" ? each.value.default_script_ids : "ScriptName")

And now, this is where the problem occurs.

Since, we are now using for_each loop to loop into all the queue's, we declared every object that exist on queue's
(referring to sample data)
queue_name = each.value.name
wrapup_codes = try(each.value.wrapup_codes,null)
bullseye_rings = try(each.value.bullseye_rings, null)
calling_party_name = try(each.value.calling_party_name,null)
.........
default_script_ids = terraform.workspace == "production" ? try(each.value.default_script_ids,{}) : {}

Now, some queue's does not have calling_party_name and other attributes. And since we have declared it, Terraform will throw an error and try to find those non-existing objects. This also applies to sub-attributes like skill_to_remove on bullseye_rings.

I'm currently looking for a function/method I can use to omit those non-existing attribute from the data blocks / sub-attributes.

I'm not sure if this is possible if not I guess, we can just take another approach.

We have taken another approach. Separating calls/emaills/chat queues. But now we have a new issue on using modules.

When we run the terraform apply, it will delete the existing queue's and replace it with the one on module.

Destroying
genesyscloud_routing_queue.this["IONOS_DE_Mywebsite_PSA_Chat_1st"] will be destroyed
(because genesyscloud_routing_queue.this is not in configuration)
resource "genesyscloud_routing_queue" "this" {

Adding

module.queue["IONOS_DE_Mywebsite_PSA_Chat_1st"].genesyscloud_routing_queue.this will be created
resource "genesyscloud_routing_queue" "this" {

Not sure if this is the correct behavior

Found the issue. Was using common terraform import instead of importing and specifying the modules

Hi Basilio,

Glad you found the issue. I looked through the routing_queue code and none of the attributes have a force_new on it.

Thanks,
John Carnell
Developer Engagement

Thanks your checking John, I really appreciate it. After we resolve the issue. We encountered new weird behavior.
When we try to hit terraform apply. The system will try to add additional attribute to routing_rules for some reason. We have traced the value it's trying to add and it seems it's the default value set.
https://registry.terraform.io/providers/MyPureCloud/genesyscloud/latest/docs/resources/routing_queue#nestedblock--routing_rules

This is how we passed the data

resourcefile.tf

routing_rules = try(each.value.routing_rules,null) #will check if has value, if not pass null value

variable.tf

#data structure for routing_rules
variable "routing_rules" {
type = object({
operator = optional(string)
threshold = optional(number)
wait_seconds = optional(number)
})
}

main.tf

routing_rules {
threshold = try(var.routing_rules.threshold,null)
operator = try(var.routing_rules.operator,null)
wait_seconds = try(var.routing_rules.wait_seconds,null)
}

However, when we run terraform apply it will add

 + routing_rules {
       + operator     = "MEETS_THRESHOLD"
       + wait_seconds = 5

What we are thinking is , It's probably because of this

https://registry.terraform.io/providers/MyPureCloud/genesyscloud/latest/docs/resources/routing_queue#nestedblock--routing_rules

This topic was automatically closed 31 days after the last reply. New replies are no longer allowed.