Skip to content

Commit 4b99120

Browse files
Initial platform infrastructure
Complete Javabin platform with all core infrastructure: Terraform (applied, 117 resources live in eu-central-1): - VPC with public/private subnets, NAT gateway - ALB with wildcard cert for *.javazone.no - 4 CI IAM roles via GitHub OIDC (infra, app, deploy, override) - Permission boundary (javabin-developer-boundary) - ECS Fargate cluster (javabin-platform) + 3 ECR repos - GuardDuty, Security Hub, AWS Config, Cost Anomaly Detection - 6 Lambda functions (slack-alert, cost-report, daily-cost-check, compliance-reporter, override-cleanup, team-provisioner stub) - SNS topics, EventBridge rules, S3 config bucket State management: - S3 backend + DynamoDB locking (bootstrapped and migrated) - AWS Organizations enabled (SCP deferred until boundary verified) CI/CD: - Platform CI workflow (plan -> LLM review -> apply) - 11 reusable workflows for app repos (detect, build, plan, apply, deploy) - Risk gate with SSM-based override mechanism Golden path: - 12 reusable Terraform modules for app repos - app.yaml schema + generate-terraform.sh - App template and registry repo content Documentation: - 8 docs files covering all components - Phase planning docs and task briefs - CODEOWNERS for platform-owners team
0 parents  commit 4b99120

141 files changed

Lines changed: 15604 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
* @javaBin/platform-owners
2+
terraform/org/ @javaBin/platform-owners
3+
terraform/platform/iam/ @javaBin/platform-owners
4+
terraform/platform/monitoring/ @javaBin/platform-owners
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Approve Override
2+
3+
# workflow_dispatch — only board members can trigger this.
4+
# IAM trust condition on javabin-ci-override-approver verifies the actor.
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
repo:
10+
description: "Repository (e.g. javaBin/moresleep)"
11+
required: true
12+
type: string
13+
sha:
14+
description: "Commit SHA to override"
15+
required: true
16+
type: string
17+
reason:
18+
description: "Reason for override"
19+
required: true
20+
type: string
21+
22+
permissions:
23+
id-token: write
24+
contents: read
25+
26+
env:
27+
AWS_ACCOUNT_ID: "553637109631"
28+
AWS_REGION: eu-central-1
29+
30+
jobs:
31+
approve:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- name: Configure AWS credentials via OIDC
35+
uses: aws-actions/configure-aws-credentials@v4
36+
with:
37+
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/javabin-ci-override-approver
38+
aws-region: ${{ env.AWS_REGION }}
39+
40+
- name: Write override token
41+
env:
42+
OVERRIDE_REPO: ${{ inputs.repo }}
43+
OVERRIDE_SHA: ${{ inputs.sha }}
44+
OVERRIDE_REASON: ${{ inputs.reason }}
45+
OVERRIDE_ACTOR: ${{ github.actor }}
46+
run: |
47+
PARAM_NAME="/javabin/platform-overrides/${OVERRIDE_REPO}/${OVERRIDE_SHA}"
48+
49+
python3 << 'PYEOF'
50+
import json, os
51+
value = json.dumps({
52+
"approved_by": os.environ["OVERRIDE_ACTOR"],
53+
"reason": os.environ["OVERRIDE_REASON"],
54+
"approved_at": __import__('datetime').datetime.utcnow().isoformat() + "Z",
55+
"run_id": os.environ.get("GITHUB_RUN_ID", ""),
56+
})
57+
with open("/tmp/override-value.txt", "w") as f:
58+
f.write(value)
59+
PYEOF
60+
61+
aws ssm put-parameter \
62+
--name "$PARAM_NAME" \
63+
--type String \
64+
--value "$(cat /tmp/override-value.txt)" \
65+
--overwrite
66+
67+
echo "Override token written: ${PARAM_NAME}"
68+
69+
- name: Post to Slack
70+
env:
71+
OVERRIDE_REPO: ${{ inputs.repo }}
72+
OVERRIDE_SHA: ${{ inputs.sha }}
73+
OVERRIDE_REASON: ${{ inputs.reason }}
74+
OVERRIDE_ACTOR: ${{ github.actor }}
75+
GITHUB_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
76+
run: |
77+
export SLACK_WEBHOOK_URL=$(aws ssm get-parameter \
78+
--name /javabin/slack/platform-override-alerts-webhook \
79+
--with-decryption --query Parameter.Value --output text)
80+
81+
python3 << 'PYEOF'
82+
import json, os, urllib.request
83+
webhook_url = os.environ.get("SLACK_WEBHOOK_URL", "")
84+
if not webhook_url:
85+
print("No webhook URL, skipping Slack notification")
86+
exit(0)
87+
run_url = os.environ["GITHUB_RUN_URL"]
88+
repo = os.environ["OVERRIDE_REPO"]
89+
sha = os.environ["OVERRIDE_SHA"]
90+
actor = os.environ["OVERRIDE_ACTOR"]
91+
reason = os.environ["OVERRIDE_REASON"]
92+
payload = json.dumps({
93+
"blocks": [
94+
{"type": "header", "text": {"type": "plain_text", "text": "Risk Override Approved", "emoji": True}},
95+
{"type": "section", "text": {"type": "mrkdwn", "text": f"*Repo:* {repo}\n*SHA:* `{sha}`\n*By:* {actor}\n*Reason:* {reason}"}},
96+
{"type": "section", "text": {"type": "mrkdwn", "text": f"<{run_url}|View Approval Run>"}},
97+
],
98+
"text": f"Risk override approved for {repo}"
99+
}).encode()
100+
req = urllib.request.Request(webhook_url, data=payload, headers={"Content-Type": "application/json"})
101+
urllib.request.urlopen(req)
102+
PYEOF

