How to Use for_each to Create Multiple Queues from a YAML Variable File in Terraform
What You Will Build
- You will provision a dynamic set of Genesys Cloud CX queues by iterating over a list of definitions stored in a YAML file.
- This tutorial uses the
for_eachmeta-argument in Terraform to map YAML entries to individualgenesyscloud_routing_queueresources. - The implementation is written in HCL (HashiCorp Configuration Language) using the official Genesys Cloud Terraform Provider.
Prerequisites
- Terraform Version: 1.5.0 or later.
- Genesys Cloud Terraform Provider: Version 1.10.0 or later.
- Genesys Cloud Organization: An active Genesys Cloud CX organization with an API user that has
routing:queue:writeandrouting:queue:viewscopes. - Dependencies:
yamlparser support is native in Terraform via theyamldecodefunction.- No external Python or Node.js dependencies are required.
Authentication Setup
Terraform handles OAuth authentication for Genesys Cloud automatically when the provider is configured. You do not need to write manual token exchange code. Instead, you configure the provider block with your credentials or environment variables.
Provider Configuration
Create a file named main.tf. This file initializes the provider and defines the data source that reads the YAML file.
terraform {
required_providers {
genesyscloud = {
source = "myntra/genesyscloud"
version = "~> 1.10.0"
}
}
}
provider "genesyscloud" {
# Credentials can be passed via environment variables:
# GENESYS_CLOUD_REGION, GENESYS_CLOUD_CLIENT_ID, GENESYS_CLOUD_CLIENT_SECRET
# Or explicitly here (not recommended for production):
# region = "us-east-1"
# client_id = "your_client_id"
# client_secret = "your_client_secret"
}
The YAML Variable File
Create a file named queues.yaml. This file contains the list of queues to be created. Each entry must include a unique name and a unique external_id (used by Terraform to track state).
queues:
- name: "Sales Support - Tier 1"
external_id: "sales-t1"
description: "First line of support for sales inquiries"
split_workflow_id: null
enabled: true
queue_flow: "longest_available_agent"
default_outbound_email: ""
wrap_up_policy: "optional"
- name: "Sales Support - Tier 2"
external_id: "sales-t2"
description: "Escalated issues for sales team"
split_workflow_id: null
enabled: true
queue_flow: "shortest_queue"
default_outbound_email: ""
wrap_up_policy: "required"
- name: "Technical Support - Billing"
external_id: "tech-billing"
description: "Billing and payment issues"
split_workflow_id: null
enabled: true
queue_flow: "longest_available_agent"
default_outbound_email: ""
wrap_up_policy: "optional"
Implementation
Step 1: Parse the YAML File into a Terraform Map
Terraform does not iterate directly over YAML lists using for_each. The for_each argument requires a map or a set of strings. Therefore, you must transform the YAML list into a map where the keys are stable identifiers (such as external_id) and the values are the queue configurations.
Use the yamldecode function to parse the file and the for expression to convert the list into a map.
Add this to main.tf:
# Read the YAML file
locals {
# yamldecode returns a map. We access the 'queues' key which contains the list.
raw_queues = yamldecode(file("${path.module}/queues.yaml"))["queues"]
# Convert the list of maps into a map keyed by external_id.
# This allows for_each to handle creation, update, and deletion of specific queues.
queue_map = {
for q in local.raw_queues : q.external_id => q
}
}
Why this matters: If you use count instead of for_each, adding a new queue to the middle of the YAML list would force Terraform to destroy and recreate all subsequent queues because their indices would shift. for_each uses the external_id as a stable key, so adding or removing an entry only affects that specific resource.
Step 2: Define the Queue Resource with for_each
Now, define the genesyscloud_routing_queue resource. This resource maps directly to the Genesys Cloud API endpoint POST /api/v2/routing/queues.
The for_each meta-argument iterates over local.queue_map. Inside the block, each.value refers to the specific queue object from the YAML file.
Add this to main.tf:
resource "genesyscloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
enabled = each.value.enabled
external_id = each.value.external_id
# Queue Flow: Determines how Genesys routes conversations to agents.
# Valid values: "longest_available_agent", "shortest_queue", "random", "balanced"
queue_flow = each.value.queue_flow
# Wrap-up Policy: Determines if agents must mark conversations as complete.
# Valid values: "optional", "required"
wrap_up_policy = each.value.wrap_up_policy
# Default Outbound Email: Used if the queue sends emails.
# If not set, leave as empty string or omit.
default_outbound_email = each.value.default_outbound_email != "" ? each.value.default_outbound_email : null
# Split Workflow ID: Optional. Links the queue to a specific IVR/Flow.
# If null in YAML, Terraform will not set this field.
dynamic "split_workflow" {
for_each = each.value.split_workflow_id != null ? [each.value.split_workflow_id] : []
content {
id = split_workflow.value
}
}
# Language Settings: Genesys requires at least one language setting.
# We default to English if not specified in YAML.
languages {
language_code = "en-US"
is_default = true
}
# Skill Requirements: Optional. Add if your queues require specific skills.
# This example leaves it empty for simplicity, but in production, you might
# parse a list of skill IDs from the YAML.
# skill_requirements {
# skill_id = "some-skill-id"
# level = 500
# required = true
# }
}
API Mapping Note:
namemaps tonamein the API body.external_idmaps toexternal_id. This is critical for idempotency.queue_flowmaps toqueue_flow.wrap_up_policymaps towrap_up_policy.languagesmaps tolanguage_settings. The provider expects a list of objects.
Step 3: Handle Dependencies and Outputs
If other resources (such as genesyscloud_routing_user or genesyscloud_ivr) need to reference these queues, you must reference them by their external_id key in the genesyscloud_routing_queue.dynamic_queues map.
Add this to main.tf to output the created queue IDs for verification:
output "created_queue_ids" {
description = "Map of external_id to Genesys Cloud Queue ID"
value = { for k, v in genesyscloud_routing_queue.dynamic_queues : k => v.id }
}
Complete Working Example
Below is the complete main.tf file. Create this file in a new directory, create the queues.yaml file as shown above, and run terraform init followed by terraform apply.
terraform {
required_providers {
genesyscloud = {
source = "myntra/genesyscloud"
version = "~> 1.10.0"
}
}
}
provider "genesyscloud" {
# Ensure GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET are set in your environment
}
locals {
# Parse the YAML file
raw_queues = yamldecode(file("${path.module}/queues.yaml"))["queues"]
# Convert list to map keyed by external_id for stable for_each iteration
queue_map = {
for q in local.raw_queues : q.external_id => q
}
}
resource "genesyscloud_routing_queue" "dynamic_queues" {
for_each = local.queue_map
name = each.value.name
description = each.value.description
enabled = each.value.enabled
external_id = each.value.external_id
queue_flow = each.value.queue_flow
wrap_up_policy = each.value.wrap_up_policy
# Handle optional default_outbound_email
default_outbound_email = each.value.default_outbound_email != "" ? each.value.default_outbound_email : null
# Handle optional split_workflow_id
dynamic "split_workflow" {
for_each = each.value.split_workflow_id != null ? [each.value.split_workflow_id] : []
content {
id = split_workflow.value
}
}
languages {
language_code = "en-US"
is_default = true
}
}
output "created_queue_ids" {
description = "Map of external_id to Genesys Cloud Queue ID"
value = { for k, v in genesyscloud_routing_queue.dynamic_queues : k => v.id }
}
Execution Steps
- Initialize:
terraform init - Plan:
Verify that Terraform showsterraform plan+3 to add(or however many queues are in your YAML). - Apply:
Typeterraform applyyesto confirm.
Common Errors & Debugging
Error: Error: Invalid index
Cause: The yamldecode function fails because the YAML file is malformed or the key path is incorrect. For example, if your YAML file does not have a top-level queues: key, yamldecode(...)[...] will return null.
Fix: Validate your YAML syntax. Ensure the top-level key matches the one you are accessing in locals.
# Debugging: Output the raw parsed data to see its structure
output "debug_raw" {
value = local.raw_queues
}
Error: Error: Queue with external_id 'sales-t1' already exists
Cause: You are trying to create a queue with an external_id that already exists in Genesys Cloud but is not managed by Terraform.
Fix: Either delete the existing queue in Genesys Cloud and re-run terraform apply, or use terraform import to bring the existing queue under Terraform management.
terraform import genesyscloud_routing_queue.dynamic_queues["sales-t1"] <genesys_cloud_queue_id>
Error: Error: Attribute "queue_flow" is required
Cause: The YAML file does not include a queue_flow field, or it is set to null. The Genesys Cloud API requires a valid queue flow type.
Fix: Ensure every entry in queues.yaml has a valid queue_flow value. Valid values are:
longest_available_agentshortest_queuerandombalanced
Error: 429 Too Many Requests
Cause: Creating many queues in a single terraform apply can trigger Genesys Cloud API rate limits.
Fix: The Genesys Cloud Terraform Provider includes built-in retry logic for 429 errors. However, if you are creating hundreds of queues, you may need to split the YAML file into smaller chunks or add a time_sleep resource between batches (though this is rarely necessary for small-to-medium deployments).