Skip to content

Commit 668cba6

Browse files
authored
Merge pull request #127 from co-cddo/feat/bops-planning
feat: BOPS walkthrough, screenshots, and password output
2 parents 527b06e + fcce9e4 commit 668cba6

53 files changed

Lines changed: 6158 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Build and publish BOPS container images to ghcr.io
2+
#
3+
# Builds two images in parallel:
4+
# - ghcr.io/co-cddo/ndx_try_aws_scenarios-bops:latest (back-office + worker)
5+
# - ghcr.io/co-cddo/ndx_try_aws_scenarios-bops-applicants:latest (public portal)
6+
#
7+
# Both clone their upstream repos at pinned commits, apply our overlay files,
8+
# and build using the upstream Dockerfile.production.
9+
10+
name: Build BOPS Container Images
11+
12+
on:
13+
push:
14+
branches: [main, feat/bops-planning]
15+
paths:
16+
- 'cloudformation/scenarios/bops-planning/docker/**'
17+
- '.github/workflows/docker-build-bops.yml'
18+
pull_request:
19+
paths:
20+
- 'cloudformation/scenarios/bops-planning/docker/**'
21+
- '.github/workflows/docker-build-bops.yml'
22+
workflow_dispatch:
23+
inputs:
24+
push_image:
25+
description: 'Push images to registry'
26+
required: false
27+
default: true
28+
type: boolean
29+
30+
env:
31+
REGISTRY: ghcr.io
32+
# Pin to specific commits for reproducibility — update these when upgrading BOPS
33+
BOPS_REPO: unboxed/bops
34+
BOPS_COMMIT: main
35+
BOPS_APPLICANTS_REPO: unboxed/bops-applicants
36+
BOPS_APPLICANTS_COMMIT: main
37+
38+
jobs:
39+
changes:
40+
name: Check for Docker changes
41+
runs-on: ubuntu-latest
42+
if: github.event_name == 'pull_request'
43+
outputs:
44+
docker: ${{ steps.filter.outputs.docker }}
45+
steps:
46+
- uses: actions/checkout@v6
47+
- uses: dorny/paths-filter@v3
48+
id: filter
49+
with:
50+
filters: |
51+
docker:
52+
- 'cloudformation/scenarios/bops-planning/docker/**'
53+
- '.github/workflows/docker-build-bops.yml'
54+
55+
build-bops:
56+
name: Build BOPS Back-Office
57+
runs-on: ubuntu-latest
58+
needs: [changes]
59+
if: |
60+
always() &&
61+
(needs.changes.result == 'skipped' || needs.changes.outputs.docker == 'true')
62+
permissions:
63+
contents: read
64+
packages: write
65+
66+
steps:
67+
- name: Checkout repository
68+
uses: actions/checkout@v6
69+
70+
- name: Clone BOPS source
71+
run: |
72+
git clone --depth 1 https://github.com/${{ env.BOPS_REPO }}.git bops-src
73+
if [ "${{ env.BOPS_COMMIT }}" != "main" ]; then
74+
cd bops-src
75+
git fetch --depth 1 origin ${{ env.BOPS_COMMIT }}
76+
git checkout ${{ env.BOPS_COMMIT }}
77+
cd ..
78+
fi
79+
80+
- name: Copy overlay files
81+
run: |
82+
mkdir -p bops-src/scripts
83+
cp cloudformation/scenarios/bops-planning/docker/bops/config/initializers/default_local_authority.rb bops-src/config/initializers/
84+
cp cloudformation/scenarios/bops-planning/docker/bops/scripts/seed_sample_data.rb bops-src/scripts/
85+
cp cloudformation/scenarios/bops-planning/docker/bops/scripts/seed-entrypoint.sh bops-src/scripts/
86+
cp cloudformation/scenarios/bops-planning/docker/bops/scripts/init-bops.sh bops-src/scripts/
87+
cp cloudformation/scenarios/bops-planning/docker/bops/entrypoint.sh bops-src/
88+
89+
- name: Set up Docker Buildx
90+
uses: docker/setup-buildx-action@v3
91+
92+
- name: Log in to GitHub Container Registry
93+
uses: docker/login-action@v3
94+
with:
95+
registry: ${{ env.REGISTRY }}
96+
username: ${{ github.actor }}
97+
password: ${{ secrets.GITHUB_TOKEN }}
98+
99+
- name: Extract metadata
100+
id: meta
101+
uses: docker/metadata-action@v5
102+
with:
103+
images: ${{ env.REGISTRY }}/co-cddo/ndx_try_aws_scenarios-bops
104+
tags: |
105+
type=sha,prefix=sha-
106+
type=raw,value=latest,enable={{is_default_branch}}
107+
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }}
108+
109+
- name: Build and push BOPS image
110+
uses: docker/build-push-action@v6
111+
with:
112+
context: bops-src
113+
file: bops-src/Dockerfile.production
114+
push: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feat/bops-planning' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image != 'false')) }}
115+
tags: ${{ steps.meta.outputs.tags }}
116+
labels: ${{ steps.meta.outputs.labels }}
117+
cache-from: type=gha,scope=bops
118+
cache-to: type=gha,mode=max,scope=bops
119+
platforms: linux/amd64
120+
121+
- name: Output image details
122+
run: |
123+
echo "## BOPS Image Built" >> $GITHUB_STEP_SUMMARY
124+
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
125+
echo '```' >> $GITHUB_STEP_SUMMARY
126+
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
127+
echo '```' >> $GITHUB_STEP_SUMMARY
128+
129+
build-bops-applicants:
130+
name: Build BOPS Applicants Portal
131+
runs-on: ubuntu-latest
132+
needs: [changes]
133+
if: |
134+
always() &&
135+
(needs.changes.result == 'skipped' || needs.changes.outputs.docker == 'true')
136+
permissions:
137+
contents: read
138+
packages: write
139+
140+
steps:
141+
- name: Checkout repository
142+
uses: actions/checkout@v6
143+
144+
- name: Clone BOPS-Applicants source
145+
run: |
146+
git clone --depth 1 https://github.com/${{ env.BOPS_APPLICANTS_REPO }}.git bops-applicants-src
147+
if [ "${{ env.BOPS_APPLICANTS_COMMIT }}" != "main" ]; then
148+
cd bops-applicants-src
149+
git fetch --depth 1 origin ${{ env.BOPS_APPLICANTS_COMMIT }}
150+
git checkout ${{ env.BOPS_APPLICANTS_COMMIT }}
151+
cd ..
152+
fi
153+
154+
- name: Copy overlay files
155+
run: |
156+
cp cloudformation/scenarios/bops-planning/docker/bops-applicants/config/initializers/default_local_authority.rb bops-applicants-src/config/initializers/
157+
158+
- name: Set up Docker Buildx
159+
uses: docker/setup-buildx-action@v3
160+
161+
- name: Log in to GitHub Container Registry
162+
uses: docker/login-action@v3
163+
with:
164+
registry: ${{ env.REGISTRY }}
165+
username: ${{ github.actor }}
166+
password: ${{ secrets.GITHUB_TOKEN }}
167+
168+
- name: Extract metadata
169+
id: meta
170+
uses: docker/metadata-action@v5
171+
with:
172+
images: ${{ env.REGISTRY }}/co-cddo/ndx_try_aws_scenarios-bops-applicants
173+
tags: |
174+
type=sha,prefix=sha-
175+
type=raw,value=latest,enable={{is_default_branch}}
176+
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }}
177+
178+
- name: Build and push BOPS-Applicants image
179+
uses: docker/build-push-action@v6
180+
with:
181+
context: bops-applicants-src
182+
file: bops-applicants-src/Dockerfile.production
183+
push: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/feat/bops-planning' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image != 'false')) }}
184+
tags: ${{ steps.meta.outputs.tags }}
185+
labels: ${{ steps.meta.outputs.labels }}
186+
cache-from: type=gha,scope=bops-applicants
187+
cache-to: type=gha,mode=max,scope=bops-applicants
188+
platforms: linux/amd64
189+
190+
- name: Output image details
191+
run: |
192+
echo "## BOPS-Applicants Image Built" >> $GITHUB_STEP_SUMMARY
193+
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
194+
echo '```' >> $GITHUB_STEP_SUMMARY
195+
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
196+
echo '```' >> $GITHUB_STEP_SUMMARY
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ISB Blueprint Registration: BOPS Planning System
2+
3+
Register the NDX:Try BOPS Planning scenario as an Innovation Sandbox (ISB) blueprint so it auto-deploys into sandbox accounts when leases are approved.
4+
5+
## Prerequisites
6+
7+
- AWS Innovation Sandbox deployed in the hub account
8+
- Hub account ID known (referred to as `{HUB_ACCOUNT_ID}` below)
9+
- ISB namespace known (referred to as `{NAMESPACE}` below)
10+
- An S3 bucket accessible from the hub account for hosting the template (referred to as `{BUCKET}` in region `{REGION}`)
11+
- Ordnance Survey Vector Tiles API key (free tier from OS Data Hub)
12+
- BOPS and BOPS-Applicants Docker images published to ghcr.io
13+
14+
## Step 1 — Upload Template to S3
15+
16+
Upload the CloudFormation template to an S3 bucket accessible from the hub account:
17+
18+
```bash
19+
aws s3 cp template.yaml \
20+
s3://{BUCKET}/scenarios/bops-planning/template.yaml
21+
```
22+
23+
**Note:** Template is ~104KB (JSON) — must use S3 URL, not inline template body.
24+
25+
## Step 2 — Create StackSet
26+
27+
Create a self-managed CloudFormation StackSet using ISB's roles:
28+
29+
```bash
30+
aws cloudformation create-stack-set \
31+
--stack-set-name ndx-try-bops-planning \
32+
--template-url https://{BUCKET}.s3.{REGION}.amazonaws.com/scenarios/bops-planning/template.yaml \
33+
--administration-role-arn arn:aws:iam::{HUB_ACCOUNT_ID}:role/InnovationSandbox-{NAMESPACE}-IntermediateRole \
34+
--execution-role-name InnovationSandbox-{NAMESPACE}-SandboxAccountRole \
35+
--managed-execution Active=true \
36+
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
37+
--parameters ParameterKey=OSVectorTilesApiKey,ParameterValue={YOUR_OS_API_KEY} \
38+
--description "NDX:Try BOPS Planning System - Digital planning for UK councils"
39+
```
40+
41+
Notes:
42+
- `CAPABILITY_IAM` and `CAPABILITY_NAMED_IAM` are required because the template uses explicit IAM role names matching ISB SCP patterns
43+
- `--managed-execution Active=true` is recommended for concurrent lease handling
44+
- `OSVectorTilesApiKey` parameter is required — maps will not render without it
45+
46+
## Step 3 — Register in ISB
47+
48+
1. Navigate to ISB admin console > **Blueprints** > **Register New Blueprint**
49+
2. Enter name: `ndx-try-bops-planning` (must be 1-50 chars, start with letter, alphanumeric + hyphens only)
50+
3. Select the `ndx-try-bops-planning` StackSet
51+
4. Configure deployment:
52+
- Select target regions: `us-east-1`
53+
- Set timeout: **40 minutes** recommended (Aurora ~10 min, Redis ~8 min, CloudFront ~10 min, seed task ~10 min — partially parallel)
54+
5. Configure parameter overrides:
55+
- `OSVectorTilesApiKey`: Set to the OS Vector Tiles API key
56+
6. Review and submit
57+
58+
## Step 4 — Associate with Lease Template
59+
60+
1. In ISB admin console, navigate to **Lease Templates**
61+
2. Edit an existing lease template or create a new one
62+
3. In the blueprint selection step, select `ndx-try-bops-planning`
63+
4. Save the lease template
64+
65+
## Verification
66+
67+
1. Create a test lease using the template
68+
2. Wait for StackSet instance creation (~25-35 minutes)
69+
3. Verify:
70+
- Stack status is `CREATE_COMPLETE`
71+
- BOPS login page loads at `BOPSLoginUrl` output
72+
- Login with `BOPSUsername` / retrieve password from `BOPSPassword` Secrets Manager link
73+
- Dashboard shows 80 planning applications
74+
- OS Maps tiles render on application detail pages
75+
- Applicants portal loads at `ApplicantsPortalUrl` output
76+
4. Test stack deletion — should complete cleanly within 15 minutes
77+
78+
## Troubleshooting
79+
80+
### Seed task fails
81+
Check CloudWatch logs at `/ndx-bops/production``bops-seed` stream prefix. Common issues:
82+
- Database not ready: seed task retries automatically, but check Aurora cluster status
83+
- Ruby/Rails errors: check the seed script output for AASM transition failures
84+
85+
### ECS services failing health checks
86+
- BOPS web: `/healthcheck` on port 3000 — check container logs for Rails boot errors
87+
- BOPS-Applicants: `/healthcheck` on port 80 — check API connectivity to BOPS
88+
- Allow 10 minutes for initial startup (health check grace period)
89+
90+
### Maps not rendering
91+
- Verify `OSVectorTilesApiKey` parameter was provided correctly
92+
- Check browser console for 401 errors from OS Maps API
93+
94+
### ISB SCP errors
95+
All IAM roles must start with `InnovationSandbox-ndx-`. The CDK Aspect handles this automatically, but if you see "AccessDenied" errors, check the role name in the error message.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env node
2+
import 'source-map-support/register';
3+
import * as cdk from 'aws-cdk-lib';
4+
import { BopsPlanningStack } from '../lib/bops-planning-stack';
5+
6+
const app = new cdk.App();
7+
8+
new BopsPlanningStack(app, 'BopsPlanningStack', {
9+
// No env — produces environment-agnostic template using CloudFormation intrinsics
10+
// so the same template works in any account/region via StackSets
11+
description: 'BOPS Planning System - NDX:Try Demonstration Environment',
12+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/app.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test",
17+
"dist"
18+
]
19+
},
20+
"context": {
21+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
22+
"@aws-cdk/core:checkSecretUsage": true,
23+
"@aws-cdk/core:target-partitions": [
24+
"aws"
25+
],
26+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27+
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
28+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
29+
"@aws-cdk/core:enablePartitionLiterals": true,
30+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
31+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true
32+
},
33+
"output": "../cdk.out"
34+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};

0 commit comments

Comments
 (0)