iac-terraform-pulumi · git:20260610.5ee9b17 · 2026-06-10 · sha256 a4adcb7a8998309e

iac-terraform-pulumi git:20260610.5ee9b17A

Immutable. This exact content never changes and is served at /api/v1/blob/a4adcb7a8998309e.

---
name: iac-terraform-pulumi
description: "Manages Control Plane resources with Terraform or Pulumi. Use when the user asks about the Terraform provider, Pulumi provider, infrastructure as code, IaC, state management, or declarative resource management."
---

# Infrastructure as Code — Terraform & Pulumi

## IaC Approaches

Control Plane supports three approaches for managing resources as code:

| Approach | Syntax | State Management | Best For |
|----------|--------|------------------|----------|
| **Terraform** | HCL | Terraform state file | Teams already using Terraform, HCL-native workflows |
| **Pulumi** | TypeScript, Python, Go, C# | Pulumi Service or self-managed backends | Teams preferring general-purpose languages |
| **`cpln apply`** | YAML/JSON manifests | No state file (API is source of truth) | GitOps pipelines, simple deployments, CI/CD automation |

### When to Use Each

- **Terraform/Pulumi**: Full lifecycle management with plan/preview, drift detection, dependency graphs, and state tracking. Use for production infrastructure managed by platform teams.
- **`cpln apply`**: Idempotent manifest application without state files. Use for GitOps workflows, CI/CD pipelines, and environments where simplicity is preferred. See the **cpln-gitops-cicd** skill for CI/CD patterns.

## Terraform Provider

### Registry

- **Source**: `controlplane-com/cpln`
- **Registry**: https://registry.terraform.io/providers/controlplane-com/cpln/latest
- **Requires**: Terraform 0.13+

### Provider Configuration

```hcl
terraform {
  required_providers {
    cpln = {
      source = "controlplane-com/cpln"
    }
  }
}

provider "cpln" {
  org = var.org  # Required. Env: CPLN_ORG
}
```

### Authentication

Configure via provider arguments or environment variables:

| Provider Arg | Env Var | Default | Required |
|--------------|---------|---------|----------|
| `org` | `CPLN_ORG` | None | Yes |
| `endpoint` | `CPLN_ENDPOINT` | `https://api.cpln.io` | No |
| `profile` | `CPLN_PROFILE` | Default CLI profile | No |
| `token` | `CPLN_TOKEN` | None | No |
| `refresh_token` | `CPLN_REFRESH_TOKEN` | None | No |

For CI/CD, use a service account token in `CPLN_TOKEN`. See the **cpln-gitops-cicd** skill for service account creation.

### Supported Terraform Resources

All resources registered by the `controlplane-com/cpln` provider. Data sources exist for `cloud_account`, `gvc`, `helm_template`, `image`, `images`, `location`, `locations`, `org`, `secret`, `workload`.

| Resource | Terraform Type |
|----------|----------------|
| Agent | `cpln_agent` |
| Audit Context | `cpln_audit_context` |
| Catalog Template | `cpln_catalog_template` |
| Cloud Account | `cpln_cloud_account` |
| Custom Location | `cpln_custom_location` |
| Domain | `cpln_domain` |
| Domain Route | `cpln_domain_route` |
| Group | `cpln_group` |
| GVC | `cpln_gvc` |
| Helm Release | `cpln_helm_release` |
| Identity | `cpln_identity` |
| IP Set | `cpln_ipset` |
| Location | `cpln_location` |
| Mk8s | `cpln_mk8s` |
| Mk8s Kubeconfig | `cpln_mk8s_kubeconfig` |
| Org | `cpln_org` |
| Org Logging | `cpln_org_logging` |
| Org Tracing | `cpln_org_tracing` |
| Policy | `cpln_policy` |
| Secret | `cpln_secret` (types: `aws`, `azure_connector`, `azure_sdk`, `dictionary`, `docker`, `ecr`, `gcp`, `keypair`, `nats_account`, `opaque`, `tls`, `userpass`) |
| Service Account | `cpln_service_account` |
| Service Account Key | `cpln_service_account_key` |
| Volume Set | `cpln_volume_set` |
| Workload | `cpln_workload` |

