Implementing Version-Controlled Terraform Modules for Genesys Cloud Queue and Skill Configs
What This Guide Covers
You are implementing Infrastructure as Code (IaC) for your Genesys Cloud contact center configuration using the official Genesys Cloud Terraform provider. When complete, your queue configurations, skill definitions, routing policies, and wrap-up code sets will be declared as Terraform HCL resources in version-controlled modules, allowing all contact center configuration changes to flow through a standard Git PR → Plan → Apply workflow-providing auditability, change history, a single source of truth, and disaster recovery for your Genesys Cloud configuration state.
Prerequisites, Roles & Licensing
- Genesys Cloud: Any CX tier.
- Permissions required:
Routing > Queue > Add/Edit/DeleteRouting > Skill > Add/Edit/DeleteOAuth > Client > Create(for the Terraform service account)
- Infrastructure:
- Terraform CLI 1.5+ installed.
- The
genesyscloudTerraform provider (from the Terraform Registry). - A remote state backend (AWS S3 + DynamoDB state locking, or Terraform Cloud).
The Implementation Deep-Dive
1. The Configuration Drift Problem
Contact center configurations mutate constantly:
- A new queue is created manually via the Genesys Cloud Admin UI.
- A skill is renamed by an ops team member.
- A wrap-up code is deleted that 3 different flows depended on.
- Six months later, nobody knows what the “correct” state is.
Terraform solves this by making the Git repository the authoritative source of truth. Any configuration not declared in Terraform is drifted - and can be detected automatically.
2. Provider Configuration
# providers.tf
terraform {
required_providers {
genesyscloud = {
source = "MyPureCloud/genesyscloud"
version = "~> 1.32.0"
}
}
backend "s3" {
bucket = "your-terraform-state-bucket"
key = "genesys-cloud/contact-center.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock"
}
}
provider "genesyscloud" {
oauthclient_id = var.genesys_client_id
oauthclient_secret = var.genesys_client_secret
aws_region = "us-east-1" # Your Genesys Cloud region
}
3. Skill Module
Create a reusable module for managing skills:
# modules/skills/main.tf
variable "skills" {
type = list(object({
name = string
description = optional(string, "")
}))
description = "List of routing skills to create/manage."
}
resource "genesyscloud_routing_skill" "skills" {
for_each = { for s in var.skills : s.name => s }
name = each.value.name
description = each.value.description
}
output "skill_ids" {
value = { for k, v in genesyscloud_routing_skill.skills : k => v.id }
}
4. Queue Module
# modules/queues/main.tf
variable "queues" {
type = list(object({
name = string
description = optional(string, "")
divisionId = optional(string)
media_settings_voice_max_wait = optional(number, 300) # 5 minutes default
service_level_percentage = optional(number, 80) # 80% SLA target
service_level_duration_ms = optional(number, 30000) # 30 seconds
skill_evaluation_method = optional(string, "BEST")
auto_answer_only = optional(bool, false)
skill_ids = optional(list(string), [])
}))
}
resource "genesyscloud_routing_queue" "queues" {
for_each = { for q in var.queues : q.name => q }
name = each.value.name
description = each.value.description
media_settings_call {
alerting_timeout_sec = 30
service_level_percentage = each.value.service_level_percentage / 100
service_level_duration_ms = each.value.service_level_duration_ms
}
skill_evaluation_method = each.value.skill_evaluation_method
auto_answer_only = each.value.auto_answer_only
dynamic "routing_rules" {
for_each = each.value.skill_ids
content {
skill_ids_to_remove = [routing_rules.value]
wait_seconds = 60
}
}
}
output "queue_ids" {
value = { for k, v in genesyscloud_routing_queue.queues : k => v.id }
}
5. Root Configuration (Environment-Specific)
# environments/production/main.tf
module "skills" {
source = "../../modules/skills"
skills = [
{ name = "Billing", description = "Billing and payment inquiries" },
{ name = "TechnicalSupport", description = "Technical troubleshooting" },
{ name = "RetentionSpecialist", description = "Customer retention specialists" },
{ name = "Spanish", description = "Spanish language support" },
]
}
module "queues" {
source = "../../modules/queues"
queues = [
{
name = "Billing_Standard"
description = "Standard billing queue"
service_level_percentage = 80
service_level_duration_ms = 30000
skill_ids = [module.skills.skill_ids["Billing"]]
},
{
name = "Billing_Priority_VIP"
description = "VIP billing escalations"
service_level_percentage = 95
service_level_duration_ms = 15000
auto_answer_only = true
skill_ids = [module.skills.skill_ids["Billing"], module.skills.skill_ids["RetentionSpecialist"]]
},
{
name = "TechSupport_General"
description = "General technical support"
service_level_percentage = 75
service_level_duration_ms = 60000
skill_ids = [module.skills.skill_ids["TechnicalSupport"]]
},
]
depends_on = [module.skills]
}
6. The CI/CD Workflow
# .github/workflows/terraform-genesys.yml
on:
pull_request:
paths: ['environments/**', 'modules/**']
push:
branches: [main]
paths: ['environments/**', 'modules/**']
jobs:
plan:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Plan
run: |
terraform init
terraform plan -out=tfplan
working-directory: environments/production
env:
GENESYS_CLIENT_ID: ${{ secrets.GENESYS_CLIENT_ID }}
GENESYS_CLIENT_SECRET: ${{ secrets.GENESYS_CLIENT_SECRET }}
- name: Comment Plan on PR
uses: borchero/terraform-plan-comment@v1
with:
plan-file: environments/production/tfplan
apply:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Apply
run: |
terraform init
terraform apply -auto-approve
working-directory: environments/production
env:
GENESYS_CLIENT_ID: ${{ secrets.GENESYS_CLIENT_ID }}
GENESYS_CLIENT_SECRET: ${{ secrets.GENESYS_CLIENT_SECRET }}
Validation, Edge Cases & Troubleshooting
Edge Case 1: Existing Configuration Not in Terraform State
If you already have 50 queues in Genesys Cloud (created manually) and you start using Terraform, the first terraform apply will attempt to create duplicate queues-not manage the existing ones.
Solution: Use terraform import to import existing resources into the Terraform state before running terraform plan. Most Genesys Cloud Terraform resources support import. Example: terraform import module.queues.genesyscloud_routing_queue.queues["Billing_Standard"] <queue-id>.
Edge Case 2: Queue Deletion Removing Active Interactions
If a Terraform apply deletes a queue that currently has interactions waiting or agents logged in, Genesys Cloud may refuse the deletion or silently strand the interactions.
Solution: Add a lifecycle rule to prevent accidental deletion: lifecycle { prevent_destroy = true }. For intentional deletions, require manual removal of the prevent_destroy flag in a separate PR, giving the team time to verify the queue is empty before the apply.
Edge Case 3: Secret Rotation Breaking State Backend Access
If the AWS credentials used to access the S3 Terraform state backend are rotated, all pipeline runs fail until the secrets are updated in GitHub.
Solution: Use IAM Roles (not IAM user access keys) for the CI pipeline with GitHub Actions OIDC integration. IAM roles auto-rotate, eliminating long-lived credential management.