Handle the Genesys Cloud User Resource Schema Breaking Change in Terraform v1.35.0

Handle the Genesys Cloud User Resource Schema Breaking Change in Terraform v1.35.0

What You Will Build

  • A Terraform configuration that successfully provisions a Genesys Cloud user with the updated schema introduced in provider version 1.35.0.
  • This tutorial uses the HashiCorp Terraform provider for Genesys Cloud (genesyscloud).
  • The implementation focuses on HCL (HashiCorp Configuration Language) and the underlying API contract changes.

Prerequisites

  • Terraform Version: 1.0.0 or higher.
  • Genesys Cloud Provider: Version 1.35.0 or higher.
  • Authentication: A valid OAuth client ID and client secret with the admin:user scope.
  • Environment Variables: GENESYS_CLOUD_REGION, GENESYS_CLOUD_CLIENT_ID, GENESYS_CLOUD_CLIENT_SECRET.
  • Understanding: Familiarity with Terraform state management and the genesyscloud_user resource arguments.

Authentication Setup

The Genesys Cloud Terraform provider handles OAuth token acquisition automatically when provided with the correct environment variables or provider block configuration. You must ensure your client has the admin:user scope to create and modify users.

In your shell or CI/CD pipeline, export the following variables:

export GENESYS_CLOUD_REGION="mycompany.mygen.com"
export GENESYS_CLOUD_CLIENT_ID="your-client-id"
export GENESYS_CLOUD_CLIENT_SECRET="your-client-secret"

In your main.tf, initialize the provider:

terraform {
  required_providers {
    genesyscloud = {
      source  = "mikesparr/genesyscloud"
      version = ">= 1.35.0"
    }
  }
}

provider "genesyscloud" {
  # The provider reads the environment variables automatically.
  # No additional configuration is needed if env vars are set.
}

Implementation

Step 1: Understand the Schema Breaking Change

In provider versions prior to 1.35.0, certain user attributes were handled differently or were optional. Starting with v1.35.0, the genesyscloud_user resource schema was updated to align more strictly with the Genesys Cloud API v2 definitions. The most significant breaking change involves the routing_email_address and routing_phone_numbers structures, as well as the deprecation of legacy field names.

Specifically, the provider now enforces that if you define routing_email_address, it must conform to the strict object structure. Additionally, the user_type argument behavior has been refined. If you are migrating from an older version, you must update your HCL to match the new required fields and remove deprecated ones.

The key changes include:

  1. routing_email_address: Must now be a block with address and enabled fields.
  2. routing_phone_numbers: Must be a block list with number, type, and enabled fields.
  3. division_id: Is now strictly required for most user types unless the user is a system user.

Step 2: Construct the New User Resource

Below is the correct HCL syntax for a standard agent user in v1.35.0. Note the structure of the routing_phone_numbers and routing_email_address blocks.

resource "genesyscloud_user" "example_user" {
  name         = "John Doe"
  email        = "john.doe@example.com"
  username     = "john.doe"
  user_type    = "AGENT"
  
  # Division ID is required. You must look this up or define it.
  division_id  = var.default_division_id

  # Breaking Change: routing_email_address is now a block
  routing_email_address {
    address = "john.doe@example.com"
    enabled = true
  }

  # Breaking Change: routing_phone_numbers is now a list of blocks
  routing_phone_numbers {
    number = "+15551234567"
    type   = "WORK"
    enabled = true
  }

  # Password must be set for interactive users
  password = "ComplexPassword123!"
}

If you were using the old syntax where routing_email_address was a simple string, this will fail with a schema validation error. You must refactor all user resources to use the block structure.

Step 3: Handle Division ID Resolution

The division_id is a mandatory field in the new schema. You cannot use a hardcoded ID if you want your infrastructure to be portable across environments (e.g., Dev vs. Prod). You must resolve the division ID dynamically.

Create a data source to look up the default division:

data "genesyscloud_division" "default" {
  name = "Default"
}

variable "default_division_id" {
  default = null
}

# Use the data source if the variable is not explicitly set
locals {
  resolved_division_id = var.default_division_id != null ? var.default_division_id : data.genesyscloud_division.default.id
}

Then update the user resource to use the local value:

resource "genesyscloud_user" "example_user" {
  name         = "John Doe"
  email        = "john.doe@example.com"
  username     = "john.doe"
  user_type    = "AGENT"
  
  division_id  = local.resolved_division_id

  routing_email_address {
    address = "john.doe@example.com"
    enabled = true
  }

  routing_phone_numbers {
    number = "+15551234567"
    type   = "WORK"
    enabled = true
  }

  password = "ComplexPassword123!"
}

