Best Practices - Barndoor Documentation

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 {
    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 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}
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

Error: ... requires the organization admin role

The credential reached the API but was refused. Confirm organization_id matches the organization the credential was generated in (copy both from Settings → API Tokens), and that the credential hasn’t been revoked.

Token request failed: invalid_client or unauthorized_client

The token endpoint rejected the client ID/secret pair — the secret is wrong, was rotated out from under you, or the credential was revoked. Mint a fresh secret by rotating in Settings → API Tokens.

base_url must be the platform host root

base_url may not carry a path suffix; the provider appends each service’s API prefix itself. Use the bare origin.

409 Conflict on create

You’re creating something that already exists and has a uniqueness constraint — a duplicate policy name, a second rate limit on the same scope and traffic type, a second connection on a server. Either import the existing object or change the conflicting field.

Blocks vs. attribute syntax: 'Blocks of type X are not expected here'

Nested values in this provider are attributes, assigned with = — for example destination = { ... } on barndoor_log_export. Writing them as blocks (destination { ... }) is a syntax error.

Intermittent failures under heavy applies

Since v0.4.0 the provider retries transient failures (rate limiting, gateway errors, connection blips) with exponential backoff, so most blips never surface. If an apply still fails partway, re-run terraform apply — it converges from wherever it stopped.

Seeing the full API exchange

Run with TF_LOG=DEBUG — the provider logs request/response detail, including full error bodies, at debug level.