Atlantis Integration
DriftWise integrates with Atlantis via a run step in your workflow. No plugins, no sidecars.
Setup
Add the DriftWise step after plan in your atlantis.yaml. The analyze
endpoint enqueues the job and returns 202 immediately — poll
GET /llm-jobs/{job_id} until status is done (or error):
atlantis.yaml
workflows:
default:
plan:
steps:
- init
- plan
- run: |
terraform show -json $PLANFILE > /tmp/dw-plan.json
JOB_ID=$(curl -sfX POST https://api.driftwise.ai/api/v2/orgs/$ORG_ID/analyze \
-H "x-api-key: $DRIFTWISE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"plan_json\": $(cat /tmp/dw-plan.json | jq -Rs .)}" | jq -r '.job_id')
for i in $(seq 1 36); do
BODY=$(curl -sf https://api.driftwise.ai/api/v2/orgs/$ORG_ID/llm-jobs/$JOB_ID \
-H "x-api-key: $DRIFTWISE_API_KEY")
STATUS=$(echo "$BODY" | jq -r '.status')
if [ "$STATUS" = "done" ]; then RESULT=$(echo "$BODY" | jq '.result'); break; fi
if [ "$STATUS" = "error" ]; then echo "analysis failed: $(echo "$BODY" | jq -r '.error')" >&2; exit 1; fi
sleep 5
done
[ "$STATUS" = "done" ] || { echo "timed out waiting for analysis" >&2; exit 1; }
echo "$RESULT" | jq -r '.narrative'
Why the conversion step?
Atlantis's $PLANFILE is a binary Terraform plan. The terraform show -json step converts it to JSON, then jq -Rs wraps it as an escaped JSON string inside the plan_json field that the API expects.
Environment Variables
Set these in your Atlantis server environment:
| Variable | Description |
|---|---|
DRIFTWISE_API_KEY | Your DriftWise API key (dw2_...) |
ORG_ID | Your DriftWise organization ID (UUID) |
How It Works
- Atlantis runs
terraform planas usual - The
runstep converts the binary plan to JSON and sends it to DriftWise - DriftWise enqueues the analysis and returns a job handle; the
runstep pollsllm-jobs/{job_id}until the risk score and narrative are ready - Atlantis includes the output in the plan result shown on the PR
Optional: CI Metadata
You can pass CI context so DriftWise can link the analysis back to the PR:
- run: |
terraform show -json $PLANFILE > /tmp/dw-plan.json
JOB_ID=$(curl -sfX POST https://api.driftwise.ai/api/v2/orgs/$ORG_ID/analyze \
-H "x-api-key: $DRIFTWISE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"plan_json\": $(cat /tmp/dw-plan.json | jq -Rs .),
\"ci\": {
\"repo_owner\": \"$BASE_REPO_OWNER\",
\"repo_name\": \"$BASE_REPO_NAME\",
\"pr_number\": $PULL_NUM,
\"commit_sha\": \"$HEAD_COMMIT\",
\"branch\": \"$HEAD_BRANCH_NAME\"
}
}" | jq -r '.job_id')
for i in $(seq 1 36); do
BODY=$(curl -sf https://api.driftwise.ai/api/v2/orgs/$ORG_ID/llm-jobs/$JOB_ID \
-H "x-api-key: $DRIFTWISE_API_KEY")
STATUS=$(echo "$BODY" | jq -r '.status')
if [ "$STATUS" = "done" ]; then RESULT=$(echo "$BODY" | jq '.result'); break; fi
if [ "$STATUS" = "error" ]; then echo "analysis failed: $(echo "$BODY" | jq -r '.error')" >&2; exit 1; fi
sleep 5
done
[ "$STATUS" = "done" ] || { echo "timed out waiting for analysis" >&2; exit 1; }
echo "$RESULT" | jq -r '.narrative'