Skip to main content

Terraform Data Sources

Data sources allow you to query existing Kubiya Control Plane resources and use their attributes in your Terraform configurations.

Why Use Data Sources?

  • Reference Existing Resources: Look up resources created outside Terraform
  • Cross-Stack References: Reference resources from other Terraform configurations
  • Dynamic Configuration: Build configurations based on existing infrastructure
  • Read-Only Access: Query resources without managing them

Available Data Sources

All managed resources have corresponding data sources:
  • controlplane_environment
  • controlplane_project
  • controlplane_team
  • controlplane_agent
  • controlplane_skill
  • controlplane_policy

Data Source Arguments

All data sources require the resource id:
data "controlplane_<resource>" "<name>" {
  id = "<resource-id>"
}

Data Source Examples

controlplane_environment

Look up an existing environment by ID:
data "controlplane_environment" "production" {
  id = "env-xxxxx"
}

# Use the data source
output "production_env_name" {
  value = data.controlplane_environment.production.name
}

output "production_env_config" {
  value = data.controlplane_environment.production.configuration
}

# Reference in other resources
resource "controlplane_agent" "new_agent" {
  name     = "agent-in-${data.controlplane_environment.production.name}"
  model_id = "kubiya/claude-sonnet-4"
  runtime  = "claude_code"

  configuration = jsonencode({
    environment = data.controlplane_environment.production.name
  })
}
Exported Attributes:
  • id - Environment ID
  • name - Environment name
  • display_name - Display name
  • description - Environment description
  • tags - List of tags
  • configuration - Environment configuration (JSON)
  • execution_environment - Execution settings (JSON)
  • created_at - Creation timestamp
  • updated_at - Last update timestamp

controlplane_project

Look up an existing project:
data "controlplane_project" "ml_platform" {
  id = "project-xxxxx"
}

output "project_info" {
  value = {
    name = data.controlplane_project.ml_platform.name
    key  = data.controlplane_project.ml_platform.key
  }
}

# Use in agent configuration
resource "controlplane_agent" "ml_agent" {
  name     = "ml-agent"
  model_id = "kubiya/claude-sonnet-4"
  runtime  = "claude_code"

  configuration = jsonencode({
    project = data.controlplane_project.ml_platform.name
  })
}
Exported Attributes:
  • id - Project ID
  • name - Project name
  • key - Project key
  • description - Project description
  • goals - Project goals
  • visibility - Visibility setting
  • metadata - Project metadata (JSON)
  • created_at - Creation timestamp
  • updated_at - Last update timestamp

controlplane_team

Look up an existing team:
data "controlplane_team" "devops" {
  id = "team-xxxxx"
}

# Create agent in existing team
resource "controlplane_agent" "new_devops_agent" {
  name        = "new-devops-agent"
  description = "New agent for ${data.controlplane_team.devops.name}"
  model_id    = "kubiya/claude-sonnet-4"
  runtime     = data.controlplane_team.devops.runtime
  team_id     = data.controlplane_team.devops.id

  llm_config = jsonencode({
    temperature = 0.7
    max_tokens  = 4096
  })
}

output "team_runtime" {
  value = data.controlplane_team.devops.runtime
}
Exported Attributes:
  • id - Team ID
  • name - Team name
  • description - Team description
  • runtime - Runtime type (default or claude_code)
  • configuration - Team configuration (JSON)
  • capabilities - List of capabilities
  • created_at - Creation timestamp
  • updated_at - Last update timestamp

controlplane_agent

Look up an existing agent:
data "controlplane_agent" "existing" {
  id = "agent-xxxxx"
}

# Clone agent configuration
resource "controlplane_agent" "clone" {
  name        = "cloned-agent"
  description = "Clone of ${data.controlplane_agent.existing.name}"
  model_id    = data.controlplane_agent.existing.model_id
  runtime     = data.controlplane_agent.existing.runtime
  llm_config  = data.controlplane_agent.existing.llm_config

  # Modify some settings
  configuration = jsonencode({
    environment = "staging"
  })
}

