Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# NDX:Try AWS Scenarios — environment variables
# Copy to .env (which is gitignored) and fill in for local work.

# -----------------------------------------------------------------------------
# Smoke-pack (scripts/smoke.sh / .github/workflows/smoke.yml)
# -----------------------------------------------------------------------------

# CFN stack name to read outputs from. Default is "all-demo" (the umbrella
# nested stack). For scoped local runs against a single scenario, set to that
# scenario's stack name.
SMOKE_STACK_NAME=all-demo

# us-east-1 per CLAUDE.md and the spec. The smoke account does not deploy
# scenarios anywhere else.
SMOKE_AWS_REGION=us-east-1

# Local only: SSO profile for the smoke-test AWS account. Unset on CI; the
# OIDC role is exported by aws-actions/configure-aws-credentials.
# Account-specific values are committed in docs/smoke-test-account-config.yml.
SMOKE_AWS_PROFILE=NDX/SmokeTest

# Optional: webhook to receive smoke-failure notifications. If unset, CI
# falls back to opening a GitHub issue tagged `smoke-failed` on nightly cron
# failures only (PR-scoped failures already surface via the PR check).
# SMOKE_FAILURE_WEBHOOK=https://...

# -----------------------------------------------------------------------------
# Portal site (existing) — see playwright.config.ts
# -----------------------------------------------------------------------------

# BASE_URL=http://localhost:8080
30 changes: 30 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Smoke-pack sensitive paths (Phase 3 / T3.10).
#
# Changes to any of these files require review by the listed owners. Because
# the smoke-test-deploy GitHub deployment environment is configured with a
# branch policy that auto-approves refs/heads/main only, a PR modifying any
# of these paths cannot run with deploy credentials until a CODEOWNERS
# reviewer approves the environment (defeats the in-repo-contributor
# exfiltration attack).
#
# Owners are deliberately not personal handles in this file; replace with
# the relevant GH team(s) when the team is provisioned. Until then the
# repo's branch protection (which already requires review on every PR via
# the standard reviewer set) is the operative gate.

