Automate Genesys Cloud Queue Provisioning with Terraform and YAML

Automate Genesys Cloud Queue Provisioning with Terraform and YAML

What You Will Build

  • A Terraform script that parses a YAML file containing queue definitions and uses for_each to provision multiple Genesys Cloud queues simultaneously.
  • This tutorial uses the Genesys Cloud Terraform Provider and standard Terraform functions.
  • The implementation is written in HashiCorp Configuration Language (HCL).

Prerequisites

  • Genesys Cloud OAuth Client: You need a Genesys Cloud OAuth client with the admin grant type. The client requires the queue:write scope to create queues and routing:queue:view to read existing configurations if you plan to reference them later.
  • Terraform: Version 1.0 or higher installed on your machine.
  • Genesys Cloud Terraform Provider: Version 1.0.0 or higher.
  • YAML File: A local queues.yaml file containing your queue data.
  • Environment Variables: You must set GENESYS_CLOUD_REGION (e.g., my.genesys.cloud) and GENESYS_CLOUD_CLIENT_SECRET / GENESYS_CLOUD_CLIENT_ID in your shell or .env file.

Authentication Setup

Terraform handles OAuth token acquisition automatically when you configure the provider block. You do not need to write manual OAuth code. However, you must ensure the provider is initialized with the correct credentials.

Create a file named main.tf. This file initializes the provider. The provider uses the environment variables to fetch an access token from the Genesys Cloud OAuth endpoint (/oauth/token).

terraform {
  required_providers {
    genesyscloud = {
      source  = "genesyscloud/genesyscloud"
      version = ">= 1.0.0"
    }
  }
}

provider "genesyscloud" {
  # These are automatically picked up from environment variables:
  # GENESYS_CLOUD_CLIENT_ID
  # GENESYS_CLOUD_CLIENT_SECRET
  # GENESYS_CLOUD_REGION
}

If you run terraform init, the provider will download the necessary binaries. When you run terraform plan, the provider will execute a POST request to the OAuth endpoint to retrieve a bearer token. If the credentials are invalid, you will receive a 401 Unauthorized error. If the client lacks the queue:write scope, you will receive a 403 Forbidden error when attempting to create resources.

Implementation

Step 1: Define the YAML Data Structure

Create a file named queues.yaml. This file serves as the single source of truth for your queue configurations. We will structure it as a list of objects. Each object represents a queue.

Key fields in Genesys Cloud queues include:

  • name: The display name of the queue.
  • description: A text description.
  • outbound_email: The email address used for outbound notifications (optional but common).
  • wrap_up_policy: Controls how wrap-up time is applied (FORCE, IGNORE, IGNORE_IF_EMPTY, USE_AGENT_SETTINGS).
  • skills: A list of skill names required to serve this queue.
# queues.yaml
- name: "Sales Support"
  description: "General sales inquiries and new customer onboarding"
  wrap_up_policy: "FORCE"
  skills:
    - "sales"
    - "english"
  outbound_email: "sales-support@example.com"

- name: "Technical Support"
  description: "Tier 1 technical troubleshooting"
  wrap_up_policy: "IGNORE_IF_EMPTY"
  skills:
    - "technical"
    - "english"
  outbound_email: "tech-support@example.com"

- name: "Billing Inquiries"
  description: "Payment and invoice questions"
  wrap_up_policy: "USE_AGENT_SETTINGS"
  skills:
    - "billing"
    - "english"
  outbound_email: "billing@example.com"

Step 2: Parse YAML and Prepare for Iteration

Terraform does not natively parse YAML files in a way that is directly usable for for_each without some transformation. We use the yamldecode function to convert the YAML content into a map of objects.

In main.tf, add a locals block to read the file and decode it.

locals {
  # Read the raw content of the YAML file
  queues_raw = file("${path.module}/queues.yaml")
  
  # Decode the YAML into a Terraform list of maps
  queues_list = yamldecode(local.queues_raw)
  
  # Convert the list into a map for for_each usage.
  # We use the queue name as the key. This ensures idempotency.
  # If a queue name changes in the YAML, Terraform will detect a replacement.
  queues_map = { for q in local.queues_list : q.name => q }
}

Why convert to a map?
The for_each meta-argument requires a map or a set of strings. Using the queue name as the key is critical because Genesys Cloud queue names must be unique within a routing language context. If you used an index (e.g., from a list), renaming a queue would cause Terraform to destroy the old queue and create a new one, potentially losing historical statistics. Using the name as the key allows Terraform to update the existing queue if other attributes change.

Step 3: Create Queues Using for_each

Now we define the genesyscloud_routing_queue resource. We use the for_each argument to iterate over local.queues_map.

resource "genesyscloud_routing_queue" "dynamic_queues" {
  for_each = local.queues_map

  name        = each.value.name
  description = each.value.description
  enable_email = false # Set to true if you enable email capability on the queue
  
  # Wrap-up policy must be one of: FORCE, IGNORE, IGNORE_IF_EMPTY, USE_AGENT_SETTINGS
  wrap_up_policy = each.value.wrap_up_policy

  # Outbound email configuration
  outbound_email = each.value.outbound_email

  # Skills configuration
  # Note: Skills must exist in Genesys Cloud before they can be assigned to a queue.
  # This tutorial assumes the skills listed in YAML already exist.
  skills = each.value.skills

  # Optional: Set the queue language. Default is usually "en-US".
  # If your queues support multiple languages, you may need a more complex structure.
  language_id = var.default_language_id
}