Complete Working Example

The following is a complete, copy-pasteable main.tf file that demonstrates the correct usage of the genesyscloud_user resource in provider version 1.35.0. It includes error handling for division lookup and proper structure for routing attributes.

terraform {
  required_providers {
    genesyscloud = {
      source  = "mikesparr/genesyscloud"
      version = ">= 1.35.0"
    }
  }
}

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

# Data source to resolve the Default Division ID
data "genesyscloud_division" "default" {
  name = "Default"
}

# Variable to allow override of division ID
variable "user_division_id" {
  description = "The division ID for the user. Defaults to the 'Default' division."
  type        = string
  default     = null
}

locals {
  # Use provided division ID or fall back to the Default division
  target_division_id = var.user_division_id != null ? var.user_division_id : data.genesyscloud_division.default.id
  
  # User details
  user_name    = "Terraform Test User"
  user_email   = "terraform.test.user@example.com"
  user_username = "terraform.test.user"
  user_password = "SecureP@ssw0rd123!"
  user_phone   = "+15559876543"
}

# Create the user with the new v1.35.0 schema
resource "genesyscloud_user" "test_user" {
  name         = local.user_name
  email        = local.user_email
  username     = local.user_username
  user_type    = "AGENT"
  division_id  = local.target_division_id

  # New Schema: routing_email_address as a block
  routing_email_address {
    address = local.user_email
    enabled = true
  }

  # New Schema: routing_phone_numbers as a list of blocks
  routing_phone_numbers {
    number  = local.user_phone
    type    = "WORK"
    enabled = true
  }

  # Set the password
  password = local.user_password

  # Optional: Set language
  languages {
    language_code = "en-US"
  }

  # Optional: Set skills if needed
  # skills {
  #   skill_id = var.some_skill_id
  # }
}

# Output the user ID for reference
output "user_id" {
  value       = genesyscloud_user.test_user.id
  description = "The ID of the created user."
}

output "user_division_id" {
  value       = local.target_division_id
  description = "The division ID used for the user."
}

Common Errors & Debugging

Error: Error: Invalid attribute name or Missing required argument

What causes it:
You are using the old schema syntax. For example, you might have routing_email_address = "user@example.com" as a string argument instead of a block. Or you omitted the division_id.

How to fix it:
Refactor the routing_email_address and routing_phone_numbers into block structures as shown in the Complete Working Example. Ensure division_id is present.

# INCORRECT (Old Schema)
resource "genesyscloud_user" "old" {
  name = "Old User"
  routing_email_address = "old@example.com" # String - Invalid in v1.35.0
}

# CORRECT (New Schema)
resource "genesyscloud_user" "new" {
  name = "New User"
  division_id = local.target_division_id
  routing_email_address {
    address = "new@example.com"
    enabled = true
  }
}

Error: Error: Division not found

What causes it:
The division_id provided does not exist in the target Genesys Cloud environment. This often happens when copying state from Dev to Prod without adjusting the division ID.

How to fix it:
Use the data "genesyscloud_division" source to dynamically resolve the division ID based on name, rather than hardcoding the ID.

data "genesyscloud_division" "by_name" {
  name = "Support Division"
}

resource "genesyscloud_user" "support_user" {
  # ...
  division_id = data.genesyscloud_division.by_name.id
}

Error: Error: 409 Conflict - User with this email already exists

What causes it:
Genesys Cloud enforces unique email addresses across the organization. If the user already exists in the UI, Terraform cannot create it.

How to fix it:
If the user already exists, you must import it into Terraform state instead of creating it. Run the following command:

terraform import genesyscloud_user.test_user <user_id>

Replace <user_id> with the actual ID of the user in Genesys Cloud. After importing, ensure your HCL definition matches the current state of the user to avoid drift on the next apply.

Error: Error: 400 Bad Request - Invalid phone number format

What causes it:
The routing_phone_numbers.number field must be in E.164 format (e.g., +15551234567). If you provide a format like (555) 123-4567, the API will reject it.

How to fix it:
Ensure the phone number string starts with + followed by the country code and number, with no spaces or dashes.

routing_phone_numbers {
  number = "+15551234567" # Correct
  type   = "WORK"
  enabled = true
}

Official References