See the [Terraform Registry](https://registry.terraform.io/providers/controlplane-com/cpln/latest) for per-resource field reference and examples.

### Example: GVC + Workload

```hcl
resource "cpln_gvc" "main" {
  name        = "my-app-gvc"
  description = "Production GVC"

  locations = [
    "aws-us-west-2",
    "gcp-us-east1"
  ]
}

resource "cpln_workload" "app" {
  gvc  = cpln_gvc.main.name
  name = "my-app"
  type = "standard"

  container {
    name   = "main"
    image  = "my-org/my-app:latest"
    cpu    = "50m"
    memory = "128Mi"

    ports {
      number   = 8080
      protocol = "http"
    }
  }

  options {
    capacity_ai     = true
    timeout_seconds = 5

    autoscaling {
      metric    = "disabled"
      target    = 95
      min_scale = 1
      max_scale = 5
    }
  }

  firewall_spec {
    external {
      inbound_allow_cidr = ["0.0.0.0/0"]
    }
  }
}

output "canonical_endpoint" {
  value = cpln_workload.app.status[0].canonical_endpoint
}
```

### Example: Secret + Identity + Policy

`cpln_secret` has no `type` argument — set exactly one of the per-type blocks/attributes (`dictionary`, `opaque`, `tls`, `aws`, `gcp`, `docker`, `keypair`, etc.). `cpln_policy` uses a `binding` block (singular, up to 50 per policy).

```hcl
resource "cpln_secret" "db_credentials" {
  name = "db-credentials"

  dictionary = {
    DB_HOST     = "db.example.com"
    DB_USER     = "app"
    DB_PASSWORD = "secret-value"
  }
}

resource "cpln_identity" "app_identity" {
  gvc  = cpln_gvc.main.name
  name = "app-identity"
}

resource "cpln_policy" "secret_access" {
  name        = "app-secret-access"
  target_kind = "secret"
  target_links = [cpln_secret.db_credentials.name]

  binding {
    permissions     = ["reveal", "use"]
    principal_links = [cpln_identity.app_identity.self_link]
  }
}
```

### Example: Custom Domain + Route

```hcl
resource "cpln_domain" "apex" {
  name = "example.com"

  spec {
    dns_mode = "cname"

    ports {
      number   = 443
      protocol = "http2"
      tls {}
    }
  }
}

resource "cpln_domain" "app" {
  depends_on = [cpln_domain.apex]
  name       = "app.example.com"

  spec {
    dns_mode = "cname"

    ports {
      number   = 443
      protocol = "http2"
      tls {}
    }
  }
}

resource "cpln_domain_route" "route" {
  depends_on    = [cpln_domain.app]
  domain_link   = cpln_domain.app.self_link
  domain_port   = 443
  prefix        = "/"
  workload_link = cpln_workload.app.self_link
}
```

### Upgrading Provider Version

1. Update the version in your `required_providers` block.
2. Run `terraform init -upgrade`.

### Importing Existing Resources

Use `terraform import` to bring existing Control Plane resources under Terraform management. See the Terraform Registry for import syntax per resource type.

To generate both the HCL and the matching `import {}` blocks in one step, call `mcp__cpln__export_terraform` (or `mcp__cpln__export_terraform_batch` for several resources) with `generateImports` set — see [Scaffold from Existing Resources](#scaffold-from-existing-resources).

## Pulumi Provider

### Registry

- **Registry**: https://www.pulumi.com/registry/packages/cpln
- **Supported languages**: TypeScript/JavaScript, Python, Go, .NET (C#)

### Installation

| Language | Package | Install Command |
|----------|---------|-----------------|
| JS/TS | `@pulumiverse/cpln` | `npm install @pulumiverse/cpln` |
| Python | `pulumiverse-cpln` | `pip install pulumiverse-cpln` |
| Go | `github.com/pulumiverse/pulumi-cpln/sdk/go/cpln` | `go get github.com/pulumiverse/pulumi-cpln/sdk/go/cpln` |
| .NET | `Pulumiverse.Cpln` | `dotnet add package Pulumiverse.Cpln` |

### Provider Configuration

```bash
# Required
pulumi config set cpln:org <your-org>

# Optional
pulumi config set --secret cpln:token <your-token>
pulumi config set cpln:endpoint <api-endpoint-url>
pulumi config set cpln:profile <profile-name>
pulumi config set --secret cpln:refreshToken <your-refresh-token>
```

Uses the same environment variables as Terraform (`CPLN_ORG`, `CPLN_TOKEN`, etc.).

### Example: GVC (TypeScript)

```typescript
import * as cpln from '@pulumiverse/cpln';

const gvc = new cpln.Gvc('my-app', {
  name: 'my-app-gvc',
  locations: ['aws-us-west-2', 'gcp-us-east1'],
});
```

### Example: GVC (Python)

```python
import pulumiverse_cpln as cpln

gvc = cpln.Gvc("my-app",
    name="my-app-gvc",
    locations=["aws-us-west-2", "gcp-us-east1"],
)
```

### Example: GVC (Go)

```go
gvc, err := cpln.NewGvc(ctx, "my-app", &cpln.GvcArgs{
    Name:      pulumi.String("my-app-gvc"),
    Locations: pulumi.StringArray{
        pulumi.String("aws-us-west-2"),
        pulumi.String("gcp-us-east1"),
    },
})
```

### Migrating from Terraform to Pulumi

1. **Convert HCL**: `pulumi convert --from terraform --language typescript --out pulumi-cpln-infra`
2. **Create stack**: `pulumi stack init migrate`
3. **Import state**: `pulumi import --from terraform /path/to/terraform.tfstate`
4. **Review and deploy**: `pulumi up`

See the [Pulumi migration guide](https://www.pulumi.com/docs/iac/adopting-pulumi/migrating-to-pulumi/from-terraform) for details.

## Template Catalog with IaC

Install templates from the Control Plane catalog using Terraform or Pulumi. Templates bundle pre-configured resources (workloads, secrets, etc.) into deployable packages.

### Terraform

```hcl
resource "cpln_catalog_template" "postgres" {
  name     = "my-postgres"
  template = "postgres"
  version  = "1.0.0"
  gvc      = "my-gvc"
  values   = file("${path.module}/values.yaml")
}
```

### Pulumi (TypeScript)

```typescript
import * as cpln from '@pulumiverse/cpln';
import * as fs from 'fs';

const values = fs.readFileSync('values.yaml', 'utf8');

const release = new cpln.CatalogTemplate('postgres', {
  name: 'my-postgres',
  template: 'postgres',
  version: '1.0.0',
  gvc: 'my-gvc',
  values: values,
});
```

### Catalog Template Arguments

| Argument | Description |
|----------|-------------|
| `name` | Unique release name for this installation |
| `template` | Catalog template name (e.g., `postgres`, `cockroachdb`) |
| `version` | Template version to install |
| `gvc` | Target GVC (leave empty if the template creates its own) |
| `values` | YAML-formatted string with template configuration |

### Outputs

After applying, the resource exposes a `resources` attribute with all created Control Plane resources, each containing `kind`, `name`, and `link`.

### Upgrade

Update the `version` and/or `values.yaml`, then re-apply. Only affected workloads are redeployed.

## Best Practices

### State Management

- **Terraform**: Use remote backends (S3, GCS, Terraform Cloud) for team collaboration. Enable state locking.
- **Pulumi**: Use Pulumi Service (default) or self-managed backends. State is stored per stack.
- **Never commit state files** to version control — they may contain sensitive values.

### Secret Handling

- Use `CPLN_TOKEN` environment variable for authentication — never hardcode tokens in HCL/Pulumi code.
- For CI/CD, create a dedicated service account with minimal permissions — `mcp__cpln__add_key_to_service_account` creates the service account if needed and issues a key in one call; scope it with `mcp__cpln__create_policy`.
- Mark sensitive Pulumi config with `--secret`: `pulumi config set --secret cpln:token <token>`.
- Terraform marks secret values as sensitive automatically when using the `cpln_secret` resource.

### CI/CD Integration

Terraform and Pulumi integrate with CI/CD platforms. Example repos:

- **GitHub Actions (Terraform)**: https://github.com/controlplane-com/github-actions-example-terraform
- **GitLab CI (Terraform)**: https://gitlab.com/controlplane-com/gitlab-pipeline-example-terraform
- **Bitbucket Pipelines (Terraform)**: https://bitbucket.org/controlplane-com/bitbucket-pipeline-example-terraform

### Drift Detection

- **Terraform**: Run `terraform plan` periodically to detect drift between state and actual resources.
- **Pulumi**: Run `pulumi preview` to detect drift.
- Both tools show a diff of what would change, allowing you to reconcile manually or re-apply.

## Quick Reference

### Provider URLs

| Provider | URL |
|----------|-----|
| Terraform Registry | https://registry.terraform.io/providers/controlplane-com/cpln/latest |
| Pulumi Registry | https://www.pulumi.com/registry/packages/cpln |
| Terraform Examples | https://github.com/controlplane-com/examples/tree/main/terraform |

### Authentication Setup (Any Tool)

```bash
# Environment variables (shared across Terraform, Pulumi, and CLI)
export CPLN_ORG=my-org
export CPLN_TOKEN=<service-account-token>
```

### Scaffold from Existing Resources

To generate Terraform (HCL) for resources that already exist in Control Plane, prefer the MCP exporter — it produces merged, de-duplicated HCL and can emit `import {}` blocks:

- `mcp__cpln__export_terraform` — HCL for a single resource (`/org/acme/gvc/prod/workload/api`) or in bulk by path depth (`/org/acme` exports the whole org). Set `generateImports` to emit `import {}` blocks.
- `mcp__cpln__export_terraform_batch` — HCL for several self links in one call; prefer this over many `export_terraform` calls.
- `mcp__cpln__convert_to_terraform` — convert a manifest (YAML/JSON) you already have into HCL. The manifest is dry-run validated against the API first, so the returned HCL always corresponds to a schema-valid resource.
- `mcp__cpln__list_terraform_kinds` — list the resource kinds the exporter supports; call before `export_terraform` / `convert_to_terraform`.

When authoring a manifest by hand for `cpln apply`, call `mcp__cpln__get_resource_schema` first to get the exact object schema and REST endpoints for the kind.

**CLI fallback** (when the MCP server is unavailable, or as the primary interface in CI/CD): the `cpln` CLI can emit existing resources in multiple formats, useful for bootstrapping IaC or `cpln apply` workflows:

```bash
# YAML manifest (for cpln apply / K8s operator)
cpln gvc get my-gvc -o yaml-slim > gvc.yaml
cpln workload get my-app --gvc my-gvc -o yaml-slim > workload.yaml

# Terraform HCL scaffold for an existing resource
cpln workload get my-app --gvc my-gvc -o tf > workload.tf

# Apply manifests (idempotent). --file accepts a file or a directory (recurses YAML/JSON).
cpln apply --file ./manifests/
cpln apply --file ./manifests/ --ready   # block until workloads are ready
```

Supported `-o` formats: `text`, `json`, `yaml`, `json-slim`, `yaml-slim`, `tf`, `crd`, `names`.

### Related Skills

- **cpln-gitops-cicd** — CI/CD pipelines, service account auth, `cpln apply` in pipelines
- **cpln-k8s-operator** — Managing resources as Kubernetes CRDs with ArgoCD
- **cpln-template-catalog** — Template catalog overview, available templates, and values configuration

## Documentation

For the latest reference, see:

- [IaC Overview](https://docs.controlplane.com/iac/overview.md)
- [Terraform Provider](https://docs.controlplane.com/iac/terraform.md)
- [Pulumi Provider](https://docs.controlplane.com/iac/pulumi.md)
- [cpln apply Guide](https://docs.controlplane.com/guides/cpln-apply.md)
- [cpln convert Guide](https://docs.controlplane.com/guides/cli/cpln-convert.md)