Create Multiple Genesys Cloud Queues from a YAML File Using Terraform `for_each`

Create Multiple Genesys Cloud Queues from a YAML File Using Terraform for_each

What You Will Build

  • A Terraform script that reads a YAML configuration file and dynamically creates multiple Genesys Cloud CX Queues.
  • The script uses the for_each meta-argument to iterate over the YAML data, ensuring each queue is created with unique settings.
  • The implementation uses the official Genesys Cloud Provider for Terraform and Python for YAML parsing.

Prerequisites

  • Terraform: Version 1.5.0 or higher.
  • Genesys Cloud Provider: Version 1.100.0 or higher.
  • Python: Version 3.9 or higher with the pyyaml library installed (pip install pyyaml).
  • Genesys Cloud Organization ID: Available in your Genesys Cloud Admin Console under Settings > Organization.
  • OAuth Credentials: A client ID and client secret with the queue:write scope.
  • Operating System: Linux, macOS, or Windows with WSL2.

Authentication Setup

Terraform handles OAuth authentication via environment variables. You must set these variables before running any Terraform commands. The provider uses these to generate access tokens automatically.

Set the following environment variables in your terminal:

export GENESYS_CLOUD_REGION="us-east-1"
export GENESYS_CLOUD_ORGANIZATION_ID="your-organization-id-here"
export GENESYS_CLOUD_CLIENT_ID="your-client-id"
export GENESYS_CLOUD_CLIENT_SECRET="your-client-secret"

If you do not set these, Terraform will fail with a 401 Unauthorized error during the provider initialization phase. The provider does not support interactive login flows; it requires machine-to-machine authentication for infrastructure-as-code operations.

Implementation

Step 1: Define the Queue Configuration in YAML

Create a file named queues.yaml in your project root. This file defines the data structure for the queues you intend to create. We use YAML because it is human-readable and easy to parse into Terraform maps.

# queues.yaml
support_general:
  name: "General Support"
  description: "Queue for general customer inquiries"
  enabled: true
  wrap_up_policy: "OPTIONAL"
  conversation_audio_recording: "NONE"
  small_group_size: 3
  large_group_size: 5
  small_group_size_threshold: 15
  large_group_size_threshold: 20
  member_flow: "LONGEST_IDLE_AGENT"
  empty_queue_flow: "DROP"
  max_wait_time: 300
  max_wait_time_in_queue: 1800

sales_enterprise:
  name: "Enterprise Sales"
  description: "Queue for high-value enterprise sales leads"
  enabled: true
  wrap_up_policy: "REQUIRED"
  conversation_audio_recording: "ALL"
  small_group_size: 2
  large_group_size: 4
  small_group_size_threshold: 10
  large_group_size_threshold: 25
  member_flow: "LONGEST_IDLE_AGENT"
  empty_queue_flow: "TRANSFER"
  max_wait_time: 60
  max_wait_time_in_queue: 1200

Each key in the YAML file (e.g., support_general) becomes the unique identifier for the Terraform resource. This key must be unique across all resources managed by this for_each block.

Step 2: Parse YAML and Initialize Terraform

Create a file named main.tf. This file initializes the provider and reads the YAML file. Terraform does not natively parse YAML, so we use a local value with the jsondecode function after converting the YAML to JSON, or more commonly, we use a simple Python script to convert YAML to JSON if the structure is complex. However, for this tutorial, we will use a simpler approach: we will use the file function to read the YAML and then use a local script to convert it, or better yet, we will use the yaml provider if available, but the standard and most robust way without external providers is to convert the YAML to JSON using a pre-processing step or use a simple Python snippet within a null_resource if dynamic.

Actually, the most robust and “Terraform-native” way to handle complex YAML without external providers is to convert the YAML to JSON manually or via a simple script before running Terraform, OR use the local block with jsondecode(file("queues.json")).

For this tutorial, we will assume you have a script convert.py that converts queues.yaml to queues.json. This is a common pattern in CI/CD pipelines.

Create convert.py:

import yaml
import json

with open('queues.yaml', 'r') as f:
    data = yaml.safe_load(f)

with open('queues.json', 'w') as f:
    json.dump(data, f, indent=2)

Run this script once:

python convert.py

Now, update main.tf to read the JSON file:

terraform {
  required_providers {
    genesyscloud = {
      source = "mikesplain/genesyscloud"
      version = ">= 1.100.0"
    }
  }
}

provider "genesyscloud" {
  # Authentication is handled via environment variables
}

# Read the queue definitions from the JSON file
locals {
  queue_definitions = jsondecode(file("queues.json"))
}

Step 3: Create Queues Using for_each

Add the resource definition to main.tf. The for_each argument iterates over the local.queue_definitions map. Each key-value pair becomes a separate resource instance.

