How to use for_each to create multiple queues from a YAML variable file
What You Will Build
- A Terraform configuration that reads a list of queue definitions from a YAML file and creates them in Genesys Cloud CX.
- This uses the Genesys Cloud CX Terraform Provider and the
localordatasource patterns to parse YAML. - The programming language covered is HCL (HashiCorp Configuration Language) for Terraform.
Prerequisites
- Genesys Cloud CX Organization: You need an active organization with API access.
- Terraform: Version 1.5+ installed on your local machine.
- Genesys Cloud Terraform Provider: Version 1.50.0+ (ensure you use a version that supports the
genesys_cloud_routing_queueresource). - YAML File: A local file named
queues.yamlcontaining your queue definitions. - Credentials: You must have a Genesys Cloud OAuth Client ID and Secret.
Authentication Setup
Terraform handles authentication to Genesys Cloud via the provider block. You do not need to write manual OAuth token refresh logic in HCL. The provider manages the token lifecycle.
Create a file named terraform.tfvars to store your credentials securely. Do not commit this file to version control.
# terraform.tfvars
# These values should be replaced with your actual Genesys Cloud API credentials
genesys_cloud_client_id = "your-client-id-here"
genesys_cloud_client_secret = "your-client-secret-here"
genesys_cloud_base_url = "https://api.mypurecloud.com"
Initialize the provider in your main.tf. This block establishes the connection to the Genesys Cloud API.
# main.tf
terraform {
required_providers {
genesyscloud = {
source = "mycloudgenesys/genesyscloud"
version = "~> 1.50.0"
}
}
}
provider "genesyscloud" {
# The provider uses the environment variables or the tfvars file
# If using tfvars, ensure the keys match the expected provider arguments
# or set them via environment variables: GENESYS_CLOUD_CLIENT_ID, etc.
}
Note: The Genesys Cloud Terraform provider prefers environment variables for credentials in production pipelines. For local development, terraform.tfvars works if you map them correctly, but the standard approach is:
export GENESYS_CLOUD_CLIENT_ID="your-client-id"
export GENESYS_CLOUD_CLIENT_SECRET="your-client-secret"
export GENESYS_CLOUD_BASE_URL="https://api.mypurecloud.com"
Implementation
Step 1: Define the YAML Data Structure
Create a file named queues.yaml. This file will serve as the source of truth for your queue configurations. We will use a list of objects. Each object represents one queue.
# queues.yaml
queues:
- name: "Support - English"
description: "Primary support queue for English speakers"
outbound_enabled: true
member_flow: "LONGEST_IDLE_AGENT"
wrap_up_policy: "OPTIONAL"
enabled: true
small_business_queue: false
tags:
- "support"
- "english"
- name: "Support - Spanish"
description: "Primary support queue for Spanish speakers"
outbound_enabled: false
member_flow: "LONGEST_IDLE_AGENT"
wrap_up_policy: "REQUIRED"
enabled: true
small_business_queue: false
tags:
- "support"
- "spanish"
- name: "Sales - Inbound"
description: "Inbound sales calls"
outbound_enabled: false
member_flow: "SKILL_BASED"
wrap_up_policy: "OPTIONAL"
enabled: true
small_business_queue: false
tags:
- "sales"
Step 2: Parse YAML in Terraform
Terraform does not natively parse YAML files into complex objects in older versions. However, recent versions of Terraform (and the Genesys Cloud provider ecosystem) often rely on the local value combined with the yamldecode function or external data sources.
The most robust and standard way to handle this in modern Terraform without external plugins is using the file function combined with yamldecode.
Create a variables.tf to define the path to your YAML file, allowing it to be overridden if needed.
# variables.tf
variable "queue_yaml_path" {
description = "Path to the YAML file containing queue definitions"
type = string
default = "./queues.yaml"
}
In your main.tf (or a dedicated locals.tf), parse the YAML file. This converts the YAML structure into a Terraform map/list that can be iterated over.
# locals.tf
# Read the file content
local queue_yaml_content = file(var.queue_yaml_path)
# Parse the YAML into a Terraform object
local queue_data = yamldecode(local.queue_yaml_content)
# Extract the list of queues from the parsed object
# Assuming the YAML structure has a top-level key "queues"
local queue_list = local.queue_data.queues
Why this approach?
Using yamldecode allows you to keep your configuration data separate from your infrastructure logic. This is critical for large deployments where queue definitions change frequently. It avoids hardcoding queue names and settings in the HCL files, reducing merge conflicts and increasing maintainability.
Step 3: Create Queues Using for_each
Now, use the genesys_cloud_routing_queue resource with the for_each meta-argument. The for_each argument takes a map or a set of strings. Since our queue_list is a list of objects, we need to convert it into a map where the key is a unique identifier (like the queue name or an index) and the value is the object itself.
Terraform does not allow for_each to iterate directly over a list of objects. We must create a map. We can use the queue name as the key, assuming names are unique.
# main.tf
# Convert the list of queue objects into a map keyed by name
# This is necessary because for_each requires a map or set
local queue_map = {
for q in local.queue_list : q.name => q
}
resource "genesys_cloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
enabled = each.value.enabled
small_business_queue = each.value.small_business_queue
# Optional but recommended: Outbound settings
outbound_enabled = each.value.outbound_enabled
# Member flow configuration
member_flow = each.value.member_flow
# Wrap up policy
wrap_up_policy = each.value.wrap_up_policy
# Tags
tags = each.value.tags
# Note: The Genesys Cloud API requires a language_id for the queue.
# For simplicity in this tutorial, we assume English (en-US).
# In a production environment, you might want to map languages in the YAML.
language_id = "en-US"
}
Critical API Detail:
The genesys_cloud_routing_queue resource maps to the /api/v2/routing/queues endpoint. The member_flow parameter corresponds to the memberFlow field in the API. Valid values include LONGEST_IDLE_AGENT, SKILL_BASED, CUSTOM, etc. The wrap_up_policy maps to wrapUpPolicy.
If your YAML file does not include language_id, the code above defaults to en-US. If you need multi-language support, add language_id to your YAML file and reference it via each.value.language_id.
Step 4: Handling Dependencies and State
When using for_each, Terraform creates a separate resource instance for each item in the map. The address of each resource will be genesys_cloud_routing_queue.dynamic_queues["Support - English"].
If you have other resources that depend on these queues (e.g., routing rules, users assigned to queues), you must reference the specific queue instance.
Example: Creating a routing rule that uses the “Support - English” queue.
# main.tf
# Example: A routing rule that uses one of the dynamically created queues
# This demonstrates how to reference a specific item from the for_each map
resource "genesys_cloud_routing_routingrule" "example_rule" {
name = "Route to English Support"
description = "Routes calls to the English support queue"
enabled = true
# Reference the specific queue by its key in the map
# The key must match exactly what is in the YAML file
queue_id = genesys_cloud_routing_queue.dynamic_queues["Support - English"].id
# Additional routing rule configuration would go here
# e.g., conditions, actions, etc.
}
Error Handling:
If the key "Support - English" does not exist in the local.queue_map (i.e., it is not in the YAML file), Terraform will fail during the plan phase with an error: Reference to undeclared resource. This is a safety feature that prevents silent failures. Ensure your YAML file contains all keys referenced in dependent resources.
Complete Working Example
Here is the complete set of files required to run this tutorial.
1. queues.yaml
queues:
- name: "Support - English"
description: "Primary support queue for English speakers"
outbound_enabled: true
member_flow: "LONGEST_IDLE_AGENT"
wrap_up_policy: "OPTIONAL"
enabled: true
small_business_queue: false
tags:
- "support"
- "english"
language_id: "en-US"
- name: "Support - Spanish"
description: "Primary support queue for Spanish speakers"
outbound_enabled: false
member_flow: "LONGEST_IDLE_AGENT"
wrap_up_policy: "REQUIRED"
enabled: true
small_business_queue: false
tags:
- "support"
- "spanish"
language_id: "es-ES"
2. variables.tf
variable "queue_yaml_path" {
description = "Path to the YAML file containing queue definitions"
type = string
default = "./queues.yaml"
}
3. locals.tf
local queue_yaml_content = file(var.queue_yaml_path)
local queue_data = yamldecode(local.queue_yaml_content)
local queue_list = local.queue_data.queues
# Create a map from the list, keyed by name
local queue_map = {
for q in local.queue_list : q.name => q
}
4. main.tf
terraform {
required_providers {
genesyscloud = {
source = "mycloudgenesys/genesyscloud"
version = "~> 1.50.0"
}
}
}
provider "genesyscloud" {
# Credentials are handled via environment variables
}
resource "genesys_cloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
enabled = each.value.enabled
small_business_queue = each.value.small_business_queue
outbound_enabled = each.value.outbound_enabled
member_flow = each.value.member_flow
wrap_up_policy = each.value.wrap_up_policy
tags = each.value.tags
language_id = each.value.language_id
}
5. terraform.tfvars (Optional, if not using environment variables)
# If you prefer to pass credentials via tfvars, you must map them to provider args
# However, the Genesys provider primarily uses env vars.
# See documentation for specific provider argument names if using tfvars.
6. output.tf (Optional, to verify creation)
output "created_queue_ids" {
description = "Map of queue names to their Genesys Cloud IDs"
value = { for k, v in genesys_cloud_routing_queue.dynamic_queues : k => v.id }
}
Common Errors & Debugging
Error: Error: Invalid index
What causes it:
This error occurs when you try to access a key in the genesys_cloud_routing_queue.dynamic_queues map that does not exist. For example, if your YAML file has "Support - English" but your code references "Support-English" (missing space).
How to fix it:
Verify that the key you are using in genesys_cloud_routing_queue.dynamic_queues["Key Name"] matches the name field in your YAML file exactly. Case sensitivity matters.
Code showing the fix:
# Incorrect
queue_id = genesys_cloud_routing_queue.dynamic_queues["Support-English"].id
# Correct (matches YAML name exactly)
queue_id = genesys_cloud_routing_queue.dynamic_queues["Support - English"].id
Error: Error: yamldecode: yaml: line 10: did not find expected key
What causes it:
This error indicates a syntax error in your queues.yaml file. YAML is sensitive to indentation. Ensure you use spaces (not tabs) and consistent indentation.
How to fix it:
Use a YAML linter or validator to check the syntax of your queues.yaml file. Ensure that lists are properly indented under their parent keys.
Code showing the fix:
# Incorrect (inconsistent indentation)
queues:
- name: "Test"
description: "Test"
# Correct (consistent indentation)
queues:
- name: "Test"
description: "Test"
Error: Error: 400 Bad Request during terraform apply
What causes it:
This error occurs when the Genesys Cloud API rejects the queue configuration. Common reasons include:
- Invalid
member_flowvalue. - Invalid
wrap_up_policyvalue. - Duplicate queue names in the same organization.
- Missing
language_id.
How to fix it:
Check the Terraform output for the specific API error message. Ensure that the values in your YAML file match the valid enums for the Genesys Cloud API. For example, member_flow must be one of LONGEST_IDLE_AGENT, SKILL_BASED, etc.
Code showing the fix:
# Incorrect (invalid member_flow)
queues:
- name: "Test"
member_flow: "INVALID_FLOW"
# Correct (valid member_flow)
queues:
- name: "Test"
member_flow: "LONGEST_IDLE_AGENT"
Error: Error: Reference to undeclared resource
What causes it:
This error occurs when you reference a resource that does not exist in the current Terraform configuration. This often happens when you try to reference a queue from the for_each map before the map is defined or if the YAML file is empty.
How to fix it:
Ensure that the locals.tf file is loaded before the main.tf file where the resources are defined. Terraform loads files in alphabetical order, but it is best practice to ensure all local variables are defined before they are used. If the YAML file is empty, the queue_map will be empty, and references to specific keys will fail.
Code showing the fix:
# Ensure locals are defined
locals {
queue_list = yamldecode(file(var.queue_yaml_path)).queues
queue_map = { for q in local.queue_list : q.name => q }
}
# Then use in resources
resource "genesys_cloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
# ...
}