output "agent_model" {
  value = data.controlplane_agent.existing.model_id
}

output "agent_capabilities" {
  value = data.controlplane_agent.existing.capabilities
}
Exported Attributes:
  • id - Agent ID
  • name - Agent name
  • description - Agent description
  • model_id - LLM model
  • runtime - Runtime type
  • team_id - Team ID
  • llm_config - LLM configuration (JSON)
  • configuration - Agent configuration (JSON)
  • capabilities - List of capabilities
  • status - Agent status
  • created_at - Creation timestamp
  • updated_at - Last update timestamp

controlplane_skill

Look up an existing skill:
data "controlplane_skill" "shell" {
  id = "skill-xxxxx"
}

output "skill_type" {
  value = data.controlplane_skill.shell.type
}

output "skill_config" {
  value = data.controlplane_skill.shell.configuration
}

# Reference in documentation
locals {
  available_commands = jsondecode(data.controlplane_skill.shell.configuration).allowed_commands
}

output "available_commands" {
  value = local.available_commands
}
Exported Attributes:
  • id - Skill ID
  • name - Skill name
  • description - Skill description
  • type - Skill type (shell, file_system, docker, custom)
  • enabled - Whether skill is enabled
  • configuration - Skill configuration (JSON)
  • created_at - Creation timestamp
  • updated_at - Last update timestamp

controlplane_policy

Look up an existing policy:
data "controlplane_policy" "security" {
  id = "policy-xxxxx"
}

output "policy_content" {
  value     = data.controlplane_policy.security.policy_content
  sensitive = true
}

output "policy_tags" {
  value = data.controlplane_policy.security.tags
}

# Reference policy in agent configuration
resource "controlplane_agent" "compliant_agent" {
  name     = "compliant-agent"
  model_id = "kubiya/claude-sonnet-4"
  runtime  = "claude_code"

  configuration = jsonencode({
    policies = [data.controlplane_policy.security.id]
  })
}
Exported Attributes:
  • id - Policy ID
  • name - Policy name
  • description - Policy description
  • enabled - Whether policy is enabled
  • policy_content - OPA Rego policy content
  • tags - List of tags
  • created_at - Creation timestamp
  • updated_at - Last update timestamp

Common Use Cases

Use Case 1: Cross-Stack References

Reference resources from a different Terraform state:
# In stack-a: Create base infrastructure
resource "controlplane_environment" "shared" {
  name = "shared-environment"
}

output "shared_env_id" {
  value = controlplane_environment.shared.id
}
# In stack-b: Reference base infrastructure
data "controlplane_environment" "shared" {
  id = var.shared_environment_id  # From stack-a output
}

resource "controlplane_agent" "app_agent" {
  name     = "app-agent"
  model_id = "kubiya/claude-sonnet-4"
  runtime  = "claude_code"

  configuration = jsonencode({
    environment = data.controlplane_environment.shared.name
  })
}

Use Case 2: Dynamic Agent Creation

Create agents based on existing team configuration:
# Look up all teams
data "controlplane_team" "devops" {
  id = var.devops_team_id
}

data "controlplane_team" "security" {
  id = var.security_team_id
}

# Create agents for each team
locals {
  teams = {
    devops   = data.controlplane_team.devops
    security = data.controlplane_team.security
  }
}

resource "controlplane_agent" "team_agents" {
  for_each = local.teams

  name     = "${each.key}-agent"
  model_id = "kubiya/claude-sonnet-4"
  runtime  = each.value.runtime
  team_id  = each.value.id

  llm_config = jsonencode({
    temperature = 0.7
    max_tokens  = 4096
  })
}

Use Case 3: Configuration Validation

Validate that resources exist before creating dependencies:
# Verify environment exists
data "controlplane_environment" "required" {
  id = var.environment_id
}