resource "genesyscloud_routing_queue" "queues" {
  for_each = local.queue_definitions

  name        = each.value.name
  description = each.value.description
  enabled     = each.value.enabled

  # Wrap-up policy determines how long an agent has to wrap up after a conversation ends
  wrap_up_policy = each.value.wrap_up_policy

  # Audio recording settings
  conversation_audio_recording = each.value.conversation_audio_recording

  # Grouping settings for routing
  small_group_size = each.value.small_group_size
  large_group_size = each.value.large_group_size
  small_group_size_threshold = each.value.small_group_size_threshold
  large_group_size_threshold = each.value.large_group_size_threshold

  # Member flow determines how agents are selected from the queue
  member_flow = each.value.member_flow

  # Empty queue flow determines what happens when no agents are available
  empty_queue_flow = each.value.empty_queue_flow

  # Wait time settings in seconds
  max_wait_time = each.value.max_wait_time
  max_wait_time_in_queue = each.value.max_wait_time_in_queue

  # Required: A default language for the queue
  default_language = "en-US"

  # Optional: Add tags for organizational purposes
  tags = ["auto-generated", "terraform"]
}

Key points about this configuration:

  1. for_each: This creates a resource instance for each entry in local.queue_definitions. The resource ID in Terraform state will be genesyscloud_routing_queue.queues["support_general"].
  2. each.value: This refers to the value associated with the current key in the iteration.
  3. default_language: This is a required field for queues. We hardcode it here, but you could also make it dynamic if your YAML included it.
  4. tags: These are metadata tags applied to the queue in Genesys Cloud for filtering and organization.

Step 4: Initialize and Apply

Initialize the Terraform working directory:

terraform init

This downloads the Genesys Cloud provider.

Validate the configuration:

terraform validate

Preview the changes:

terraform plan

You should see a plan that creates two new queues:

  1. genesyscloud_routing_queue.queues["support_general"]
  2. genesyscloud_routing_queue.queues["sales_enterprise"]

Apply the configuration:

terraform apply

Confirm the changes by typing yes when prompted. Terraform will call the Genesys Cloud API to create the queues.

Complete Working Example

Below is the complete main.tf file for reference.

terraform {
  required_providers {
    genesyscloud = {
      source = "mikesplain/genesyscloud"
      version = ">= 1.100.0"
    }
  }
}

provider "genesyscloud" {
  # Authentication is handled via environment variables:
  # GENESYS_CLOUD_REGION
  # GENESYS_CLOUD_ORGANIZATION_ID
  # GENESYS_CLOUD_CLIENT_ID
  # GENESYS_CLOUD_CLIENT_SECRET
}

# Read the queue definitions from the JSON file generated by convert.py
locals {
  queue_definitions = jsondecode(file("queues.json"))
}

resource "genesyscloud_routing_queue" "queues" {
  for_each = local.queue_definitions

  name        = each.value.name
  description = each.value.description
  enabled     = each.value.enabled

  # Wrap-up policy
  wrap_up_policy = each.value.wrap_up_policy

  # Audio recording
  conversation_audio_recording = each.value.conversation_audio_recording

  # Grouping
  small_group_size = each.value.small_group_size
  large_group_size = each.value.large_group_size
  small_group_size_threshold = each.value.small_group_size_threshold
  large_group_size_threshold = each.value.large_group_size_threshold

  # Routing
  member_flow = each.value.member_flow
  empty_queue_flow = each.value.empty_queue_flow

  # Wait times
  max_wait_time = each.value.max_wait_time
  max_wait_time_in_queue = each.value.max_wait_time_in_queue

  # Required fields
  default_language = "en-US"

  # Optional fields
  tags = ["auto-generated", "terraform"]
}

# Output the IDs of the created queues for verification
output "queue_ids" {
  value = {
    for k, v in genesyscloud_routing_queue.queues : k => v.id
  }
}

output "queue_names" {
  value = {
    for k, v in genesyscloud_routing_queue.queues : k => v.name
  }
}

Common Errors & Debugging

Error: Error: Conflicting configuration arguments

Cause: You have specified a field in the genesyscloud_routing_queue resource that conflicts with another field or is not allowed in the current provider version. For example, specifying both small_group_size and large_group_size with invalid values.

Fix: Ensure all numeric fields are integers and boolean fields are booleans. Check the Genesys Cloud Routing Queue API documentation for valid values.

Error: Error: Invalid index

Cause: The for_each argument expects a map or a set of strings. If jsondecode(file("queues.json")) returns a list instead of a map, Terraform will throw this error.

Fix: Ensure your queues.yaml file uses keys (like support_general) at the top level, not a list of objects. The YAML structure must be a dictionary/map.

Error: Error: Error creating queue: 409 Conflict

Cause: A queue with the same name already exists in your Genesys Cloud organization. Genesys Cloud enforces unique queue names.

Fix: Either delete the existing queue in the Admin Console or change the name field in your queues.yaml file to a unique value. You can also use the import command to bring an existing queue into Terraform state if you want to manage it going forward.

Error: Error: Provider produced inconsistent final plan

Cause: This can happen if the provider returns a value that differs from what was planned, often due to default values being applied by the API.

Fix: Run terraform apply again. If the error persists, check the provider version and update to the latest release. Ensure that all required fields are explicitly defined in your resource block.

Official References