Skip to content

Commit e9161f7

Browse files
committed
Add release workflow: versioned image, bundled manifests, auto release notes
1 parent 83ca303 commit e9161f7

1 file changed

Lines changed: 167 additions & 0 deletions

File tree

.github/workflows/release.yaml

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
packages: write
13+
outputs:
14+
image: ghcr.io/pprecel/claude-warmup:${{ github.ref_name }}
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Run npm audit
20+
run: npm audit --audit-level=high
21+
22+
- name: Log in to GHCR
23+
uses: docker/login-action@v3
24+
with:
25+
registry: ghcr.io
26+
username: ${{ github.actor }}
27+
password: ${{ secrets.GITHUB_TOKEN }}
28+
29+
- name: Build and push
30+
uses: docker/build-push-action@v6
31+
with:
32+
push: true
33+
tags: |
34+
ghcr.io/pprecel/claude-warmup:${{ github.ref_name }}
35+
ghcr.io/pprecel/claude-warmup:latest
36+
37+
scan:
38+
runs-on: ubuntu-latest
39+
needs: build
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v4
43+
44+
- name: Scan image for vulnerabilities
45+
uses: aquasecurity/trivy-action@master
46+
with:
47+
image-ref: ghcr.io/pprecel/claude-warmup:${{ github.ref_name }}
48+
format: table
49+
exit-code: 1
50+
severity: CRITICAL,HIGH
51+
trivyignores: .trivyignore
52+
53+
release:
54+
runs-on: ubuntu-latest
55+
needs: [build, scan]
56+
permissions:
57+
contents: write
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v4
61+
with:
62+
fetch-depth: 0
63+
64+
- name: Package k8s manifests
65+
run: |
66+
# Update deployment image to the release tag
67+
sed "s|ghcr.io/pprecel/claude-warmup:latest|ghcr.io/pprecel/claude-warmup:${{ github.ref_name }}|g" \
68+
k8s/deployment.yaml > /tmp/deployment.yaml
69+
cp /tmp/deployment.yaml k8s/deployment.yaml
70+
71+
# Bundle all non-secret manifests into a single file
72+
kubectl kustomize /dev/null 2>/dev/null || true
73+
cat k8s/rbac.yaml \
74+
k8s/network-policy.yaml \
75+
k8s/redis.yaml \
76+
k8s/service.yaml \
77+
k8s/deployment.yaml \
78+
| sed "s|---||g" \
79+
| awk 'NF' \
80+
| sed '1!{/^---/!{/^apiVersion/i ---
81+
}}' \
82+
> /tmp/manifests.yaml
83+
84+
echo "---" >> /tmp/manifests.yaml
85+
echo "# NOTE: Create the redis-credentials secret separately before applying." >> /tmp/manifests.yaml
86+
echo "# See k8s/secret.yaml.example" >> /tmp/manifests.yaml
87+
88+
- name: Generate release notes
89+
id: notes
90+
run: |
91+
# Get previous tag
92+
PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${{ github.ref_name }}$" | head -1)
93+
94+
if [ -z "$PREV_TAG" ]; then
95+
COMMIT_RANGE="HEAD"
96+
RANGE_LABEL="all commits"
97+
else
98+
COMMIT_RANGE="${PREV_TAG}..${{ github.ref_name }}"
99+
RANGE_LABEL="since ${PREV_TAG}"
100+
fi
101+
102+
# Categorise commits
103+
FEATURES=$(git log $COMMIT_RANGE --oneline --no-merges | grep -iE "^[a-f0-9]+ (add|feat|new)" || true)
104+
FIXES=$(git log $COMMIT_RANGE --oneline --no-merges | grep -iE "^[a-f0-9]+ (fix|bug|patch)" || true)
105+
SECURITY=$(git log $COMMIT_RANGE --oneline --no-merges | grep -iE "^[a-f0-9]+ (sec|cve|vuln|auth|rbac|policy)" || true)
106+
DOCS=$(git log $COMMIT_RANGE --oneline --no-merges | grep -iE "^[a-f0-9]+ (doc|readme|claude)" || true)
107+
CI=$(git log $COMMIT_RANGE --oneline --no-merges | grep -iE "^[a-f0-9]+ (ci|build|workflow|pipeline|trivy|scan|test|integr)" || true)
108+
OTHER=$(git log $COMMIT_RANGE --oneline --no-merges | grep -viE "^[a-f0-9]+ (add|feat|new|fix|bug|patch|sec|cve|vuln|auth|rbac|policy|doc|readme|claude|ci|build|workflow|pipeline|trivy|scan|test|integr)" || true)
109+
110+
format_section() {
111+
local title="$1"
112+
local commits="$2"
113+
if [ -n "$commits" ]; then
114+
echo "## $title"
115+
echo "$commits" | while read -r line; do
116+
sha=$(echo "$line" | awk '{print $1}')
117+
msg=$(echo "$line" | cut -d' ' -f2-)
118+
echo "- ${msg} (\`${sha}\`)"
119+
done
120+
echo ""
121+
fi
122+
}
123+
124+
NOTES=$(cat <<NOTES
125+
## What's included in ${{ github.ref_name }}
126+
127+
**Image:** \`ghcr.io/pprecel/claude-warmup:${{ github.ref_name }}\`
128+
**Manifests:** See \`manifests.yaml\` attached to this release
129+
130+
Changes ${RANGE_LABEL}:
131+
132+
$(format_section "Features" "$FEATURES")
133+
$(format_section "Bug Fixes" "$FIXES")
134+
$(format_section "Security" "$SECURITY")
135+
$(format_section "Documentation" "$DOCS")
136+
$(format_section "CI/CD" "$CI")
137+
$(format_section "Other" "$OTHER")
138+
139+
## Deploying this release
140+
141+
\`\`\`bash
142+
# 1. Create the Redis secret (first time only)
143+
kubectl create secret generic redis-credentials \\
144+
--from-literal=redis-password=\$(openssl rand -base64 24)
145+
146+
# 2. Apply all manifests
147+
kubectl apply -f manifests.yaml
148+
149+
# 3. Verify
150+
kubectl rollout status deployment/redis
151+
kubectl rollout status deployment/claude-warmup
152+
\`\`\`
153+
NOTES
154+
)
155+
156+
# Write to file for gh release create
157+
echo "$NOTES" > /tmp/release-notes.md
158+
cat /tmp/release-notes.md
159+
160+
- name: Create GitHub release
161+
env:
162+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
163+
run: |
164+
gh release create ${{ github.ref_name }} \
165+
--title "Release ${{ github.ref_name }}" \
166+
--notes-file /tmp/release-notes.md \
167+
/tmp/manifests.yaml#manifests.yaml

0 commit comments

Comments
 (0)