grada-run 0.0.2 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy-docs.yml +37 -0
- package/.github/workflows/iac-validation.yml +303 -0
- package/.github/workflows/publish.yml +68 -0
- package/.github/workflows/sync-bedrock-models.yml +57 -0
- package/.github/workflows/test.yml +43 -0
- package/.muserules +31 -0
- package/LICENSE +21 -0
- package/README.md +193 -3
- package/apps/docs/.astro/collections/docs.schema.json +644 -0
- package/apps/docs/.astro/content-assets.mjs +4 -0
- package/apps/docs/.astro/content-modules.mjs +4 -0
- package/apps/docs/.astro/content.d.ts +179 -0
- package/apps/docs/.astro/data-store.json +1 -0
- package/apps/docs/.astro/dev.json +14 -0
- package/apps/docs/.astro/settings.json +5 -0
- package/apps/docs/.astro/types.d.ts +2 -0
- package/apps/docs/astro.config.mjs +97 -0
- package/apps/docs/package.json +17 -0
- package/apps/docs/src/content/docs/adrs/0001-s3-native-state-locking.md +37 -0
- package/apps/docs/src/content/docs/adrs/0002-eject-mechanism-pure-iac.md +39 -0
- package/apps/docs/src/content/docs/adrs/0003-sync-ai-context-strategy.md +48 -0
- package/apps/docs/src/content/docs/adrs/0004-iac-driven-diagnostic-context.md +37 -0
- package/apps/docs/src/content/docs/adrs/0005-ecs-fargate-alb-runtime-target.md +38 -0
- package/apps/docs/src/content/docs/adrs/0006-github-oidc-no-stored-keys.md +37 -0
- package/apps/docs/src/content/docs/adrs/0007-framework-detection-with-fallback.md +37 -0
- package/apps/docs/src/content/docs/adrs/0008-secrets-names-in-git-values-in-aws.md +37 -0
- package/apps/docs/src/content/docs/adrs/0009-regenerate-with-backup-on-rerun.md +37 -0
- package/apps/docs/src/content/docs/adrs/0010-advisory-only-security-scans.md +37 -0
- package/apps/docs/src/content/docs/cli/add.md +84 -0
- package/apps/docs/src/content/docs/cli/apply.md +32 -0
- package/apps/docs/src/content/docs/cli/db.md +200 -0
- package/apps/docs/src/content/docs/cli/destroy.md +31 -0
- package/apps/docs/src/content/docs/cli/diagnose.md +37 -0
- package/apps/docs/src/content/docs/cli/doctor.md +28 -0
- package/apps/docs/src/content/docs/cli/domain.md +57 -0
- package/apps/docs/src/content/docs/cli/drift.md +40 -0
- package/apps/docs/src/content/docs/cli/eject.md +29 -0
- package/apps/docs/src/content/docs/cli/exec.md +49 -0
- package/apps/docs/src/content/docs/cli/gc.md +37 -0
- package/apps/docs/src/content/docs/cli/init.md +72 -0
- package/apps/docs/src/content/docs/cli/logs.md +39 -0
- package/apps/docs/src/content/docs/cli/rollback.md +51 -0
- package/apps/docs/src/content/docs/cli/secrets.md +73 -0
- package/apps/docs/src/content/docs/cli/sleep.md +53 -0
- package/apps/docs/src/content/docs/cli/status.md +34 -0
- package/apps/docs/src/content/docs/cli/sync-ai.md +27 -0
- package/apps/docs/src/content/docs/guides/architecture.md +87 -0
- package/apps/docs/src/content/docs/guides/aws-credentials.md +72 -0
- package/apps/docs/src/content/docs/guides/background-workers.md +45 -0
- package/apps/docs/src/content/docs/guides/cicd-pipeline.md +64 -0
- package/apps/docs/src/content/docs/guides/database-connections.md +64 -0
- package/apps/docs/src/content/docs/guides/docker-compose.md +37 -0
- package/apps/docs/src/content/docs/guides/dockerfiles.md +46 -0
- package/apps/docs/src/content/docs/guides/ephemeral-pr-previews.md +39 -0
- package/apps/docs/src/content/docs/guides/examples.md +50 -0
- package/apps/docs/src/content/docs/guides/frameworks.md +88 -0
- package/apps/docs/src/content/docs/guides/headless.md +75 -0
- package/apps/docs/src/content/docs/guides/quickstart.md +52 -0
- package/apps/docs/src/content/docs/guides/rerun-init.md +43 -0
- package/apps/docs/src/content/docs/guides/secrets-management.md +83 -0
- package/apps/docs/src/content/docs/guides/understanding-your-bill.md +63 -0
- package/apps/docs/src/content/docs/index.mdx +103 -0
- package/apps/docs/src/content/docs/migrations/astro-vercel-to-aws.md +55 -0
- package/apps/docs/src/content/docs/migrations/heroku-procfile-to-aws.md +41 -0
- package/apps/docs/src/content/docs/migrations/nextjs-vercel-to-aws.md +51 -0
- package/apps/docs/src/content/docs/migrations/sveltekit-vercel-to-aws.md +63 -0
- package/apps/docs/src/content/docs/roadmap.md +97 -0
- package/apps/docs/src/content/docs/testing-strategy.md +32 -0
- package/apps/docs/src/content.config.ts +7 -0
- package/apps/docs/src/custom.css +14 -0
- package/apps/docs/tsconfig.json +6 -0
- package/bin/cli.js +140 -0
- package/package.json +105 -7
- package/scripts/sync-bedrock-models.js +22 -0
- package/scripts/test-iac.js +261 -0
- package/specs/add-redis-sqs-bedrock.md +128 -0
- package/specs/add-storage-dynamodb.md +106 -0
- package/specs/bedrock-model-catalog.md +131 -0
- package/specs/ci-pipeline.md +17 -0
- package/specs/cost-transparency.md +115 -0
- package/specs/custom-domains-and-ses.md +153 -0
- package/specs/database-suite-expansion.md +151 -0
- package/specs/db-connect.md +69 -0
- package/specs/db-lifecycle-migrations.md +159 -0
- package/specs/dependency-aware-init.md +176 -0
- package/specs/deploy-stack-to-grada-run-rebrand.md +45 -0
- package/specs/deployment-safety.md +170 -0
- package/specs/diagnose.md +16 -0
- package/specs/docs-hub.md +16 -0
- package/specs/dx-polish.md +46 -0
- package/specs/exec.md +25 -0
- package/specs/finops-cron-drift.md +161 -0
- package/specs/gc.md +26 -0
- package/specs/integration-suite.md +16 -0
- package/specs/logs.md +32 -0
- package/specs/rollback-live-polling.md +40 -0
- package/specs/secrets-pull-audit.md +51 -0
- package/specs/serverless-lambda-target.md +133 -0
- package/specs/status.md +31 -0
- package/specs/telemetry-and-spawn-hardening.md +69 -0
- package/specs/telemetry-hardening.md +35 -0
- package/src/commands/add.js +1111 -0
- package/src/commands/apply.js +214 -0
- package/src/commands/db/backup.js +229 -0
- package/src/commands/db/connect.js +304 -0
- package/src/commands/db/enable-vector.js +344 -0
- package/src/commands/db/import.js +604 -0
- package/src/commands/db/migrate.js +477 -0
- package/src/commands/db/restore.js +361 -0
- package/src/commands/db.js +87 -0
- package/src/commands/destroy.js +217 -0
- package/src/commands/diagnose.js +460 -0
- package/src/commands/doctor.js +109 -0
- package/src/commands/domain.js +685 -0
- package/src/commands/drift.js +243 -0
- package/src/commands/eject.js +127 -0
- package/src/commands/exec.js +222 -0
- package/src/commands/gc.js +250 -0
- package/src/commands/init.js +649 -0
- package/src/commands/logs.js +256 -0
- package/src/commands/rollback.js +323 -0
- package/src/commands/secrets.js +485 -0
- package/src/commands/sleep.js +347 -0
- package/src/commands/status.js +309 -0
- package/src/commands/sync-ai.js +115 -0
- package/src/commands/wake.js +337 -0
- package/src/core/parser.js +126 -0
- package/src/core/telemetry.js +244 -0
- package/src/data/bedrock-models.json +896 -0
- package/src/utils/addons.js +126 -0
- package/src/utils/ai-rules.js +59 -0
- package/src/utils/args.js +91 -0
- package/src/utils/aws.js +178 -0
- package/src/utils/backup.js +69 -0
- package/src/utils/bedrock-catalog.js +511 -0
- package/src/utils/capabilities.js +500 -0
- package/src/utils/command.js +65 -0
- package/src/utils/db-tunnel.js +164 -0
- package/src/utils/detector.js +298 -0
- package/src/utils/dockerCompose.js +65 -0
- package/src/utils/domains.js +73 -0
- package/src/utils/ecs-runner.js +289 -0
- package/src/utils/ecs.js +92 -0
- package/src/utils/frameworks.js +55 -0
- package/src/utils/generator.js +527 -0
- package/src/utils/hcl.js +426 -0
- package/src/utils/lambda-ecr.js +185 -0
- package/src/utils/prompts.js +278 -0
- package/src/utils/rds.js +131 -0
- package/src/utils/resolvers.js +174 -0
- package/src/utils/sleep-state.js +140 -0
- package/src/utils/sleep-targets.js +139 -0
- package/src/utils/system.js +42 -0
- package/src/utils/terraform.js +70 -0
- package/src/utils/visualizer.js +381 -0
- package/src/utils/warnings.js +49 -0
- package/templates/README.md +150 -0
- package/templates/docker/django.Dockerfile +40 -0
- package/templates/docker/go.Dockerfile +23 -0
- package/templates/docker/nestjs.Dockerfile +33 -0
- package/templates/docker/nextjs.Dockerfile +55 -0
- package/templates/docker/node.Dockerfile +24 -0
- package/templates/docker/nuxt.Dockerfile +47 -0
- package/templates/docker/python.Dockerfile +38 -0
- package/templates/docker/rails.Dockerfile +59 -0
- package/templates/docker/static.Dockerfile +32 -0
- package/templates/docker/svelte.Dockerfile +52 -0
- package/templates/github/deploy-lambda.yml +120 -0
- package/templates/github/deploy.yml +138 -0
- package/templates/github/drift.yml +112 -0
- package/templates/github/preview-lambda.yml +86 -0
- package/templates/github/preview.yml +69 -0
- package/templates/github/teardown.yml +43 -0
- package/templates/terraform/addons/bedrock.tf +34 -0
- package/templates/terraform/addons/cron-lambda.tf +78 -0
- package/templates/terraform/addons/cron.tf +101 -0
- package/templates/terraform/addons/dynamodb.tf +73 -0
- package/templates/terraform/addons/redis.tf +64 -0
- package/templates/terraform/addons/s3.tf +143 -0
- package/templates/terraform/addons/ses.tf +73 -0
- package/templates/terraform/addons/sqs.tf +67 -0
- package/templates/terraform/backend.tf +22 -0
- package/templates/terraform/cloudfront-lambda.tf +80 -0
- package/templates/terraform/cloudfront.tf +80 -0
- package/templates/terraform/database-aurora-postgresql.tf +92 -0
- package/templates/terraform/database-mysql.tf +72 -0
- package/templates/terraform/database.tf +71 -0
- package/templates/terraform/main-lambda.tf +229 -0
- package/templates/terraform/main.tf +296 -0
- package/templates/terraform/network.tf +95 -0
- package/templates/terraform/oidc.tf +64 -0
- package/templates/terraform/secrets.tf +31 -0
- package/templates/terraform/worker.tf +69 -0
- package/tests/__snapshots__/generator.test.js.snap +9633 -0
- package/tests/add.test.js +2037 -0
- package/tests/ai.test.js +94 -0
- package/tests/apply.test.js +488 -0
- package/tests/args.test.js +86 -0
- package/tests/aws.test.js +244 -0
- package/tests/capabilities.test.js +307 -0
- package/tests/cli.test.js +29 -0
- package/tests/command.test.js +100 -0
- package/tests/commands-import.test.js +74 -0
- package/tests/db.test.js +2704 -0
- package/tests/destroy.test.js +391 -0
- package/tests/detector.test.js +79 -0
- package/tests/diagnose.test.js +779 -0
- package/tests/doctor.test.js +202 -0
- package/tests/domain.test.js +899 -0
- package/tests/drift.test.js +243 -0
- package/tests/ecs.test.js +130 -0
- package/tests/eject.test.js +65 -0
- package/tests/exec.test.js +380 -0
- package/tests/gc.test.js +496 -0
- package/tests/generator.test.js +794 -0
- package/tests/headless.test.js +562 -0
- package/tests/lambda-ecr.test.js +185 -0
- package/tests/logs.test.js +447 -0
- package/tests/parser.test.js +160 -0
- package/tests/rds.test.js +244 -0
- package/tests/resolvers.test.js +282 -0
- package/tests/rollback.test.js +692 -0
- package/tests/secrets.test.js +752 -0
- package/tests/sleep-wake.test.js +1016 -0
- package/tests/status.test.js +370 -0
- package/tests/system.test.js +70 -0
- package/tests/telemetry.test.js +520 -0
- package/tests/terraform.test.js +84 -0
- package/tests/visualizer.test.js +496 -0
- package/vitest.config.js +9 -0
- package/index.js +0 -2
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: IaC Drift Detection
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 6 * * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
AWS_REGION: {{REGION}}
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
contents: read
|
|
14
|
+
issues: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
drift:
|
|
18
|
+
name: Detect Terraform drift
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout Code
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Configure AWS Credentials
|
|
26
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
27
|
+
with:
|
|
28
|
+
role-to-assume: {{ROLE_ARN}}
|
|
29
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
30
|
+
|
|
31
|
+
- name: Setup Terraform
|
|
32
|
+
uses: hashicorp/setup-terraform@v3
|
|
33
|
+
with:
|
|
34
|
+
# The wrapper mangles -detailed-exitcode; raw codes (0/1/2) are the signal.
|
|
35
|
+
terraform_wrapper: false
|
|
36
|
+
|
|
37
|
+
- name: Terraform Init
|
|
38
|
+
working-directory: ./terraform
|
|
39
|
+
run: terraform init -input=false
|
|
40
|
+
|
|
41
|
+
# -detailed-exitcode: 0 = no changes, 1 = error, 2 = drift detected.
|
|
42
|
+
- name: Terraform Plan
|
|
43
|
+
id: plan
|
|
44
|
+
working-directory: ./terraform
|
|
45
|
+
run: |
|
|
46
|
+
set +e
|
|
47
|
+
terraform plan -detailed-exitcode -no-color -out=tfplan > plan-full.txt 2>&1
|
|
48
|
+
EXITCODE=$?
|
|
49
|
+
echo "exitcode=$EXITCODE" >> "$GITHUB_OUTPUT"
|
|
50
|
+
if [ "$EXITCODE" != "1" ]; then
|
|
51
|
+
terraform show -no-color tfplan > plan.txt
|
|
52
|
+
else
|
|
53
|
+
cp plan-full.txt plan.txt
|
|
54
|
+
fi
|
|
55
|
+
echo "### Drift plan (exit $EXITCODE)" >> "$GITHUB_STEP_SUMMARY"
|
|
56
|
+
head -50 plan.txt >> "$GITHUB_STEP_SUMMARY"
|
|
57
|
+
|
|
58
|
+
- name: Report drift as GitHub Issue
|
|
59
|
+
if: steps.plan.outputs.exitcode == '2' || steps.plan.outputs.exitcode == '0'
|
|
60
|
+
uses: actions/github-script@v7
|
|
61
|
+
with:
|
|
62
|
+
script: |
|
|
63
|
+
const fs = require('fs');
|
|
64
|
+
const { owner, repo } = context.repo;
|
|
65
|
+
const exitcode = '${{ steps.plan.outputs.exitcode }}';
|
|
66
|
+
const label = 'iac-drift';
|
|
67
|
+
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${context.runId}`;
|
|
68
|
+
let diff = '';
|
|
69
|
+
try {
|
|
70
|
+
diff = fs.readFileSync('terraform/plan.txt', 'utf8');
|
|
71
|
+
} catch (error) {
|
|
72
|
+
diff = '(plan output unavailable)';
|
|
73
|
+
}
|
|
74
|
+
if (diff.length > 30000) {
|
|
75
|
+
diff = `${diff.slice(0, 30000)}\n... (truncated, see ${runUrl})`;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
await github.rest.issues.getLabel({ owner, repo, name: label });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.status === 404) {
|
|
81
|
+
await github.rest.issues.createLabel({ owner, repo, name: label, color: 'd93f0b', description: 'Out-of-band AWS infrastructure changes detected by Terraform' });
|
|
82
|
+
} else {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const { data: open } = await github.rest.issues.listForRepo({ owner, repo, state: 'open', labels: label });
|
|
87
|
+
const existing = open[0];
|
|
88
|
+
if (exitcode === '2') {
|
|
89
|
+
const body = `## ⚠️ Infrastructure Drift Detected in AWS\n\nLive AWS state differs from Terraform on \`${new Date().toISOString()}\` ([workflow run](${runUrl})).\n\n\`\`\`terraform\n${diff}\n\`\`\`\n\nReconcile with \`npx grada-run apply\`, or adopt the live resource with \`terraform import\`.`;
|
|
90
|
+
if (existing) {
|
|
91
|
+
await github.rest.issues.createComment({ owner, repo, issue_number: existing.number, body: `Drift still present on \`${new Date().toISOString()}\` ([run](${runUrl})).\n\n\`\`\`terraform\n${diff}\n\`\`\`` });
|
|
92
|
+
await github.rest.issues.update({ owner, repo, issue_number: existing.number, body });
|
|
93
|
+
} else {
|
|
94
|
+
await github.rest.issues.create({ owner, repo, title: '⚠️ Infrastructure Drift Detected in AWS', labels: [label], body });
|
|
95
|
+
}
|
|
96
|
+
} else if (existing) {
|
|
97
|
+
await github.rest.issues.createComment({ owner, repo, issue_number: existing.number, body: `✅ Drift resolved on \`${new Date().toISOString()}\` ([run](${runUrl})).` });
|
|
98
|
+
await github.rest.issues.update({ owner, repo, issue_number: existing.number, state: 'closed' });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
- name: Notify Slack
|
|
102
|
+
if: steps.plan.outputs.exitcode == '2' && secrets.SLACK_WEBHOOK_URL != ''
|
|
103
|
+
env:
|
|
104
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
105
|
+
run: |
|
|
106
|
+
SUMMARY=$(head -c 1500 terraform/plan.txt)
|
|
107
|
+
jq -n \
|
|
108
|
+
--arg repo "$GITHUB_REPOSITORY" \
|
|
109
|
+
--arg url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
|
|
110
|
+
--arg summary "$SUMMARY" \
|
|
111
|
+
'{text: (":warning: Infrastructure drift detected in " + $repo + "\n" + $url + "\n```" + $summary + "```")}' > /tmp/drift-slack.json
|
|
112
|
+
curl -sS -X POST -H 'Content-Type: application/json' -d @/tmp/drift-slack.json "$SLACK_WEBHOOK_URL"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: Ephemeral PR Preview
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
AWS_REGION: {{REGION}}
|
|
9
|
+
ECR_REPOSITORY: {{PROJECT_NAME}}-repo
|
|
10
|
+
WORKSPACE: pr-${{ github.event.pull_request.number }}
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: read
|
|
15
|
+
pull-requests: write # Required to post the comment
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy-preview:
|
|
19
|
+
name: Provision Preview Environment
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout Code
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Configure AWS credentials
|
|
27
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
28
|
+
with:
|
|
29
|
+
role-to-assume: arn:aws:iam::{{AWS_ACCOUNT_ID}}:role/{{PROJECT_NAME}}-github-actions-role
|
|
30
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
31
|
+
|
|
32
|
+
- name: Login to Amazon ECR
|
|
33
|
+
id: login-ecr
|
|
34
|
+
uses: aws-actions/amazon-ecr-login@v2
|
|
35
|
+
|
|
36
|
+
- name: Build and Push Docker image
|
|
37
|
+
env:
|
|
38
|
+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
39
|
+
run: |
|
|
40
|
+
# We tag the image specifically for this PR workspace
|
|
41
|
+
docker build --platform linux/amd64 -t $ECR_REGISTRY/$ECR_REPOSITORY:${{ env.WORKSPACE }} .
|
|
42
|
+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:${{ env.WORKSPACE }}
|
|
43
|
+
|
|
44
|
+
- name: Setup Terraform
|
|
45
|
+
uses: hashicorp/setup-terraform@v3
|
|
46
|
+
|
|
47
|
+
- name: Terraform Init & Apply
|
|
48
|
+
id: tf-apply
|
|
49
|
+
working-directory: ./terraform
|
|
50
|
+
run: |
|
|
51
|
+
terraform init
|
|
52
|
+
# Create or select the workspace specific to this PR
|
|
53
|
+
terraform workspace select -or-create ${{ env.WORKSPACE }}
|
|
54
|
+
terraform apply -auto-approve
|
|
55
|
+
|
|
56
|
+
- name: Deploy PR image to the preview function
|
|
57
|
+
env:
|
|
58
|
+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
59
|
+
run: |
|
|
60
|
+
# Terraform provisions the function pointing at :latest (which it
|
|
61
|
+
# then ignores), so the PR-tagged image is deployed explicitly —
|
|
62
|
+
# mirroring the workspace-tagged task definition on ECS targets.
|
|
63
|
+
aws lambda update-function-code \
|
|
64
|
+
--function-name {{PROJECT_NAME}}-${{ env.WORKSPACE }}-fn \
|
|
65
|
+
--image-uri $ECR_REGISTRY/$ECR_REPOSITORY:${{ env.WORKSPACE }} \
|
|
66
|
+
--region ${{ env.AWS_REGION }}
|
|
67
|
+
aws lambda wait function-updated \
|
|
68
|
+
--function-name {{PROJECT_NAME}}-${{ env.WORKSPACE }}-fn \
|
|
69
|
+
--region ${{ env.AWS_REGION }}
|
|
70
|
+
|
|
71
|
+
- name: Read preview URL
|
|
72
|
+
working-directory: ./terraform
|
|
73
|
+
run: |
|
|
74
|
+
API_URL=$(terraform output -raw api_gateway_url)
|
|
75
|
+
echo "PREVIEW_URL=$API_URL" >> $GITHUB_ENV
|
|
76
|
+
|
|
77
|
+
- name: Comment PR
|
|
78
|
+
uses: actions/github-script@v7
|
|
79
|
+
with:
|
|
80
|
+
script: |
|
|
81
|
+
github.rest.issues.createComment({
|
|
82
|
+
issue_number: context.issue.number,
|
|
83
|
+
owner: context.repo.owner,
|
|
84
|
+
repo: context.repo.repo,
|
|
85
|
+
body: `🚀 **Preview Environment Deployed!**\n\nYour ephemeral AWS Lambda environment is live at:\n[${process.env.PREVIEW_URL}](${process.env.PREVIEW_URL})\n\n_This environment will be automatically destroyed when the PR is merged or closed._\n\n---\n<div align="right"><sup>☁️ Generated by <a href="https://github.com/grada-run/grada">Grada</a></sup></div>`
|
|
86
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Ephemeral PR Preview
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
AWS_REGION: {{REGION}}
|
|
9
|
+
ECR_REPOSITORY: {{PROJECT_NAME}}-repo
|
|
10
|
+
WORKSPACE: pr-${{ github.event.pull_request.number }}
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: read
|
|
15
|
+
pull-requests: write # Required to post the comment
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy-preview:
|
|
19
|
+
name: Provision Preview Environment
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout Code
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Configure AWS credentials
|
|
27
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
28
|
+
with:
|
|
29
|
+
role-to-assume: arn:aws:iam::{{AWS_ACCOUNT_ID}}:role/{{PROJECT_NAME}}-github-actions-role
|
|
30
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
31
|
+
|
|
32
|
+
- name: Login to Amazon ECR
|
|
33
|
+
id: login-ecr
|
|
34
|
+
uses: aws-actions/amazon-ecr-login@v2
|
|
35
|
+
|
|
36
|
+
- name: Build and Push Docker image
|
|
37
|
+
env:
|
|
38
|
+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
39
|
+
run: |
|
|
40
|
+
# We tag the image specifically for this PR workspace
|
|
41
|
+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:${{ env.WORKSPACE }} .
|
|
42
|
+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:${{ env.WORKSPACE }}
|
|
43
|
+
|
|
44
|
+
- name: Setup Terraform
|
|
45
|
+
uses: hashicorp/setup-terraform@v3
|
|
46
|
+
|
|
47
|
+
- name: Terraform Init & Apply
|
|
48
|
+
id: tf-apply
|
|
49
|
+
working-directory: ./terraform
|
|
50
|
+
run: |
|
|
51
|
+
terraform init
|
|
52
|
+
# Create or select the workspace specific to this PR
|
|
53
|
+
terraform workspace select -or-create ${{ env.WORKSPACE }}
|
|
54
|
+
terraform apply -auto-approve
|
|
55
|
+
|
|
56
|
+
# Extract the ALB URL to post to GitHub
|
|
57
|
+
ALB_URL=$(terraform output -raw alb_direct_url)
|
|
58
|
+
echo "PREVIEW_URL=$ALB_URL" >> $GITHUB_ENV
|
|
59
|
+
|
|
60
|
+
- name: Comment PR
|
|
61
|
+
uses: actions/github-script@v7
|
|
62
|
+
with:
|
|
63
|
+
script: |
|
|
64
|
+
github.rest.issues.createComment({
|
|
65
|
+
issue_number: context.issue.number,
|
|
66
|
+
owner: context.repo.owner,
|
|
67
|
+
repo: context.repo.repo,
|
|
68
|
+
body: `🚀 **Preview Environment Deployed!**\n\nYour ephemeral AWS Fargate environment is live at:\n[${process.env.PREVIEW_URL}](${process.env.PREVIEW_URL})\n\n_This environment will be automatically destroyed when the PR is merged or closed._\n\n---\n<div align="right"><sup>☁️ Generated by <a href="https://github.com/grada-run/grada">Grada</a></sup></div>`
|
|
69
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Destroy PR Preview
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
AWS_REGION: {{REGION}}
|
|
9
|
+
WORKSPACE: pr-${{ github.event.pull_request.number }}
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
destroy-preview:
|
|
17
|
+
name: Teardown Preview Environment
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout Code
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Configure AWS credentials
|
|
25
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
26
|
+
with:
|
|
27
|
+
role-to-assume: arn:aws:iam::{{AWS_ACCOUNT_ID}}:role/{{PROJECT_NAME}}-github-actions-role
|
|
28
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
29
|
+
|
|
30
|
+
- name: Setup Terraform
|
|
31
|
+
uses: hashicorp/setup-terraform@v3
|
|
32
|
+
|
|
33
|
+
- name: Terraform Init & Destroy
|
|
34
|
+
working-directory: ./terraform
|
|
35
|
+
run: |
|
|
36
|
+
terraform init
|
|
37
|
+
# Select the PR workspace
|
|
38
|
+
terraform workspace select ${{ env.WORKSPACE }}
|
|
39
|
+
# Destroy all resources in this workspace safely
|
|
40
|
+
terraform destroy -auto-approve
|
|
41
|
+
# Switch back to default and delete the PR workspace
|
|
42
|
+
terraform workspace select default
|
|
43
|
+
terraform workspace delete ${{ env.WORKSPACE }}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# grada addon: ai:bedrock
|
|
2
|
+
# Generated by `grada add ai:bedrock`. Do not edit by hand unless
|
|
3
|
+
# you know what you are doing — re-running with --force overwrites this file.
|
|
4
|
+
#
|
|
5
|
+
# Billing: no fixed hourly instance cost (usage-billed per 1K input/output
|
|
6
|
+
# tokens on InvokeModel calls). Remember to enable model access for your
|
|
7
|
+
# chosen model in the AWS Bedrock console first.
|
|
8
|
+
|
|
9
|
+
# --- Least-Privilege IAM Policy for the ECS Task Role ---
|
|
10
|
+
resource "aws_iam_role_policy" "ai_bedrock_access" {
|
|
11
|
+
name = "${local.app_name}-ai-bedrock-access"
|
|
12
|
+
role = aws_iam_role.task_role.id
|
|
13
|
+
policy = jsonencode({
|
|
14
|
+
Version = "2012-10-17"
|
|
15
|
+
Statement = [
|
|
16
|
+
{
|
|
17
|
+
Effect = "Allow"
|
|
18
|
+
Action = [
|
|
19
|
+
"bedrock:InvokeModel",
|
|
20
|
+
"bedrock:InvokeModelWithResponseStream"
|
|
21
|
+
]
|
|
22
|
+
Resource = [
|
|
23
|
+
"arn:aws:bedrock:*::foundation-model/*",
|
|
24
|
+
"arn:aws:bedrock:*:*:inference-profile/*"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# --- Outputs ---
|
|
32
|
+
output "bedrock_model_id" {
|
|
33
|
+
value = "{{BEDROCK_MODEL_ID}}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# grada addon: cron (Lambda target)
|
|
2
|
+
# Generated by `grada add cron` on a --target lambda project. Do not
|
|
3
|
+
# edit by hand unless you know what you are doing — re-running with --force
|
|
4
|
+
# overwrites this file.
|
|
5
|
+
#
|
|
6
|
+
# Billing: $0/mo fixed baseline (first 14M EventBridge Scheduler
|
|
7
|
+
# invocations/mo free); billed only for Lambda invocations while the job runs.
|
|
8
|
+
#
|
|
9
|
+
# NOTE: unlike the ECS variant, the scheduler invokes the web function
|
|
10
|
+
# directly instead of running a container command. Handle the scheduled event
|
|
11
|
+
# in application code (the payload below carries the configured command), or
|
|
12
|
+
# point the schedule at a dedicated function.
|
|
13
|
+
|
|
14
|
+
# --- IAM Role assumed by EventBridge Scheduler ---
|
|
15
|
+
resource "aws_iam_role" "scheduler_cron_role" {
|
|
16
|
+
name = "${local.app_name}-cron-scheduler-role"
|
|
17
|
+
assume_role_policy = jsonencode({
|
|
18
|
+
Version = "2012-10-17"
|
|
19
|
+
Statement = [
|
|
20
|
+
{
|
|
21
|
+
Effect = "Allow"
|
|
22
|
+
Principal = {
|
|
23
|
+
Service = "scheduler.amazonaws.com"
|
|
24
|
+
}
|
|
25
|
+
Action = "sts:AssumeRole"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# --- Least-privilege permission to invoke the function ---
|
|
32
|
+
resource "aws_iam_role_policy" "scheduler_cron_policy" {
|
|
33
|
+
name = "${local.app_name}-cron-scheduler-policy"
|
|
34
|
+
role = aws_iam_role.scheduler_cron_role.id
|
|
35
|
+
policy = jsonencode({
|
|
36
|
+
Version = "2012-10-17"
|
|
37
|
+
Statement = [
|
|
38
|
+
{
|
|
39
|
+
Effect = "Allow"
|
|
40
|
+
Action = ["lambda:InvokeFunction"]
|
|
41
|
+
Resource = [
|
|
42
|
+
aws_lambda_function.app.arn,
|
|
43
|
+
"${aws_lambda_function.app.arn}:*"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# --- Scheduled invocation (single schedule per project) ---
|
|
51
|
+
resource "aws_scheduler_schedule" "cron" {
|
|
52
|
+
name = "${local.app_name}-cron-{{CRON_NAME}}"
|
|
53
|
+
schedule_expression = "{{SCHEDULE_EXPRESSION}}"
|
|
54
|
+
schedule_expression_timezone = "{{SCHEDULE_TIMEZONE}}"
|
|
55
|
+
|
|
56
|
+
flexible_time_window {
|
|
57
|
+
mode = "OFF"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
target {
|
|
61
|
+
arn = aws_lambda_function.app.arn
|
|
62
|
+
role_arn = aws_iam_role.scheduler_cron_role.arn
|
|
63
|
+
|
|
64
|
+
input = jsonencode({
|
|
65
|
+
source = "grada-cron"
|
|
66
|
+
command = {{CRON_COMMAND_JSON}}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# --- Outputs ---
|
|
72
|
+
output "cron_schedule_name" {
|
|
73
|
+
value = aws_scheduler_schedule.cron.name
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
output "cron_schedule_arn" {
|
|
77
|
+
value = aws_scheduler_schedule.cron.arn
|
|
78
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# grada addon: cron
|
|
2
|
+
# Generated by `grada add cron`. Do not edit by hand unless
|
|
3
|
+
# you know what you are doing — re-running with --force overwrites this file.
|
|
4
|
+
#
|
|
5
|
+
# Billing: $0/mo fixed baseline (first 14M EventBridge Scheduler
|
|
6
|
+
# invocations/mo free); billed only for Fargate seconds while the task runs.
|
|
7
|
+
|
|
8
|
+
# --- IAM Role assumed by EventBridge Scheduler ---
|
|
9
|
+
resource "aws_iam_role" "scheduler_cron_role" {
|
|
10
|
+
name = "${local.app_name}-cron-scheduler-role"
|
|
11
|
+
assume_role_policy = jsonencode({
|
|
12
|
+
Version = "2012-10-17"
|
|
13
|
+
Statement = [
|
|
14
|
+
{
|
|
15
|
+
Effect = "Allow"
|
|
16
|
+
Principal = {
|
|
17
|
+
Service = "scheduler.amazonaws.com"
|
|
18
|
+
}
|
|
19
|
+
Action = "sts:AssumeRole"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# --- Least-privilege permission to launch the scheduled task ---
|
|
26
|
+
resource "aws_iam_role_policy" "scheduler_cron_policy" {
|
|
27
|
+
name = "${local.app_name}-cron-scheduler-policy"
|
|
28
|
+
role = aws_iam_role.scheduler_cron_role.id
|
|
29
|
+
policy = jsonencode({
|
|
30
|
+
Version = "2012-10-17"
|
|
31
|
+
Statement = [
|
|
32
|
+
{
|
|
33
|
+
Effect = "Allow"
|
|
34
|
+
Action = ["ecs:RunTask"]
|
|
35
|
+
Resource = [
|
|
36
|
+
aws_ecs_task_definition.app.arn,
|
|
37
|
+
"${aws_ecs_task_definition.app.arn_without_revision}:*"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
Effect = "Allow"
|
|
42
|
+
Action = ["iam:PassRole"]
|
|
43
|
+
Resource = [
|
|
44
|
+
aws_iam_role.execution_role.arn,
|
|
45
|
+
aws_iam_role.task_role.arn
|
|
46
|
+
]
|
|
47
|
+
Condition = {
|
|
48
|
+
StringLike = {
|
|
49
|
+
"iam:PassedToService" = "ecs-tasks.amazonaws.com"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# --- Scheduled task (single schedule per project) ---
|
|
58
|
+
resource "aws_scheduler_schedule" "cron" {
|
|
59
|
+
name = "${local.app_name}-cron-{{CRON_NAME}}"
|
|
60
|
+
schedule_expression = "{{SCHEDULE_EXPRESSION}}"
|
|
61
|
+
schedule_expression_timezone = "{{SCHEDULE_TIMEZONE}}"
|
|
62
|
+
|
|
63
|
+
flexible_time_window {
|
|
64
|
+
mode = "OFF"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
target {
|
|
68
|
+
arn = aws_ecs_cluster.main.arn
|
|
69
|
+
role_arn = aws_iam_role.scheduler_cron_role.arn
|
|
70
|
+
|
|
71
|
+
ecs_parameters {
|
|
72
|
+
task_definition_arn = aws_ecs_task_definition.app.arn
|
|
73
|
+
launch_type = "FARGATE"
|
|
74
|
+
task_count = 1
|
|
75
|
+
|
|
76
|
+
network_configuration {
|
|
77
|
+
subnets = aws_subnet.public[*].id
|
|
78
|
+
security_groups = [aws_security_group.ecs_tasks.id]
|
|
79
|
+
assign_public_ip = true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
input = jsonencode({
|
|
84
|
+
containerOverrides = [
|
|
85
|
+
{
|
|
86
|
+
name = "${local.app_name}-container"
|
|
87
|
+
command = ["sh", "-c", {{CRON_COMMAND_JSON}}]
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# --- Outputs ---
|
|
95
|
+
output "cron_schedule_name" {
|
|
96
|
+
value = aws_scheduler_schedule.cron.name
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
output "cron_schedule_arn" {
|
|
100
|
+
value = aws_scheduler_schedule.cron.arn
|
|
101
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# grada addon: db:dynamodb
|
|
2
|
+
# Generated by `grada add db:dynamodb`. Do not edit by hand unless
|
|
3
|
+
# you know what you are doing — re-running with --force overwrites this file.
|
|
4
|
+
|
|
5
|
+
# --- Scale-to-Zero DynamoDB Table ---
|
|
6
|
+
resource "aws_dynamodb_table" "main" {
|
|
7
|
+
name = "${local.app_name}-table"
|
|
8
|
+
billing_mode = "PAY_PER_REQUEST"
|
|
9
|
+
hash_key = "{{PARTITION_KEY}}"
|
|
10
|
+
|
|
11
|
+
attribute {
|
|
12
|
+
name = "{{PARTITION_KEY}}"
|
|
13
|
+
type = "S"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
point_in_time_recovery {
|
|
17
|
+
enabled = true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
server_side_encryption {
|
|
21
|
+
enabled = true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# --- VPC Gateway Endpoint (keeps traffic on the AWS network) ---
|
|
26
|
+
# The endpoint itself carries no hourly or data-processing charge. The table
|
|
27
|
+
# above has no fixed hourly instance cost (usage-billed): table storage
|
|
28
|
+
# ($0.25/GB-mo), read/write requests, and PITR continuous backups
|
|
29
|
+
# ($0.20/GB-mo once data is written) are billed by usage.
|
|
30
|
+
resource "aws_vpc_endpoint" "dynamodb" {
|
|
31
|
+
vpc_id = aws_vpc.main.id
|
|
32
|
+
service_name = "com.amazonaws.{{REGION}}.dynamodb"
|
|
33
|
+
vpc_endpoint_type = "Gateway"
|
|
34
|
+
route_table_ids = [aws_route_table.public.id]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# --- Least-Privilege IAM Policy for the ECS Task Role ---
|
|
38
|
+
resource "aws_iam_role_policy" "dynamodb_access" {
|
|
39
|
+
name = "${local.app_name}-dynamodb-access"
|
|
40
|
+
role = aws_iam_role.task_role.id
|
|
41
|
+
policy = jsonencode({
|
|
42
|
+
Version = "2012-10-17"
|
|
43
|
+
Statement = [
|
|
44
|
+
{
|
|
45
|
+
Effect = "Allow"
|
|
46
|
+
Action = [
|
|
47
|
+
"dynamodb:GetItem",
|
|
48
|
+
"dynamodb:PutItem",
|
|
49
|
+
"dynamodb:UpdateItem",
|
|
50
|
+
"dynamodb:DeleteItem",
|
|
51
|
+
"dynamodb:Query",
|
|
52
|
+
"dynamodb:Scan",
|
|
53
|
+
"dynamodb:BatchGetItem",
|
|
54
|
+
"dynamodb:BatchWriteItem",
|
|
55
|
+
"dynamodb:DescribeTable"
|
|
56
|
+
]
|
|
57
|
+
Resource = [
|
|
58
|
+
aws_dynamodb_table.main.arn,
|
|
59
|
+
"${aws_dynamodb_table.main.arn}/index/*"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# --- Outputs ---
|
|
67
|
+
output "dynamodb_table_name" {
|
|
68
|
+
value = aws_dynamodb_table.main.name
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
output "dynamodb_table_arn" {
|
|
72
|
+
value = aws_dynamodb_table.main.arn
|
|
73
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# grada addon: db:redis
|
|
2
|
+
# Generated by `grada add db:redis`. Do not edit by hand unless
|
|
3
|
+
# you know what you are doing — re-running with --force overwrites this file.
|
|
4
|
+
#
|
|
5
|
+
# Billing: fixed ~$9.49/mo baseline (cache.t4g.micro Valkey 8.0 at $0.013/hr
|
|
6
|
+
# in us-east-2); $0 intra-AZ VPC data transfer.
|
|
7
|
+
|
|
8
|
+
# --- ElastiCache Subnet Group ---
|
|
9
|
+
resource "aws_elasticache_subnet_group" "redis" {
|
|
10
|
+
name = "${local.app_name}-redis-subnets"
|
|
11
|
+
subnet_ids = aws_subnet.public[*].id
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# --- Redis Security Group (ECS tasks only) ---
|
|
15
|
+
resource "aws_security_group" "redis" {
|
|
16
|
+
name = "${local.app_name}-redis-sg"
|
|
17
|
+
description = "Allow Redis/Valkey traffic strictly from ECS tasks"
|
|
18
|
+
vpc_id = aws_vpc.main.id
|
|
19
|
+
|
|
20
|
+
ingress {
|
|
21
|
+
from_port = 6379
|
|
22
|
+
to_port = 6379
|
|
23
|
+
protocol = "tcp"
|
|
24
|
+
security_groups = [aws_security_group.ecs_tasks.id]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
egress {
|
|
28
|
+
from_port = 0
|
|
29
|
+
to_port = 0
|
|
30
|
+
protocol = "-1"
|
|
31
|
+
# trivy:ignore:AVD-AWS-0104 - Allow cache nodes to reach standard AWS services
|
|
32
|
+
cidr_blocks = ["0.0.0.0/0"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# --- ElastiCache for Valkey Replication Group ---
|
|
37
|
+
# Valkey requires aws_elasticache_replication_group even for a single node:
|
|
38
|
+
# the aws_elasticache_cluster API rejects engine = "valkey".
|
|
39
|
+
resource "aws_elasticache_replication_group" "redis" {
|
|
40
|
+
# replication_group_id allows at most 40 chars and must start with a letter.
|
|
41
|
+
# The ds- prefix guarantees a leading letter and the md5 suffix keeps
|
|
42
|
+
# default and PR-preview workspaces unique.
|
|
43
|
+
replication_group_id = "ds-${trim(substr(replace(lower(local.app_name), "/[^a-z0-9-]+/", "-"), 0, 5), "-")}-${substr(md5(local.app_name), 0, 5)}-redis"
|
|
44
|
+
description = "Valkey cache for ${local.app_name}"
|
|
45
|
+
engine = "valkey"
|
|
46
|
+
engine_version = "8.0"
|
|
47
|
+
node_type = "cache.t4g.micro"
|
|
48
|
+
num_cache_clusters = 1
|
|
49
|
+
parameter_group_name = "default.valkey8"
|
|
50
|
+
port = 6379
|
|
51
|
+
subnet_group_name = aws_elasticache_subnet_group.redis.name
|
|
52
|
+
security_group_ids = [aws_security_group.redis.id]
|
|
53
|
+
automatic_failover_enabled = false
|
|
54
|
+
tags = { Name = "${local.app_name}-redis" }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# --- Outputs ---
|
|
58
|
+
output "redis_endpoint" {
|
|
59
|
+
value = "redis://${aws_elasticache_replication_group.redis.primary_endpoint_address}:${aws_elasticache_replication_group.redis.port}"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
output "redis_port" {
|
|
63
|
+
value = aws_elasticache_replication_group.redis.port
|
|
64
|
+
}
|