# Create resources only if environment exists
resource "controlplane_agent" "validated" {
  count = data.controlplane_environment.required.id != "" ? 1 : 0

  name     = "validated-agent"
  model_id = "kubiya/claude-sonnet-4"
  runtime  = "claude_code"

  configuration = jsonencode({
    environment = data.controlplane_environment.required.name
  })
}

Use Case 4: Import Existing Resources

Find existing resources to import into Terraform:
# Look up existing agent
data "controlplane_agent" "existing" {
  id = "agent-xxxxx"
}

# Verify it's the right agent
output "verify_agent" {
  value = {
    name    = data.controlplane_agent.existing.name
    model   = data.controlplane_agent.existing.model_id
    runtime = data.controlplane_agent.existing.runtime
  }
}

# After verification, import it
# terraform import controlplane_agent.managed agent-xxxxx

# Then manage it with Terraform
resource "controlplane_agent" "managed" {
  name     = data.controlplane_agent.existing.name
  model_id = data.controlplane_agent.existing.model_id
  runtime  = data.controlplane_agent.existing.runtime
  # ... rest of configuration
}

Use Case 5: Clone Configurations

Clone existing resources with modifications:
# Look up production agent
data "controlplane_agent" "production" {
  id = var.production_agent_id
}

# Create staging clone with same config
resource "controlplane_agent" "staging" {
  name        = "staging-${data.controlplane_agent.production.name}"
  description = "Staging clone of production agent"
  model_id    = data.controlplane_agent.production.model_id
  runtime     = data.controlplane_agent.production.runtime
  llm_config  = data.controlplane_agent.production.llm_config

  # Override environment-specific settings
  configuration = jsonencode(
    merge(
      jsondecode(data.controlplane_agent.production.configuration),
      {
        environment = "staging"
        timeout     = 300  # Shorter timeout for staging
      }
    )
  )
}

Working with JSON Attributes

Many data source attributes return JSON strings. Use jsondecode() to parse them:
data "controlplane_environment" "prod" {
  id = "env-xxxxx"
}

locals {
  env_config = jsondecode(data.controlplane_environment.prod.configuration)
  max_workers = local.env_config.max_workers
  region = local.env_config.region
}

output "environment_details" {
  value = {
    max_workers = local.max_workers
    region      = local.region
  }
}

Best Practices

1. Use Variables for IDs

Don’t hardcode resource IDs:
# Good
variable "production_env_id" {
  description = "Production environment ID"
  type        = string
}

data "controlplane_environment" "prod" {
  id = var.production_env_id
}

# Avoid
data "controlplane_environment" "prod" {
  id = "env-12345"  # Hardcoded
}

2. Validate Data Source Results

Check that required data exists:
data "controlplane_team" "required" {
  id = var.team_id
}

# Fail if team doesn't exist
resource "null_resource" "validate_team" {
  triggers = {
    team_exists = data.controlplane_team.required.id
  }

  lifecycle {
    precondition {
      condition     = data.controlplane_team.required.id != ""
      error_message = "Required team does not exist"
    }
  }
}

3. Document Data Source Usage

Add clear comments:
# Look up existing production environment
# Created manually or by base infrastructure stack
data "controlplane_environment" "production" {
  id = var.production_env_id
}

4. Use Outputs for Debugging

Output data source attributes for troubleshooting:
output "debug_environment" {
  value = {
    id     = data.controlplane_environment.prod.id
    name   = data.controlplane_environment.prod.name
    config = data.controlplane_environment.prod.configuration
  }
}

Error Handling

If a data source can’t find a resource:
Error: Cannot find resource with ID: agent-xxxxx
Solutions:
  1. Verify the resource ID is correct
  2. Check that the resource exists in your control plane
  3. Verify your API key has read permissions
  4. Ensure you’re connected to the correct control plane (hosted vs self-hosted)

Next Steps