Skip to content

Commit eee10fd

Browse files
olegsuclaude
andauthored
feat: implement Version Bump Automation Phase 3 (#4813)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 26abee9 commit eee10fd

10 files changed

Lines changed: 531 additions & 4 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Required env vars from PSI trigger params:
5+
# BRANCH — new minor branch name (e.g. 9.3)
6+
# NEW_VERSION — first version on that branch (e.g. 9.3.0)
7+
# REPO — repository name (e.g. cloudbeat)
8+
# WORKFLOW — must be "minor"
9+
: "${BRANCH:?BRANCH is required}"
10+
: "${NEW_VERSION:?NEW_VERSION is required}"
11+
: "${REPO:?REPO is required}"
12+
: "${WORKFLOW:?WORKFLOW is required}"
13+
14+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15+
# shellcheck source=common.sh
16+
source "${SCRIPT_DIR}/common.sh"
17+
18+
GH_REPO="elastic/${REPO}"
19+
BUMP_BRANCH="bump-to-${NEW_VERSION}"
20+
NEXT_CLOUDBEAT_VERSION="${NEW_VERSION}"
21+
BACKPORT_LABEL="backport-v${BRANCH}.0"
22+
DRY_RUN="${DRY_RUN:-false}"
23+
24+
git fetch origin main
25+
git checkout main
26+
27+
CURRENT_CLOUDBEAT_VERSION=$(grep defaultBeatVersion version/version.go | cut -f2 -d '"')
28+
29+
echo "--- Minor bump parameters"
30+
echo " REPO: ${REPO}"
31+
echo " BRANCH: ${BRANCH}"
32+
echo " CURRENT: ${CURRENT_CLOUDBEAT_VERSION}"
33+
echo " NEXT: ${NEXT_CLOUDBEAT_VERSION}"
34+
echo " BACKPORT_LABEL: ${BACKPORT_LABEL}"
35+
echo " DRY_RUN: ${DRY_RUN}"
36+
37+
setup_git_identity
38+
39+
update_mergify() {
40+
if grep -q "backport patches to ${BRANCH} branch" .mergify.yml; then
41+
echo "Mergify backport rule for ${BRANCH} already exists — skipping."
42+
return
43+
fi
44+
45+
cat >>.mergify.yml <<EOF
46+
- name: backport patches to ${BRANCH} branch
47+
conditions:
48+
- merged
49+
- label=${BACKPORT_LABEL}
50+
actions:
51+
backport:
52+
assignees:
53+
- "{{ author }}"
54+
branches:
55+
- "${BRANCH}"
56+
labels:
57+
- "backport"
58+
title: "[{{ destination_branch }}](backport #{{ number }}) {{ title }}"
59+
EOF
60+
61+
git add .mergify.yml
62+
git commit -m "Add mergify backport rule for ${BRANCH}"
63+
}
64+
65+
run_minor_bump() {
66+
pr_exists && return
67+
68+
git checkout -b "${BUMP_BRANCH}" origin/main
69+
70+
update_version_beat
71+
update_arm_templates "${NEXT_CLOUDBEAT_VERSION}"
72+
update_mergify
73+
74+
local body
75+
body=$(render_template "${SCRIPT_DIR}/templates/pr-body-minor.md")
76+
77+
# TODO: re-enable `--label "version-bump-auto-approve"` once the first
78+
# end-to-end manual run validates the auto-approve workflow + Mergify
79+
# auto-merge rule. Until then, the bump PR is created label-free so a
80+
# human reviews and merges it manually.
81+
if [[ "${DRY_RUN}" == "true" ]]; then
82+
echo "--- Dry run: skipping push and PR creation"
83+
gh pr create \
84+
--repo "${GH_REPO}" \
85+
--head "${BUMP_BRANCH}" \
86+
--base main \
87+
--title "Bump cloudbeat version to ${NEXT_CLOUDBEAT_VERSION}" \
88+
--body "${body}" \
89+
--label "backport-skip" \
90+
--dry-run
91+
return
92+
fi
93+
94+
git push --force origin "${BUMP_BRANCH}"
95+
gh pr create \
96+
--repo "${GH_REPO}" \
97+
--head "${BUMP_BRANCH}" \
98+
--base main \
99+
--title "Bump cloudbeat version to ${NEXT_CLOUDBEAT_VERSION}" \
100+
--body "${body}" \
101+
--label "backport-skip"
102+
}
103+
104+
run_minor_bump
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Required env vars from PSI trigger params:
5+
# BRANCH — release branch (e.g. 9.3)
6+
# NEW_VERSION — target patch version (e.g. 9.3.4)
7+
# REPO — repository name (e.g. cloudbeat)
8+
# WORKFLOW — must be "patch"
9+
: "${BRANCH:?BRANCH is required}"
10+
: "${NEW_VERSION:?NEW_VERSION is required}"
11+
: "${REPO:?REPO is required}"
12+
: "${WORKFLOW:?WORKFLOW is required}"
13+
14+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15+
# shellcheck source=common.sh
16+
source "${SCRIPT_DIR}/common.sh"
17+
18+
BASE_BRANCH="${BRANCH}"
19+
BUMP_BRANCH="bump-to-${NEW_VERSION}"
20+
GH_REPO="elastic/${REPO}"
21+
22+
git fetch origin "${BASE_BRANCH}"
23+
git checkout "${BASE_BRANCH}"
24+
25+
NEXT_CLOUDBEAT_VERSION="${NEW_VERSION}"
26+
CURRENT_CLOUDBEAT_VERSION=$(grep defaultBeatVersion version/version.go | cut -f2 -d '"')
27+
28+
DRY_RUN="${DRY_RUN:-false}"
29+
30+
echo "--- Patch bump parameters"
31+
echo " REPO: ${REPO}"
32+
echo " BRANCH: ${BASE_BRANCH}"
33+
echo " CURRENT: ${CURRENT_CLOUDBEAT_VERSION}"
34+
echo " NEXT: ${NEXT_CLOUDBEAT_VERSION}"
35+
echo " DRY_RUN: ${DRY_RUN}"
36+
37+
setup_git_identity
38+
39+
run_patch_bump() {
40+
pr_exists && return
41+
42+
git checkout -b "${BUMP_BRANCH}" "origin/${BASE_BRANCH}"
43+
44+
update_version_beat
45+
46+
local body
47+
body=$(render_template "${SCRIPT_DIR}/templates/pr-body-patch.md")
48+
49+
# TODO: re-enable `--label "version-bump-auto-approve"` once the first
50+
# end-to-end manual run validates the auto-approve workflow + Mergify
51+
# auto-merge rule. Until then, the bump PR is created label-free so a
52+
# human reviews and merges it manually.
53+
if [[ "${DRY_RUN}" == "true" ]]; then
54+
echo "--- Dry run: skipping push and PR creation"
55+
gh pr create \
56+
--repo "${GH_REPO}" \
57+
--head "${BUMP_BRANCH}" \
58+
--base "${BASE_BRANCH}" \
59+
--title "Bump cloudbeat version ${BASE_BRANCH} to ${NEXT_CLOUDBEAT_VERSION}" \
60+
--body "${body}" \
61+
--label "backport-skip" \
62+
--dry-run
63+
return
64+
fi
65+
66+
git push origin "${BUMP_BRANCH}"
67+
gh pr create \
68+
--repo "${GH_REPO}" \
69+
--head "${BUMP_BRANCH}" \
70+
--base "${BASE_BRANCH}" \
71+
--title "Bump cloudbeat version ${BASE_BRANCH} to ${NEXT_CLOUDBEAT_VERSION}" \
72+
--body "${body}" \
73+
--label "backport-skip"
74+
}
75+
76+
run_patch_bump
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env bash
2+
# Sourced by bump scripts — do not execute directly.
3+
# Expects callers to have validated: BRANCH, NEW_VERSION, REPO, WORKFLOW
4+
# and to have set: BUMP_BRANCH, NEXT_CLOUDBEAT_VERSION, GH_REPO (=elastic/${REPO})
5+
6+
# pr_exists
7+
# Returns 0 (true) if an open PR already exists for BUMP_BRANCH, 1 otherwise.
8+
pr_exists() {
9+
local existing_pr
10+
existing_pr=$(gh pr list --repo "${GH_REPO}" --head "${BUMP_BRANCH}" --state open \
11+
--json number --jq '.[0].number' 2>/dev/null || echo "")
12+
if [[ -n "${existing_pr}" ]]; then
13+
echo "INFO: PR #${existing_pr} already open for ${BUMP_BRANCH} — skipping."
14+
return 0
15+
fi
16+
return 1
17+
}
18+
19+
setup_git_identity() {
20+
git config --global user.email "cloudsecmachine@users.noreply.github.com"
21+
git config --global user.name "Cloud Security Machine"
22+
}
23+
24+
# update_version_beat
25+
# Updates defaultBeatVersion in version/version.go to NEXT_CLOUDBEAT_VERSION and stages the file.
26+
update_version_beat() {
27+
sed -i'' -E "s/const defaultBeatVersion = .*/const defaultBeatVersion = \"${NEXT_CLOUDBEAT_VERSION}\"/g" version/version.go
28+
git add version/version.go
29+
if ! git diff --cached --quiet; then
30+
git commit -m "Bump to ${NEXT_CLOUDBEAT_VERSION}"
31+
fi
32+
}
33+
34+
# update_arm_templates <version>
35+
# Updates ElasticAgentVersion in both Azure ARM templates and regenerates dev variants.
36+
update_arm_templates() {
37+
local version="$1"
38+
echo "--- Update ARM templates to ${version}"
39+
jq --indent 4 ".parameters.ElasticAgentVersion.defaultValue = \"${version}\"" \
40+
deploy/azure/ARM-for-single-account.json >tmp.json && mv tmp.json deploy/azure/ARM-for-single-account.json
41+
jq --indent 4 ".parameters.ElasticAgentVersion.defaultValue = \"${version}\"" \
42+
deploy/azure/ARM-for-organization-account.json >tmp.json && mv tmp.json deploy/azure/ARM-for-organization-account.json
43+
./deploy/azure/generate_dev_template.py --template-type single-account
44+
./deploy/azure/generate_dev_template.py --template-type organization-account
45+
git add \
46+
deploy/azure/ARM-for-single-account.json \
47+
deploy/azure/ARM-for-single-account.dev.json \
48+
deploy/azure/ARM-for-organization-account.json \
49+
deploy/azure/ARM-for-organization-account.dev.json
50+
if ! git diff --cached --quiet; then
51+
git commit -m "Update Azure ARM templates to ${version}"
52+
fi
53+
}
54+
55+
# render_template <path>
56+
# Expands ${VAR} references in a template file using the caller's environment.
57+
render_template() {
58+
local content
59+
# shellcheck disable=SC2016
60+
# Single quotes are intentional: we need a literal backslash passed to sed.
61+
content=$(sed 's/`/\\`/g' "$1")
62+
eval "cat <<__EOF__
63+
${content}
64+
__EOF__
65+
"
66+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Post-PR-merge operations for a patch version bump.
5+
# Runs after the bump PR is merged and DRA artifacts for NEW_VERSION are confirmed.
6+
#
7+
# Required env vars from PSI trigger params:
8+
# BRANCH — release branch (e.g. 9.3)
9+
# NEW_VERSION — the version that was bumped to (e.g. 9.3.4)
10+
# REPO — repository name (e.g. elastic/cloudbeat)
11+
# WORKFLOW — must be "patch"
12+
#
13+
# Secrets (from kv/ci-shared/cloudbeat/* via vault — wired in task-7):
14+
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY — for CFT upload to S3
15+
# SNYK_ORG_ID, SNYK_API_KEY, SNYK_INTEGRATION_ID — for Snyk branch monitoring
16+
: "${BRANCH:?BRANCH is required}"
17+
: "${NEW_VERSION:?NEW_VERSION is required}"
18+
: "${REPO:?REPO is required}"
19+
: "${WORKFLOW:?WORKFLOW is required}"
20+
21+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22+
# shellcheck source=common.sh
23+
source "${SCRIPT_DIR}/common.sh"
24+
25+
echo "--- Post-bump ops for ${NEW_VERSION}"
26+
27+
upload_cloud_formation_templates() {
28+
echo "--- Upload CloudFormation templates for ${NEW_VERSION}"
29+
aws configure set aws_access_key_id "${AWS_ACCESS_KEY_ID}"
30+
aws configure set aws_secret_access_key "${AWS_SECRET_ACCESS_KEY}"
31+
aws configure set region us-east-2
32+
scripts/publish_cft.sh
33+
}
34+
35+
bump_snyk_branch_monitoring() {
36+
echo "--- Update Snyk branch monitoring"
37+
local branches latest_major previous_major latest_major_latest_minor previous_major_latest_minor
38+
39+
branches=$(git branch -r | grep -Eo '[0-9]+\.[0-9]+' | sort -V | uniq)
40+
latest_major=$(echo "${branches}" | cut -d. -f1 | uniq | tail -1)
41+
previous_major=$(echo "${branches}" | cut -d. -f1 | uniq | tail -2 | head -1)
42+
latest_major_latest_minor=$(echo "${branches}" | grep -E "^${latest_major}\." | tail -1)
43+
previous_major_latest_minor=$(echo "${branches}" | grep -E "^${previous_major}\." | tail -1)
44+
45+
echo " latest: ${latest_major_latest_minor}"
46+
echo " previous: ${previous_major_latest_minor}"
47+
48+
local cloudbeat_id
49+
cloudbeat_id=$(curl -sf -X GET \
50+
"https://api.snyk.io/rest/orgs/${SNYK_ORG_ID}/targets?version=2024-05-23&display_name=cloudbeat" \
51+
-H "accept: application/vnd.api+json" \
52+
-H "authorization: ${SNYK_API_KEY}" | jq -r '.data[0].id')
53+
54+
curl -sf -X DELETE \
55+
"https://api.snyk.io/rest/orgs/${SNYK_ORG_ID}/targets/${cloudbeat_id}?version=2024-05-23" \
56+
-H "accept: application/vnd.api+json" \
57+
-H "authorization: ${SNYK_API_KEY}"
58+
59+
for branch in main "${latest_major_latest_minor}" "${previous_major_latest_minor}"; do
60+
curl -sf -X POST \
61+
"https://api.snyk.io/v1/org/${SNYK_ORG_ID}/integrations/${SNYK_INTEGRATION_ID}/import" \
62+
-H "Content-Type: application/json; charset=utf-8" \
63+
-H "Authorization: token ${SNYK_API_KEY}" \
64+
-d "{\"target\":{\"owner\":\"elastic\",\"name\":\"cloudbeat\",\"branch\":\"${branch}\"},\"exclusionGlobs\":\"deploy, scripts, tests, security-policies\"}"
65+
done
66+
}
67+
68+
upload_cloud_formation_templates
69+
bump_snyk_branch_monitoring

0 commit comments

Comments
 (0)