.github/workflows/build-jvm.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Build JVM
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
java_version:
7+
description: "Java version"
8+
type: string
9+
default: "21"
10+
maven_args:
11+
description: "Additional Maven arguments"
12+
type: string
13+
default: ""
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: actions/setup-java@v4
22+
with:
23+
distribution: temurin
24+
java-version: ${{ inputs.java_version }}
25+
cache: maven
26+
27+
- name: Build and test
28+
run: mvn --batch-mode verify ${{ inputs.maven_args }}
29+
30+
- name: Upload test results
31+
if: always()
32+
uses: actions/upload-artifact@v4
33+
with:
34+
name: test-results
35+
path: "**/target/surefire-reports/*.xml"
36+
retention-days: 7

.github/workflows/build-ts.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Build TypeScript
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
node_version:
7+
description: "Node.js version"
8+
type: string
9+
default: "22"
10+
pnpm_version:
11+
description: "pnpm version"
12+
type: string
13+
default: "9"
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: pnpm/action-setup@v4
22+
with:
23+
version: ${{ inputs.pnpm_version }}
24+
25+
- uses: actions/setup-node@v4
26+
with:
27+
node-version: ${{ inputs.node_version }}
28+
cache: pnpm
29+
30+
- name: Install dependencies
31+
run: pnpm install --frozen-lockfile
32+
33+
- name: Build
34+
run: pnpm build
35+
36+
- name: Test
37+
run: pnpm test

.github/workflows/detect.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Detect Repo Contents
2+
3+
on:
4+
workflow_call:
5+
outputs:
6+
has_yaml:
7+
description: "app.yaml exists"
8+
value: ${{ jobs.detect.outputs.has_yaml }}
9+
has_tf:
10+
description: "terraform/ directory exists"
11+
value: ${{ jobs.detect.outputs.has_tf }}
12+
has_docker:
13+
description: "Dockerfile exists"
14+
value: ${{ jobs.detect.outputs.has_docker }}
15+
has_maven:
16+
description: "pom.xml exists"
17+
value: ${{ jobs.detect.outputs.has_maven }}
18+
has_pnpm:
19+
description: "pnpm-lock.yaml exists"
20+
value: ${{ jobs.detect.outputs.has_pnpm }}
21+
has_eb:
22+
description: ".elasticbeanstalk/ directory exists"
23+
value: ${{ jobs.detect.outputs.has_eb }}
24+
has_cdk:
25+
description: "cdk.json exists"
26+
value: ${{ jobs.detect.outputs.has_cdk }}
27+
app_name:
28+
description: "App name from app.yaml (empty if no app.yaml)"
29+
value: ${{ jobs.detect.outputs.app_name }}
30+
31+
jobs:
32+
detect:
33+
runs-on: ubuntu-latest
34+
outputs:
35+
has_yaml: ${{ steps.check.outputs.has_yaml }}
36+
has_tf: ${{ steps.check.outputs.has_tf }}
37+
has_docker: ${{ steps.check.outputs.has_docker }}
38+
has_maven: ${{ steps.check.outputs.has_maven }}
39+
has_pnpm: ${{ steps.check.outputs.has_pnpm }}
40+
has_eb: ${{ steps.check.outputs.has_eb }}
41+
has_cdk: ${{ steps.check.outputs.has_cdk }}
42+
app_name: ${{ steps.check.outputs.app_name }}
43+
steps:
44+
- uses: actions/checkout@v4
45+
46+
- name: Detect repo contents
47+
id: check
48+
run: |
49+
echo "has_yaml=$(test -f app.yaml && echo true || echo false)" >> "$GITHUB_OUTPUT"
50+
echo "has_tf=$(test -d terraform && echo true || echo false)" >> "$GITHUB_OUTPUT"
51+
echo "has_docker=$(test -f Dockerfile && echo true || echo false)" >> "$GITHUB_OUTPUT"
52+
echo "has_maven=$(test -f pom.xml && echo true || echo false)" >> "$GITHUB_OUTPUT"
53+
echo "has_pnpm=$(test -f pnpm-lock.yaml && echo true || echo false)" >> "$GITHUB_OUTPUT"
54+
echo "has_eb=$(test -d .elasticbeanstalk && echo true || echo false)" >> "$GITHUB_OUTPUT"
55+
echo "has_cdk=$(test -f cdk.json && echo true || echo false)" >> "$GITHUB_OUTPUT"
56+
57+
if [ -f app.yaml ]; then
58+
APP_NAME=$(grep -m1 '^name:' app.yaml | awk '{print $2}' | tr -d '"'"'" || echo "")
59+
echo "app_name=${APP_NAME}" >> "$GITHUB_OUTPUT"
60+
else
61+
echo "app_name=" >> "$GITHUB_OUTPUT"
62+
fi

