Automate Genesys Cloud Queue Provisioning with Terraform for_each and YAML
What You Will Build
- A Terraform configuration that reads a YAML file containing queue definitions and creates multiple Genesys Cloud CX queues in a single apply.
- This tutorial uses the Genesys Cloud Terraform Provider (
genesyscloud/genesyscloud) and theyamldecodefunction. - The code is written in HashiCorp Configuration Language (HCL) with no external scripting languages required.
Prerequisites
- Terraform Version: 1.5.0 or later.
- Genesys Cloud Provider: Version 1.20.0 or later.
- Genesys Cloud API Client: A Service Account or Client Credentials client ID and secret with the
queue:writescope. - Environment Variables:
GENESYS_CLOUD_REGION(e.g.,mypurecloud.comorus-east-1.mypurecloud.ie) andGENESYS_CLOUD_CLIENT_ID/GENESYS_CLOUD_CLIENT_SECRET. - Dependencies: No external pip or npm packages are needed. This is pure HCL.
Authentication Setup
Terraform handles authentication via the provider block. You must configure the provider to use your Genesys Cloud credentials. It is critical to use environment variables rather than hardcoding secrets in your .tf files.
Create a main.tf file and define the provider:
terraform {
required_providers {
genesyscloud = {
source = "genesyscloud/genesyscloud"
version = ">= 1.20.0"
}
}
}
provider "genesyscloud" {
# These are automatically picked up from environment variables
# GENESYS_CLOUD_CLIENT_ID
# GENESYS_CLOUD_CLIENT_SECRET
# GENESYS_CLOUD_REGION
}
If you do not have environment variables set, you can use a credentials block, but this is discouraged for production:
provider "genesyscloud" {
client_id = "your-client-id"
client_secret = "your-client-secret"
region = "mypurecloud.com"
}
Implementation
Step 1: Define the Queue Data in YAML
The for_each meta-argument requires a map or a set. A YAML file is an excellent source for structured data. We will define a list of queue objects. Each object will contain the name, description, and outbound_calling_enabled flag.
Create a file named queues.yaml:
queues:
- name: "Sales Support - Tier 1"
description: "Handles initial customer inquiries for sales."
outbound_calling_enabled: true
wrap_up_code_required: false
enable_wait_time: true
enable_queue_metrics: true
- name: "Sales Support - Tier 2"
description: "Escalations for complex sales issues."
outbound_calling_enabled: true
wrap_up_code_required: true
enable_wait_time: true
enable_queue_metrics: true
- name: "Billing Inquiries"
description: "Handles billing and payment questions."
outbound_calling_enabled: false
wrap_up_code_required: false
enable_wait_time: true
enable_queue_metrics: true
Note that we use a key queues to hold the list. The Terraform yamldecode function will parse this into a map.
Step 2: Parse YAML and Create Resources with for_each
In your main.tf, you will use the file() function to read the YAML content and yamldecode() to convert it into a Terraform-compatible map.
The genesyscloud_routing_queue resource supports for_each. This allows Terraform to manage each queue as an independent resource instance. If you add a new queue to the YAML file later, Terraform will only create that new queue, not destroy and recreate the existing ones.
locals {
# Read and parse the YAML file
# yamldecode returns a map. We access the 'queues' key which contains the list.
# To use for_each on a list, we must convert it to a map.
# We use the 'name' as the key for the map.
raw_queues = yamldecode(file("${path.module}/queues.yaml")).queues
# Convert the list of objects into a map of objects keyed by name
# This is necessary because for_each on a list uses indices, which are unstable if items are added/removed.
# Using a unique name as the key ensures stability.
queue_map = { for q in local.raw_queues : q.name => q }
}
resource "genesyscloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
enable_wait_time = each.value.enable_wait_time
enable_queue_metrics = each.value.enable_queue_metrics
wrap_up_code_required = each.value.wrap_up_code_required
# Outbound calling settings
outbound_calling_enabled = each.value.outbound_calling_enabled
# Default settings for new queues to prevent validation errors
# The provider requires these to be explicitly set or inherited from a default.
# Here we set explicit defaults to ensure idempotency.
max_wait_time_ms = 3600000 # 1 hour
max_queue_size = 10000
addressable_name = each.value.name
# Language settings (optional but recommended for production)
languages {
language_id = "en-US"
priority = 1
}
}
Why use a map instead of a list for for_each?
If you use for_each on a list, Terraform identifies resources by their index (0, 1, 2). If you insert a new item at index 0, the old index 0 becomes index 1. Terraform sees this as a change in identity and may destroy the old resource and create a new one. By using a map with a stable key (like the queue name), Terraform recognizes that “Sales Support - Tier 1” still exists and only updates it if the configuration changes.
Step 3: Handle Dependencies and State
Queues often depend on other resources, such as Wrap-up Codes or Language Settings. In this example, we used a hardcoded language ID "en-US". In a production environment, you should retrieve the language ID dynamically to avoid hardcoding.
Add this lookup to your main.tf:
data "genesyscloud_language" "english" {
name = "English (United States)"
}
resource "genesyscloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
enable_wait_time = each.value.enable_wait_time
enable_queue_metrics = each.value.enable_queue_metrics
wrap_up_code_required = each.value.wrap_up_code_required
outbound_calling_enabled = each.value.outbound_calling_enabled
max_wait_time_ms = 3600000
max_queue_size = 10000
addressable_name = each.value.name
languages {
language_id = data.genesyscloud_language.english.id
priority = 1
}
}
This ensures that even if the language ID changes in Genesys Cloud (rare, but possible in multi-tenant environments), your Terraform state remains consistent.
Complete Working Example
Below is the complete main.tf file. Save this in a directory with the queues.yaml file.
terraform {
required_providers {
genesyscloud = {
source = "genesyscloud/genesyscloud"
version = ">= 1.20.0"
}
}
}
provider "genesyscloud" {
# Credentials are expected in environment variables:
# GENESYS_CLOUD_CLIENT_ID
# GENESYS_CLOUD_CLIENT_SECRET
# GENESYS_CLOUD_REGION
}
# Lookup the English language ID to avoid hardcoding
data "genesyscloud_language" "english" {
name = "English (United States)"
}
locals {
# Read the YAML file from the current module path
raw_queues = yamldecode(file("${path.module}/queues.yaml")).queues
# Convert list to map keyed by name for stable for_each behavior
queue_map = { for q in local.raw_queues : q.name => q }
}
resource "genesyscloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
# Queue behavior settings
enable_wait_time = each.value.enable_wait_time
enable_queue_metrics = each.value.enable_queue_metrics
wrap_up_code_required = each.value.wrap_up_code_required
# Outbound settings
outbound_calling_enabled = each.value.outbound_calling_enabled
# Capacity and addressability
max_wait_time_ms = 3600000
max_queue_size = 10000
addressable_name = each.value.name
# Language assignment
languages {
language_id = data.genesyscloud_language.english.id
priority = 1
}
}
# Output the IDs of the created queues for verification
output "queue_ids" {
value = { for name, queue in genesyscloud_routing_queue.dynamic_queues : name => queue.id }
}
The corresponding queues.yaml file:
queues:
- name: "Sales Support - Tier 1"
description: "Handles initial customer inquiries for sales."
outbound_calling_enabled: true
wrap_up_code_required: false
enable_wait_time: true
enable_queue_metrics: true
- name: "Sales Support - Tier 2"
description: "Escalations for complex sales issues."
outbound_calling_enabled: true
wrap_up_code_required: true
enable_wait_time: true
enable_queue_metrics: true
- name: "Billing Inquiries"
description: "Handles billing and payment questions."
outbound_calling_enabled: false
wrap_up_code_required: false
enable_wait_time: true
enable_queue_metrics: true
To run this:
- Initialize the provider:
terraform init - Plan the changes:
terraform plan - Apply the changes:
terraform apply
Common Errors & Debugging
Error: Error: Invalid index
What causes it:
This error occurs if you try to access a key in the YAML map that does not exist. For example, if your YAML file has a typo in enable_wait_time (e.g., enable_wait), and your HCL references each.value.enable_wait_time, Terraform will fail.
How to fix it:
Ensure the keys in your HCL resource block exactly match the keys in your YAML file. Use terraform console to debug the parsed structure:
terraform console
> yamldecode(file("queues.yaml"))
Verify the output matches your expectations.
Error: Error: Queue with name 'X' already exists
What causes it:
If you manually created a queue in the Genesys Cloud Admin Console with the same name as one in your YAML file, Terraform will detect a conflict. The provider uses the name as a unique identifier for lookup.
How to fix it:
Import the existing queue into Terraform state. First, find the queue ID in Genesys Cloud or via API. Then run:
terraform import genesyscloud_routing_queue.dynamic_queues["Sales Support - Tier 1"] <queue-id>
Replace <queue-id> with the actual ID. Note the syntax: ["Sales Support - Tier 1"] must match the key in your queue_map exactly.
Error: Error: Missing required argument
What causes it:
The Genesys Cloud API requires certain fields for queue creation. If your YAML file omits a required field like name or description, the provider will throw an error.
How to fix it:
Ensure every object in your YAML list has all the fields referenced in the genesyscloud_routing_queue resource block. You can set defaults in HCL if you do not want to specify them in YAML:
description = each.value.description != null ? each.value.description : "Default description"
Error: Error: 409 Conflict
What causes it:
This often happens if you are trying to create a queue with a name that is already in use by another queue in the same organization.
How to fix it:
Check the Genesys Cloud Admin Console for existing queues with the same name. Rename the queue in your YAML file or delete the conflicting queue in the console.