Skip to content

Commit bc2f75d

Browse files
authored
feat(releases): Create automated PRs when new release is added (#107)
* chore: set version to v0.13 Signed-off-by: sandert-k8s <sandert98@gmail.com> * feat: add automated PRs when new version is added Signed-off-by: sandert-k8s <sandert98@gmail.com> * fix: disable cache so version is dynamic Signed-off-by: sandert-k8s <sandert98@gmail.com> * feat: use GitHub API for verified+signoff commits Signed-off-by: sandert-k8s <sandert98@gmail.com> --------- Signed-off-by: sandert-k8s <sandert98@gmail.com>
1 parent fb71c11 commit bc2f75d

3 files changed

Lines changed: 197 additions & 4 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
name: Sync version switcher
2+
permissions: {}
3+
4+
# Triggered automatically when the `version:` field in params.yaml changes on
5+
# main (i.e. a new release is cut), or manually for one-off runs.
6+
on:
7+
push:
8+
branches: [main]
9+
paths: ['config/_default/params.yaml']
10+
workflow_dispatch:
11+
inputs:
12+
version:
13+
description: 'Version label to add (e.g. v0.14)'
14+
required: true
15+
url:
16+
description: 'URL for this version'
17+
required: true
18+
default: 'https://projectcapsule.dev'
19+
20+
concurrency:
21+
group: ${{ github.workflow }}-${{ github.ref }}
22+
cancel-in-progress: true
23+
24+
jobs:
25+
sync:
26+
name: Sync version switcher
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: write
30+
pull-requests: write
31+
steps:
32+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33+
with:
34+
fetch-depth: 0
35+
36+
- name: Determine version to sync
37+
id: ver
38+
run: |
39+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
40+
echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
41+
echo "url=${{ inputs.url }}" >> "$GITHUB_OUTPUT"
42+
else
43+
# Use github.event.before as the reliable baseline for the push.
44+
# HEAD~1 breaks when multiple commits land in a single push.
45+
OLD=$(git show "${{ github.event.before }}":config/_default/params.yaml 2>/dev/null \
46+
| grep '^version:' | awk '{print $2}' || echo "")
47+
NEW=$(grep '^version:' config/_default/params.yaml | awk '{print $2}')
48+
if [ "$OLD" = "$NEW" ]; then
49+
echo "Version unchanged ($NEW), nothing to sync"
50+
echo "skip=true" >> "$GITHUB_OUTPUT"
51+
exit 0
52+
fi
53+
echo "version=$NEW" >> "$GITHUB_OUTPUT"
54+
echo "url=$(grep '^url_latest_version:' config/_default/params.yaml | awk '{print $2}')" \
55+
>> "$GITHUB_OUTPUT"
56+
fi
57+
58+
- name: Open PRs for all release branches
59+
if: steps.ver.outputs.skip != 'true'
60+
env:
61+
VERSION: ${{ steps.ver.outputs.version }}
62+
URL: ${{ steps.ver.outputs.url }}
63+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64+
run: |
65+
VERSION_SLUG=$(echo "$VERSION" | sed 's/^v//;s/\./-/g')
66+
REPO="${GITHUB_REPOSITORY}"
67+
68+
# Ensure all remote release branches are available locally
69+
git fetch --prune origin
70+
71+
# Find all versioned release branches (release/0.x), skip preview branches
72+
for branch in $(git branch -r | grep -E 'origin/release/[0-9]' | sed 's|.*origin/||'); do
73+
echo "::group::$branch"
74+
git checkout -B "$branch" "origin/$branch"
75+
76+
WORK_BRANCH="chore/add-${VERSION_SLUG}-to-${branch//\//-}"
77+
78+
# Determine whether to open a new PR or update an existing one
79+
if git ls-remote --exit-code origin "refs/heads/${WORK_BRANCH}" > /dev/null 2>&1; then
80+
PR_ACTION="update"
81+
else
82+
PR_ACTION="create"
83+
fi
84+
85+
python3 - <<'PYEOF'
86+
import os, re
87+
88+
version = os.environ['VERSION']
89+
url = os.environ['URL']
90+
91+
with open('config/_default/params.yaml') as f:
92+
content = f.read()
93+
94+
# Repoint any version still pointing to projectcapsule.dev to its
95+
# frozen release subdomain (it was a previous latest, now archived).
96+
def frozen_url(ver):
97+
slug = ver.lstrip('v').replace('.', '-')
98+
return f"https://release-{slug}.projectcapsule.dev"
99+
100+
content = re.sub(
101+
r'^(- version: (v[\d.]+)\n url: https://projectcapsule\.dev)$',
102+
lambda m: f"- version: {m.group(2)}\n url: {frozen_url(m.group(2))}",
103+
content,
104+
flags=re.MULTILINE,
105+
)
106+
107+
# Insert the new version only if not already present (idempotent).
108+
if not re.search(r'^- version: ' + re.escape(version) + r'$', content, re.MULTILINE):
109+
new_entry = f"- version: {version}\n url: {url}\n"
110+
content = re.sub(
111+
r'(^versions:\n)',
112+
r'\g<1>' + new_entry,
113+
content,
114+
flags=re.MULTILINE,
115+
count=1,
116+
)
117+
118+
with open('config/_default/params.yaml', 'w') as f:
119+
f.write(content)
120+
PYEOF
121+
122+
# Check if there are actual changes vs the release branch HEAD
123+
if git diff --quiet HEAD -- config/_default/params.yaml; then
124+
echo "Already in desired state, skipping ${branch}"
125+
git restore config/_default/params.yaml
126+
echo "::endgroup::"
127+
continue
128+
fi
129+
130+
# Save desired content to a temp file (preserves trailing newline)
131+
DESIRED_FILE=$(mktemp)
132+
cat config/_default/params.yaml > "$DESIRED_FILE"
133+
git restore config/_default/params.yaml
134+
135+
# --- API-based commit (GitHub auto-verifies; no GPG key needed) ---
136+
137+
# Get the release branch HEAD and its tree
138+
BASE_SHA=$(gh api "repos/${REPO}/branches/${branch}" --jq '.commit.sha')
139+
BASE_TREE_SHA=$(gh api "repos/${REPO}/git/commits/${BASE_SHA}" --jq '.tree.sha')
140+
141+
# Upload the modified file as a blob
142+
CONTENT_B64=$(base64 -w 0 < "$DESIRED_FILE")
143+
rm -f "$DESIRED_FILE"
144+
BLOB_SHA=$(gh api "repos/${REPO}/git/blobs" \
145+
-f content="$CONTENT_B64" \
146+
-f encoding="base64" \
147+
--jq '.sha')
148+
149+
# Create a new tree with the updated file
150+
NEW_TREE_SHA=$(jq -n \
151+
--arg base_tree "$BASE_TREE_SHA" \
152+
--arg blob "$BLOB_SHA" \
153+
'{base_tree:$base_tree,tree:[{path:"config/_default/params.yaml",mode:"100644",type:"blob",sha:$blob}]}' | \
154+
gh api "repos/${REPO}/git/trees" --input - --jq '.sha')
155+
156+
# Build commit message with Signed-off-by for DCO
157+
COMMIT_MSG=$(printf 'chore: add %s to version switcher in %s\n\nSigned-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>' \
158+
"$VERSION" "$branch")
159+
160+
# Create the commit (GitHub signs API commits automatically → Verified)
161+
NEW_COMMIT_SHA=$(jq -n \
162+
--arg msg "$COMMIT_MSG" \
163+
--arg tree "$NEW_TREE_SHA" \
164+
--arg parent "$BASE_SHA" \
165+
'{message:$msg,tree:$tree,parents:[$parent]}' | \
166+
gh api "repos/${REPO}/git/commits" --input - --jq '.sha')
167+
168+
# Create or force-update the work branch ref
169+
if [ "$PR_ACTION" = "update" ]; then
170+
gh api "repos/${REPO}/git/refs/heads/${WORK_BRANCH}" \
171+
-X PATCH -f sha="$NEW_COMMIT_SHA" -F force=true
172+
echo "Updated PR branch: ${WORK_BRANCH}"
173+
else
174+
gh api "repos/${REPO}/git/refs" \
175+
-f ref="refs/heads/${WORK_BRANCH}" \
176+
-f sha="$NEW_COMMIT_SHA"
177+
gh pr create \
178+
--repo "$REPO" \
179+
--base "$branch" \
180+
--head "$WORK_BRANCH" \
181+
--title "chore: add ${VERSION} to version switcher in ${branch}" \
182+
--body "Automated PR: adds \`${VERSION}\` to the version switcher in the \`${branch}\` release docs. Triggered by: ${{ github.event_name }} (${{ github.sha }})"
183+
echo "Opened PR: ${WORK_BRANCH} -> ${branch}"
184+
fi
185+
186+
echo "::endgroup::"
187+
done

config/_default/hugo.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,11 @@ menu:
173173
url: /project/
174174
pre: <i class='fas fa-flask'></i>
175175
weight: 18
176+
177+
# Disable the file-system cache for remote resources (resources.GetRemote).
178+
# Hugo's default is maxAge: -1 (never expire), which causes stale Helm index
179+
# responses to persist across Netlify builds. Setting maxAge to 0 forces a
180+
# fresh fetch on every build so chart versions are always current.
181+
caches:
182+
getresource:
183+
maxAge: 0

config/_default/params.yaml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ version_menu: "Releases"
55
# The version number for the version of the docs represented in this doc set.
66
# Used in the "version-banner" partial to display a version number for the
77
# current doc set.
8-
version: latest
8+
version: v0.13
99
url_latest_version: https://projectcapsule.dev
1010
archived_version: false
1111
versions:
1212
- version: v0.13
13-
url: https://release-0-13.projectcapsule.dev
13+
url: https://projectcapsule.dev
1414
- version: v0.12
1515
url: https://release-0-12.projectcapsule.dev
1616
- version: v0.11
1717
url: https://release-0-11.projectcapsule.dev
1818
- version: v0.10
1919
url: https://release-0-10.projectcapsule.dev
20-
- version: latest
21-
url: https://projectcapsule.dev

0 commit comments

Comments
 (0)