.github/workflows/docker-build.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Docker Build
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
dockerfile:
7+
description: "Path to Dockerfile"
8+
type: string
9+
default: "Dockerfile"
10+
build_context:
11+
description: "Docker build context"
12+
type: string
13+
default: "."
14+
aws_account_id:
15+
description: "AWS account ID"
16+
type: string
17+
default: "553637109631"
18+
aws_region:
19+
description: "AWS region"
20+
type: string
21+
default: "eu-central-1"
22+
outputs:
23+
image_uri:
24+
description: "Full image URI with tag"
25+
value: ${{ jobs.build.outputs.image_uri }}
26+
image_tag:
27+
description: "Primary image tag (sha-*)"
28+
value: ${{ jobs.build.outputs.image_tag }}
29+
30+
permissions:
31+
id-token: write
32+
contents: read
33+
34+
jobs:
35+
build:
36+
runs-on: ubuntu-latest
37+
outputs:
38+
image_uri: ${{ steps.push.outputs.image_uri }}
39+
image_tag: ${{ steps.tags.outputs.primary_tag }}
40+
steps:
41+
- uses: actions/checkout@v4
42+
43+
- name: Configure AWS credentials via OIDC
44+
uses: aws-actions/configure-aws-credentials@v4
45+
with:
46+
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-deploy-${{ github.event.repository.name }}
47+
aws-region: ${{ inputs.aws_region }}
48+
49+
- name: Login to ECR
50+
id: ecr
51+
uses: aws-actions/amazon-ecr-login@v2
52+
53+
- name: Determine image tags
54+
id: tags
55+
run: |
56+
REPO="${{ steps.ecr.outputs.registry }}/${{ github.event.repository.name }}"
57+
SHA_TAG="sha-${GITHUB_SHA::8}"
58+
echo "primary_tag=${SHA_TAG}" >> "$GITHUB_OUTPUT"
59+
echo "repo=${REPO}" >> "$GITHUB_OUTPUT"
60+
61+
TAGS="${REPO}:${SHA_TAG}"
62+
63+
if [ "${{ github.ref_name }}" = "main" ] || [ "${{ github.ref_name }}" = "master" ]; then
64+
DATE_TAG="main-$(date -u +%Y%m%d-%H%M)"
65+
TAGS="${TAGS},${REPO}:${DATE_TAG},${REPO}:latest"
66+
fi
67+
68+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
69+
TAGS="${TAGS},${REPO}:${{ github.ref_name }}"
70+
fi
71+
72+
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
73+
74+
- uses: docker/setup-buildx-action@v3
75+
76+
- name: Build and push
77+
uses: docker/build-push-action@v6
78+
with:
79+
context: ${{ inputs.build_context }}
80+
file: ${{ inputs.dockerfile }}
81+
push: true
82+
tags: ${{ steps.tags.outputs.tags }}
83+
cache-from: type=gha
84+
cache-to: type=gha,mode=max
85+
86+
- name: Set output
87+
id: push
88+
run: |
89+
echo "image_uri=${{ steps.tags.outputs.repo }}:${{ steps.tags.outputs.primary_tag }}" >> "$GITHUB_OUTPUT"

.github/workflows/eb-deploy.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: EB Deploy
2+
3+
# Transitional workflow for Elastic Beanstalk apps. Remove per-repo
4+
# once the app is confirmed working on ECS.
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
environment_name:
10+
description: "Elastic Beanstalk environment name"
11+
type: string
12+
required: true
13+
aws_account_id:
14+
description: "AWS account ID"
15+
type: string
16+
default: "553637109631"
17+
aws_region:
18+
description: "AWS region"
19+
type: string
20+
default: "eu-central-1"
21+
22+
permissions:
23+
id-token: write
24+
contents: read
25+
26+
jobs:
27+
deploy:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- name: Configure AWS credentials via OIDC
33+
uses: aws-actions/configure-aws-credentials@v4
34+
with:
35+
role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/javabin-ci-deploy-${{ github.event.repository.name }}
36+
aws-region: ${{ inputs.aws_region }}
37+
38+
- name: Install EB CLI
39+
run: pip install awsebcli
40+
41+
- name: Deploy to Elastic Beanstalk
42+
run: eb deploy ${{ inputs.environment_name }} --staged

0 commit comments

Comments
 (0)