# Smoke workflow + entrypoint + fixtures
/.github/workflows/smoke.yml @co-cddo/ndx-try-maintainers
/scripts/smoke.sh @co-cddo/ndx-try-maintainers
/tests/smoke/fixtures/** @co-cddo/ndx-try-maintainers

# Smoke account state (runbook + config)
/docs/smoke-test-account-setup.md @co-cddo/ndx-try-maintainers
/docs/smoke-test-account-config.yml @co-cddo/ndx-try-maintainers

# Renovate adoption (Phase 6 — guarded so a Renovate config drift cannot
# self-merge via its own PR flow)
/renovate.json @co-cddo/ndx-try-maintainers
/.github/workflows/renovate.yml @co-cddo/ndx-try-maintainers

# Retention-policy lint (Phase 2b — guarded against accidental loosening)
/scripts/lint-retention-policies.sh @co-cddo/ndx-try-maintainers
241 changes: 240 additions & 1 deletion .github/workflows/deploy-blueprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ on:
- 'cloudformation/scenarios/fixmystreet/cdk/**'
- 'cloudformation/scenarios/paperless-ngx/cdk/**'
- 'cloudformation/scenarios/ai-contact-centre/**'
- 'cloudformation/scenarios/planx/cdk/**'
- 'cloudformation/scenarios/bops-planning/cdk/**'
- 'cloudformation/scenarios/digital-planning-register/cdk/**'
- 'cloudformation/isb-hub/**'
workflow_dispatch:

Expand Down Expand Up @@ -402,8 +405,194 @@ jobs:
path: cloudformation/scenarios/paperless-ngx/template.yaml
retention-days: 1

synth-planx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: '22'

- name: Build and synth planx CDK
working-directory: cloudformation/scenarios/planx/cdk
run: |
npm install
npm run build
npx cdk synth

- name: Strip bootstrap cruft and validate template
working-directory: cloudformation/scenarios/planx/cdk
run: |
node -e "
const fs = require('fs');
const t = JSON.parse(fs.readFileSync('cdk.out/PlanxStack.template.json', 'utf8'));
delete t.Parameters?.BootstrapVersion;
delete t.Resources?.CDKMetadata;
delete t.Rules?.CheckBootstrapVersion;
const str = JSON.stringify(t);
const errors = [];
if (str.includes('AssetParameters') || str.includes('cdk-bootstrap')) {
errors.push('Template contains CDK bootstrap/asset dependencies');
}
const deletionPolicies = str.match(/\"DeletionPolicy\":\s*\"(Snapshot|Retain)\"/g);
if (deletionPolicies) {
errors.push('Template contains non-DESTROY deletion policies: ' + deletionPolicies.join(', '));
}
const size = Buffer.byteLength(JSON.stringify(t, null, 2));
if (size > 400000) {
errors.push('Template size ' + size + ' bytes approaching CloudFormation S3 limit (460,800)');
}
if (errors.length > 0) {
errors.forEach(e => console.error('ERROR: ' + e));
process.exit(1);
}
const content = '# Auto-generated from CDK synthesis. Do not edit.\n' + JSON.stringify(t, null, 2);
fs.writeFileSync('../template.yaml', content);
console.log('Wrote template.yaml (' + size + ' bytes, ' + Object.keys(t.Resources || {}).length + ' resources)');
"

- uses: actions/upload-artifact@v7
with:
name: planx-template
path: cloudformation/scenarios/planx/template.yaml
retention-days: 1

synth-bops-planning:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: '22'

- name: Build and synth bops-planning CDK
working-directory: cloudformation/scenarios/bops-planning/cdk
run: |
npm install
npm run build
npx cdk synth

- name: Strip bootstrap cruft and write template
working-directory: cloudformation/scenarios/bops-planning/cdk
run: |
node -e "
const fs = require('fs');
const t = JSON.parse(fs.readFileSync('cdk.out/BopsPlanningStack.template.json', 'utf8'));
delete t.Parameters?.BootstrapVersion;
delete t.Resources?.CDKMetadata;
delete t.Rules?.CheckBootstrapVersion;
const content = '# Auto-generated from CDK synthesis. Do not edit.\n' + JSON.stringify(t, null, 2);
fs.writeFileSync('../template.yaml', content);
console.log('Wrote template.yaml (' + Buffer.byteLength(content) + ' bytes, ' + Object.keys(t.Resources || {}).length + ' resources)');
"

- name: Retention-policy lint
run: ./scripts/lint-retention-policies.sh cloudformation/scenarios/bops-planning/template.yaml

- uses: actions/upload-artifact@v7
with:
name: bops-planning-template
path: cloudformation/scenarios/bops-planning/template.yaml
retention-days: 1

synth-digital-planning-register:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: '22'

- name: Build and synth digital-planning-register CDK
working-directory: cloudformation/scenarios/digital-planning-register/cdk
run: |
npm install
npm run build
npx cdk synth

- name: Strip bootstrap cruft and validate template
working-directory: cloudformation/scenarios/digital-planning-register/cdk
run: |
node -e "
const fs = require('fs');
const t = JSON.parse(fs.readFileSync('cdk.out/DigitalPlanningRegisterStack.template.json', 'utf8'));
delete t.Parameters?.BootstrapVersion;
delete t.Resources?.CDKMetadata;
delete t.Rules?.CheckBootstrapVersion;
const str = JSON.stringify(t);
const errors = [];
if (str.includes('AssetParameters') || str.includes('cdk-bootstrap')) {
errors.push('Template contains CDK bootstrap/asset dependencies');
}
const deletionPolicies = str.match(/\"DeletionPolicy\":\s*\"(Snapshot|Retain)\"/g);
if (deletionPolicies) {
errors.push('Template contains non-DESTROY deletion policies: ' + deletionPolicies.join(', '));
}
const size = Buffer.byteLength(JSON.stringify(t, null, 2));
if (size > 400000) {
errors.push('Template size ' + size + ' bytes approaching CloudFormation S3 limit (460,800)');
}
if (errors.length > 0) {
errors.forEach(e => console.error('ERROR: ' + e));
process.exit(1);
}
const content = '# Auto-generated from CDK synthesis. Do not edit.\n' + JSON.stringify(t, null, 2);
fs.writeFileSync('../template.yaml', content);
console.log('Wrote template.yaml (' + size + ' bytes, ' + Object.keys(t.Resources || {}).length + ' resources)');
"

- uses: actions/upload-artifact@v7
with:
name: digital-planning-register-template
path: cloudformation/scenarios/digital-planning-register/template.yaml
retention-days: 1

# Phase 2b: top-level retention-policy lint over the hand-authored CFN
# templates that don't go through any synth job (council-chatbot,
# foi-redaction, planning-ai, smart-car-park, text-to-speech,
# quicksight-dashboard, ai-contact-centre, all-demo). The lint script also
# detects oversize templates and AssetParameters / cdk-bootstrap residue.
lint-committed-templates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install yq (for real-YAML template parsing)
run: |
sudo wget -qO /usr/local/bin/yq \
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
yq --version

- name: Lint committed scenario templates
run: |
# Scenarios whose template.yaml is committed (not synthesized in CI).
# Synthesized scenarios are linted by their per-scenario synth job.
./scripts/lint-retention-policies.sh \
cloudformation/scenarios/council-chatbot/template.yaml \
cloudformation/scenarios/foi-redaction/template.yaml \
cloudformation/scenarios/planning-ai/template.yaml \
cloudformation/scenarios/smart-car-park/template.yaml \
cloudformation/scenarios/text-to-speech/template.yaml \
cloudformation/scenarios/quicksight-dashboard/template.yaml \
cloudformation/scenarios/ai-contact-centre/template.yaml \
cloudformation/scenarios/all-demo/template.yaml

deploy:
needs: [synth-localgov-drupal, synth-localgov-ims, synth-simply-readable, synth-minute, synth-fixmystreet, synth-paperless-ngx]
needs:
- synth-localgov-drupal
- synth-localgov-ims
- synth-simply-readable
- synth-minute
- synth-fixmystreet
- synth-paperless-ngx
- synth-planx
- synth-bops-planning
- synth-digital-planning-register
- lint-committed-templates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -448,6 +637,21 @@ jobs:
name: simply-readable-website
path: cloudformation/scenarios/simply-readable/website-build

- uses: actions/download-artifact@v8
with:
name: planx-template
path: cloudformation/scenarios/planx

- uses: actions/download-artifact@v8
with:
name: bops-planning-template
path: cloudformation/scenarios/bops-planning

- uses: actions/download-artifact@v8
with:
name: digital-planning-register-template
path: cloudformation/scenarios/digital-planning-register

- name: Build planning-ai template (inject PDF + text into template)
run: python3 cloudformation/scenarios/planning-ai/build-template.py -o cloudformation/scenarios/planning-ai/template.yaml

Expand All @@ -470,6 +674,21 @@ jobs:
aws s3 sync cloudformation/scenarios/simply-readable/website-build/ \
"s3://${BUCKET}/scenarios/simply-readable/website-build/" --region us-east-1 --delete

# The non-ISB scenarios (planx, bops-planning, digital-planning-register)
# and the orphan-import scenarios (minute, fixmystreet, paperless-ngx) do
# NOT go through the isb-hub StackSet pipeline. Upload their synthed
# templates directly to the blueprints bucket so the all-demo nested
# stacks can reference them by URL via the scenarios/<name>/template.yaml
# path convention.
- name: Upload non-StackSet scenario templates to S3
run: |
BUCKET=ndx-try-isb-blueprints-568672915267
for SCENARIO in planx digital-planning-register bops-planning minute fixmystreet paperless-ngx; do
aws s3 cp "cloudformation/scenarios/${SCENARIO}/template.yaml" \
"s3://${BUCKET}/scenarios/${SCENARIO}/template.yaml" \
--region us-east-1
done

# AI Contact Centre: SAM-style scenario. `sam package` zips each Lambda
# CodeUri, uploads zips to the blueprints bucket, and writes a packaged
# template (with absolute s3:// CodeUri values) to dist/template.yaml.
Expand All @@ -490,6 +709,26 @@ jobs:
--s3-prefix scenarios/ai-contact-centre/assets \
--region us-east-1

# Phase 2a / T2a.5: verify the packaged template's CodeUri values all
# target the blueprints bucket. SAM's default --s3-bucket would write
# CodeUri values pointing at aws-sam-cli-managed-default-samclisourcebucket-*;
# the deploy role only has read on the blueprints bucket, so a packaging
# regression that drops --s3-bucket would silently produce a template
# the smoke-test account cannot deploy. Fail the build early instead.
- name: Verify AI Contact Centre packaged CodeUri targets the blueprints bucket
working-directory: cloudformation/scenarios/ai-contact-centre
run: |
BAD=$(grep -oE 's3://[a-z0-9.-]+' dist/template.yaml | sort -u | grep -v 'ndx-try-isb-blueprints-568672915267' || true)
if [ -n "$BAD" ]; then
echo "ERROR: packaged template references non-blueprints buckets:"
echo "$BAD"
echo ""
echo "Re-check the sam package command's --s3-bucket flag."
exit 1
fi
COUNT=$(grep -c 's3://ndx-try-isb-blueprints-568672915267' dist/template.yaml || true)
echo "Verified $COUNT CodeUri references point at the blueprints bucket"

- run: npm ci
working-directory: cloudformation/isb-hub

Expand Down
Loading
Loading