Important Note on Skills:
The skills parameter in the Terraform provider expects a list of skill names. The provider will automatically resolve these names to skill IDs. However, if a skill name in your YAML does not exist in your Genesys Cloud org, the terraform apply will fail with a 404 or validation error. Ensure your skills are provisioned via other Terraform resources or manually before running this script.

Step 4: Handling Dependencies and Language IDs

Genesys Cloud queues are associated with a routing language. You must provide a language_id. For simplicity, we assume a single default language (e.g., English US). In a production environment, you might fetch the language ID dynamically.

Add a variable for the language ID and a data source to look it up by name, ensuring the code is portable across organizations.

variable "default_language_name" {
  description = "The name of the default routing language (e.g., 'English (US)')"
  type        = string
  default     = "English (US)"
}

data "genesyscloud_language" "default" {
  name = var.default_language_name
}

locals {
  default_language_id = data.genesyscloud_language.default.id
}

Update the queue resource to use this ID:

resource "genesyscloud_routing_queue" "dynamic_queues" {
  for_each = local.queues_map

  name        = each.value.name
  description = each.value.description
  wrap_up_policy = each.value.wrap_up_policy
  outbound_email = each.value.outbound_email
  skills = each.value.skills
  language_id = local.default_language_id
}

Complete Working Example

Below is the complete main.tf file. Save this as main.tf in the same directory as queues.yaml.

terraform {
  required_providers {
    genesyscloud = {
      source  = "genesyscloud/genesyscloud"
      version = ">= 1.0.0"
    }
  }
}

provider "genesyscloud" {
  # Credentials sourced from environment variables
}

variable "default_language_name" {
  description = "The name of the default routing language"
  type        = string
  default     = "English (US)"
}

# Fetch the language ID to use in queue creation
data "genesyscloud_language" "default" {
  name = var.default_language_name
}

locals {
  # Read and parse the YAML file
  queues_raw  = file("${path.module}/queues.yaml")
  queues_list = yamldecode(local.queues_raw)
  
  # Create a map keyed by queue name for stable for_each iteration
  queues_map = { for q in local.queues_list : q.name => q }
  
  # Cache the language ID
  default_language_id = data.genesyscloud_language.default.id
}

# Provision queues based on the YAML configuration
resource "genesyscloud_routing_queue" "dynamic_queues" {
  for_each = local.queues_map

  name        = each.value.name
  description = each.value.description
  wrap_up_policy = each.value.wrap_up_policy
  outbound_email = each.value.outbound_email
  skills = each.value.skills
  language_id = local.default_language_id
  
  # Optional: Add tags if needed
  # tags = ["automated-provisioning"]
}

And the corresponding queues.yaml:

- name: "Sales Support"
  description: "General sales inquiries"
  wrap_up_policy: "FORCE"
  skills:
    - "sales"
    - "english"
  outbound_email: "sales@example.com"

- name: "Tech Support"
  description: "Technical issues"
  wrap_up_policy: "IGNORE_IF_EMPTY"
  skills:
    - "technical"
    - "english"
  outbound_email: "tech@example.com"

To run this:

  1. Set environment variables:
    export GENESYS_CLOUD_REGION="my.genesys.cloud"
    export GENESYS_CLOUD_CLIENT_ID="your_client_id"
    export GENESYS_CLOUD_CLIENT_SECRET="your_client_secret"
    
  2. Initialize Terraform:
    terraform init
    
  3. Plan the changes:
    terraform plan
    
  4. Apply the changes:
    terraform apply
    

Common Errors & Debugging

Error: Invalid function argument: yamldecode failed

  • Cause: The YAML file is malformed or contains syntax errors.
  • Fix: Validate your YAML syntax using a linter or online validator. Ensure indentation is consistent (spaces, not tabs). Check for missing quotes around strings that contain special characters.

Error: Skill not found: “sales”

  • Cause: The skill named “sales” does not exist in your Genesys Cloud organization.
  • Fix: Create the skill in Genesys Cloud first, either manually or via a separate genesyscloud_routing_skill resource. Terraform will not create skills implicitly when assigning them to queues.

Error: 409 Conflict: Queue with name “Sales Support” already exists

  • Cause: You are trying to create a queue with a name that already exists, but it is not managed by Terraform (orphaned resource).
  • Fix: You have two options:
    1. Import the existing queue into Terraform state:
      terraform import genesyscloud_routing_queue.dynamic_queues["Sales Support"] <queue_id>
      
    2. Delete the existing queue manually in the Genesys Cloud admin console and re-run terraform apply.

Error: 403 Forbidden: Insufficient permissions

  • Cause: The OAuth client used for authentication lacks the queue:write scope.
  • Fix: Go to the Genesys Cloud Admin Console > Platform > OAuth Clients. Select your client, edit the grants, and ensure queue:write and routing:queue:view are selected. Save and regenerate the client secret if necessary.

Error: for_each map key conflict

  • Cause: Two entries in queues.yaml have the same name.
  • Fix: Ensure all queue names in the YAML file are unique. The for_each map creation will fail or overwrite entries if keys are duplicated, leading to unpredictable behavior.

Official References