best practices.md

Best Practices

Patterns for running the Barndoor Terraform provider in production, and the platform semantics worth knowing before you rely on it.

One owner per object

Give every Barndoor object exactly one owner: either Terraform or the portal, never both. If an admin edits a Terraform-managed policy in the app, the next terraform plan shows a diff and the next apply reverts their change — usually a surprise for both sides.

Practical rules:

Secrets and state hygiene

Pin the provider version

tf
terraform {
  required_providers {
    barndoor = {
      source  = "barndoor-ai/barndoor"
      version = "~> 0.3"
    }
  }
}

~> 0.3 accepts patch and minor updates within the 0.x line as they release. Check the changelog before widening the range.

Importing existing configuration

Every resource supports terraform import, so an organization configured through the portal can move to code incrementally. With Terraform 1.5+, prefer import blocks — they're reviewable and plannable:

import {
  to = barndoor_policy.search_read_only
  id = "6f1c9312-8f3e-4bde-9c2b-b3a1d1cf34a4"
}
tf
terraform plan -generate-config-out=generated.tf

Most resources import by their UUID, with these exceptions:

Resource Import ID
barndoor_connection The server's ID or slug (a server has at most one tenant-wide connection)
barndoor_log_export Your organization ID, or {organization_id}/{export_type}
barndoor_llm_model_pricing The rule's logical identity: {model_provider}|{model_pattern}, or a bare {model_pattern} for provider-unscoped rules
barndoor_dlp_org_config, barndoor_idp, barndoor_llm_governance_config Your organization ID (they're org singletons)

Two things to expect right after an import:

Destroy is not always delete

terraform destroy maps to whatever removal means for each object on the platform:

Resource What destroy actually does
barndoor_policy Archives the policy (terminal lifecycle state, kept for audit; never hard-deleted)
barndoor_llm_model_pricing Archives the rule with full version history; recreating the same rule restores it
barndoor_mcp_server Soft-deletes the server; its connections and stored credentials are torn down, the name is freed
barndoor_agent Unregisters the agent; the platform archives dependent policies
barndoor_log_export Removes the destination configuration; the platform-provisioned export slot survives
barndoor_dlp_org_config, barndoor_llm_governance_config Resets the org singleton to platform defaults; nothing is deleted
Everything else Deletes the object
Renames of immutable fields are replacements, and replacements are destroys. For example, `barndoor_policy.mcp_server_id` is immutable — changing it archives the existing policy and creates a new one. Read the plan output for `-/+` markers before applying.

Plans that look like drift but aren't

CI/CD with GitHub Actions

The standard shape — plan on pull requests, apply on merge to main:

name: terraform
on:
  pull_request:
  push:
    branches: [main]

env:
  BARNDOOR_BASE_URL: https://platform.barndoor.ai
  BARNDOOR_TOKEN_URL: https://auth.barndoor.ai/realms/barndoor/protocol/openid-connect/token
  BARNDOOR_CLIENT_ID: ${{ vars.BARNDOOR_CLIENT_ID }}
  BARNDOOR_ORGANIZATION_ID: ${{ vars.BARNDOOR_ORGANIZATION_ID }}
  BARNDOOR_CLIENT_SECRET: ${{ secrets.BARNDOOR_CLIENT_SECRET }}

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform plan -input=false
        if: github.event_name == 'pull_request'
      - run: terraform apply -input=false -auto-approve
        if: github.ref == 'refs/heads/main'

Notes:

Troubleshooting