Skip to content

Commit ed55643

Browse files
committed
Add workflow to automate feature release tasks in the org
This workflow takes the following inputs: * BRANCH_NAME - release branch name, e.g. 18.0-fr4 * NEW_VERSION - The new version to set in the Makefile on the main/source branch, e.g. 0.5 * FORCE_BUMP_BRANCHES - The new list of branches for the force-bump-branches.yaml workflow, e.g. ["main", "18.0-fr3"] * DRY_RUN - Bool to run in Dry mode to not push changes This workflow does the following tasks: - BRANCH_NAME updates: * checks every repository in the org for an olive branch. If it exists, the new BRANCH_NAME gets created from main * each Makefile of the BRANCH_NAME, the Makefile gets updated to set the feature branch * in case of install_yamls, the OPENSTACK_K8S_TAG gets updated in the Makefile for the feature branch - openstack-operator main branch updates: * Updates Makefile to reflect the NEW_VERSION * updates hack/fake_minor_update.sh for the NEW_VERSION * updates .github/workflows/catalog-openstack-operator-upgrades.yaml for the version change - Update openstack-k8s-operators-ci workflows * updates .github/workflows/release-branch-sync.yaml for the new BRANCH_NAME * updates .github/workflows/force-bump-branches.yaml for the new FORCE_BUMP_BRANCHES - Add rabbitmq-cluster-operator-index tag for the branch * calls .github/workflows/rabbitmq-cluster-operator-index-feature-tag.yaml to push a new tag for the BRANCHE_NAME Note: this workflow requires authentication using a GitHub App with permissions to read/write content of the repos and workflows. Depends-On: openstack-k8s-operators#135 Depends-On: openstack-k8s-operators/openstack-operator#1539 Depends-On: openstack-k8s-operators/openstack-operator#1540 Jira: OSPRH-18397 Assisted-by: gemini Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
1 parent 338876c commit ed55643

1 file changed

Lines changed: 369 additions & 0 deletions

File tree

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
# .github/workflows/create-release-branch-v1.yml
2+
name: Create Feature Branches in Repos v1
3+
4+
on:
5+
# This workflow can be run manually from the Actions tab.
6+
workflow_dispatch:
7+
inputs:
8+
BRANCH_NAME:
9+
description: 'The name of the new feature/release branch to create, e.g. 18.0-fr3 .'
10+
required: true
11+
type: string
12+
NEW_VERSION:
13+
description: 'The new version to set in the Makefile on the main/source branch, e.g. 0.5 .'
14+
required: true
15+
type: string
16+
FORCE_BUMP_BRANCHES:
17+
description: 'The new list of branches for the force-bump-branches.yaml workflow, e.g. ["main", "18.0-fr3"] .'
18+
required: true
19+
type: string
20+
DRY_RUN:
21+
description: 'Test Run! Run without pushing any changes. Disable to apply changes.'
22+
required: true
23+
type: boolean
24+
default: true
25+
26+
jobs:
27+
create-branches:
28+
runs-on: ubuntu-latest
29+
env:
30+
# Use current org as the ORG_NAME
31+
ORG_NAME: ${{ github.repository_owner }}
32+
# --- CONFIGURATION FROM INPUTS ---
33+
BRANCH_NAME: ${{ inputs.BRANCH_NAME }}
34+
NEW_VERSION: ${{ inputs.NEW_VERSION }}
35+
FORCE_BUMP_BRANCHES: ${{ inputs.FORCE_BUMP_BRANCHES }}
36+
DRY_RUN: ${{ inputs.DRY_RUN }}
37+
# 'The name of the version variable in the Makefile.'
38+
VERSION_VARIABLE: 'VERSION'
39+
# 'The name of the branch variable in the Makefile.'
40+
BRANCH_VARIABLE: 'BRANCH'
41+
# 'The name of the OpenStack K8s tag variable in the Makefile (for install_yamls).'
42+
OPENSTACK_K8S_TAG_VARIABLE: 'OPENSTACK_K8S_TAG'
43+
# 'The branch that must exist in a repo for it to be processed.'
44+
TRIGGER_BRANCH: 'olive'
45+
# 'The branch to create the new branch from.'
46+
SOURCE_BRANCH: 'main'
47+
steps:
48+
- name: Generate a token from the GitHub App
49+
uses: actions/create-github-app-token@v2
50+
id: app-token
51+
with:
52+
app-id: ${{ secrets.APP_ID }}
53+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
54+
# request an app token for the org instead of just the repo
55+
owner: ${{ env.ORG_NAME }}
56+
57+
- name: Get GitHub App User ID
58+
id: get-user-id
59+
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
60+
env:
61+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
62+
63+
- name: Install yq
64+
run: |
65+
sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq
66+
sudo chmod +x /usr/bin/yq
67+
echo "yq version $(yq --version) installed."
68+
69+
- name: Validate Inputs
70+
run: |
71+
set -e
72+
if ! echo "${FORCE_BUMP_BRANCHES}" | yq -e 'tag == "!!seq"' >/dev/null 2>&1; then
73+
echo "Error: The 'FORCE_BUMP_BRANCHES' input "${FORCE_BUMP_BRANCHES}" is not a valid YAML array."
74+
echo "Please provide it in the format [\"item1\", \"item2\"]."
75+
exit 1
76+
fi
77+
78+
if [[ -z "$BRANCH_NAME" ]] || [[ -z "$NEW_VERSION" ]]; then
79+
echo "Error: Required inputs BRANCH_NAME or NEW_VERSION were not provided."
80+
exit 1
81+
fi
82+
echo "Inputs are valid."
83+
84+
- name: Checkout code (for the workflow itself)
85+
uses: actions/checkout@v4
86+
with:
87+
token: ${{ steps.app-token.outputs.token }}
88+
89+
- name: Create Function Library
90+
run: |
91+
set -e
92+
93+
# Use a heredoc to write a multi-line script file.
94+
# This file will contain all your reusable functions.
95+
cat <<'EOF' > workflow_functions.sh
96+
#!/bin/bash
97+
98+
# A function to handle committing and pushing changes.
99+
# Arguments: 1: Commit Message, 2: File(s) to add, 3: Branch to push
100+
commit_and_push() {
101+
local commit_message="$1"
102+
local files_to_add="$2"
103+
local branch_to_push="$3"
104+
105+
if ! git diff --quiet -- $files_to_add; then
106+
echo "Showing changes for branch '${branch_to_push}':"
107+
git diff -- $files_to_add
108+
if [ "$DRY_RUN" = "true" ]; then
109+
echo "DRY RUN: Commit '${commit_message}' not created for branch '${branch_to_push}'."
110+
else
111+
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
112+
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
113+
git add $files_to_add
114+
git commit -m "$commit_message"
115+
echo "Pushing changes to ${branch_to_push}..."
116+
git push origin ${branch_to_push}
117+
fi
118+
else
119+
echo "No changes detected in '${files_to_add}'. Nothing to commit."
120+
fi
121+
}
122+
123+
# Updates a value in a YAML file, automatically detecting the type of the existing node.
124+
# Arguments: 1: File Path, 2: YQ Target Path, 3: New Value
125+
update_yaml_value() {
126+
local file_path="$1"
127+
local yq_path="$2"
128+
local new_value="$3"
129+
130+
if [ ! -f "${file_path}" ]; then echo "Error: File '${file_path}' not found. Skipping."; return; fi
131+
if ! yq -e "(${yq_path}) != null" "${file_path}" >/dev/null; then echo "Error: Path '${yq_path}' not found in '${file_path}'."; return; fi
132+
133+
# Get the tag of the existing node to determine its type
134+
local current_tag
135+
current_tag=$(yq e "(${yq_path}) | tag" "${file_path}")
136+
137+
if [ "$current_tag" = "!!seq" ]; then
138+
# It's an array, treat new value as a literal so yq can parse it
139+
echo "Path '${yq_path}' is an array. Replacing with new value: ${new_value}"
140+
yq -i "${yq_path} = ${new_value}" "${file_path}"
141+
else
142+
# It's a scalar (string, int, etc.), treat new value as a string
143+
echo "Path '${yq_path}' is a scalar. Updating from '${current_value}' to '${new_value}'..."
144+
yq -i "${yq_path} = \"${new_value}\"" "${file_path}"
145+
fi
146+
}
147+
148+
# Reads a value from a YAML file using a yq path.
149+
# Arguments: 1: File Path, 2: YQ Target Path
150+
get_yaml_value() {
151+
local file_path="$1"
152+
local yq_path="$2"
153+
154+
# Check if the file exists
155+
if [ ! -f "${file_path}" ]; then echo "Error: File '${file_path}' not found. Skipping." >&2; return; fi
156+
if ! yq -e "(${yq_path}) != null" "${file_path}" >/dev/null; then echo "Error: Path '${yq_path}' not found in '${file_path}'."; return; fi
157+
158+
# If checks pass, get and echo the value
159+
yq e "${yq_path}" "${file_path}"
160+
}
161+
EOF
162+
163+
- name: Create release branches and update the release branch
164+
env:
165+
# Use the token generated from the GitHub App
166+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
167+
# GH_TOKEN: ${{ secrets.ORG_ADMIN_PAT }}
168+
run: |
169+
set -e # Exit immediately if a command exits with a non-zero status.
170+
source ./workflow_functions.sh
171+
172+
if [ "$DRY_RUN" = "true" ]; then
173+
echo "!!! --- Running in DRY RUN mode. No changes will be pushed. --- !!!"
174+
fi
175+
176+
echo "Running with the following configuration:"
177+
echo "Organization: ${ORG_NAME}"
178+
echo "Feature Branch Name: ${BRANCH_NAME}"
179+
echo "New Version on main: ${NEW_VERSION}"
180+
echo "New FORCE_BUMP_BRANCHES: ${FORCE_BUMP_BRANCHES}"
181+
echo "Trigger Branch: ${TRIGGER_BRANCH}"
182+
echo "Source Branch: ${SOURCE_BRANCH}"
183+
echo "-------------------------------------------"
184+
185+
echo "Searching for repositories in organization '${ORG_NAME}'..."
186+
repos=$(gh repo list $ORG_NAME --limit 1000 --json name --jq '.[] | .name')
187+
188+
if [ -z "$repos" ]; then
189+
echo "No repositories found in the organization or permissions are missing."
190+
exit 1
191+
fi
192+
193+
created_repos=""
194+
195+
for repo_name in $repos; do
196+
echo "--- Checking REPOSITORY: ${repo_name} ---"
197+
198+
if [[ "$repo_name" == "rabbitmq-cluster-operator" ]]; then
199+
continue
200+
fi
201+
202+
if gh api "repos/${ORG_NAME}/${repo_name}/branches/${TRIGGER_BRANCH}" >/dev/null 2>&1; then
203+
echo "✅ Trigger branch '${TRIGGER_BRANCH}' found in ${repo_name}."
204+
205+
TEMP_DIR=$(mktemp -d)
206+
echo "Cloning repository into ${TEMP_DIR}..."
207+
git clone "https://x-access-token:${GH_TOKEN}@github.com/${ORG_NAME}/${repo_name}.git" "$TEMP_DIR"
208+
209+
cd "$TEMP_DIR"
210+
211+
if ! git show-ref --verify --quiet "refs/remotes/origin/${SOURCE_BRANCH}"; then
212+
echo "⚠️ Source branch '${SOURCE_BRANCH}' does not exist in ${repo_name}. Skipping."
213+
cd .. && rm -rf "$TEMP_DIR"
214+
continue
215+
fi
216+
217+
if git show-ref --verify --quiet "refs/remotes/origin/${BRANCH_NAME}"; then
218+
echo "ℹ️ Branch '${BRANCH_NAME}' already exists in ${repo_name}. Skipping this repository."
219+
cd .. && rm -rf "$TEMP_DIR"
220+
continue
221+
fi
222+
223+
echo "Creating branch '${BRANCH_NAME}' from '${SOURCE_BRANCH}'..."
224+
git checkout ${SOURCE_BRANCH}
225+
git checkout -b ${BRANCH_NAME}
226+
227+
if [ -f "Makefile" ]; then
228+
echo "Makefile found. Checking for variables to update on new branch..."
229+
if grep -qE "^${BRANCH_VARIABLE}\s*(\?=|=)" Makefile; then
230+
sed -i -E "s/^(${BRANCH_VARIABLE}\s*(\?=|=)\s*).*/\1${BRANCH_NAME}/" Makefile
231+
fi
232+
if [[ "$repo_name" == "install_yamls" ]]; then
233+
if grep -qE "^${OPENSTACK_K8S_TAG_VARIABLE}\s*(\?=|=)" Makefile; then
234+
sed -i -E "s/^((${OPENSTACK_K8S_TAG_VARIABLE})\s*(\?=|=)\s*).*/\1${BRANCH_NAME}-latest/" Makefile
235+
fi
236+
fi
237+
commit_and_push "[${BRANCH_NAME}] Update Makefile for ${BRANCH_NAME}" "Makefile" "${BRANCH_NAME}"
238+
else
239+
echo "No Makefile found. No changes will be committed to new branch."
240+
fi
241+
242+
created_repos+="- ${repo_name}\n"
243+
244+
cd .. && rm -rf "$TEMP_DIR"
245+
else
246+
echo "❌ Trigger branch '${TRIGGER_BRANCH}' not found in ${repo_name}. Skipping."
247+
fi
248+
done
249+
250+
echo "-------------------------------------------"
251+
if [ "$DRY_RUN" = "true" ]; then
252+
echo "Summary of repos where the new branch ${BRANCH_NAME} would have been created (DRY RUN):"
253+
else
254+
echo "Summary of repos where the new branch ${BRANCH_NAME} was created:"
255+
fi
256+
257+
if [[ -n "$created_repos" ]]; then
258+
echo -e "$created_repos"
259+
else
260+
echo "No new branches were created."
261+
fi
262+
263+
- name: Update openstack-operator main branch
264+
env:
265+
# Use the token generated from the GitHub App
266+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
267+
run: |
268+
set -e # Exit immediately if a command exits with a non-zero status.
269+
source ./workflow_functions.sh
270+
271+
echo "--- Checking REPOSITORY: openstack-k8s-operators-ci ---"
272+
TEMP_DIR=$(mktemp -d)
273+
echo "Cloning repository into ${TEMP_DIR}..."
274+
git clone "https://x-access-token:${GH_TOKEN}@github.com/${ORG_NAME}/openstack-operator.git" "$TEMP_DIR"
275+
cd "$TEMP_DIR"
276+
277+
# Update workflows on the main branch
278+
git checkout main
279+
git pull origin main
280+
281+
# change to the Makefile
282+
if [ -f "Makefile" ]; then
283+
FULL_MAKEFILE_VERSION="${NEW_VERSION}.0"
284+
if grep -qE "^${VERSION_VARIABLE}\s*(:=|\?=|=)" Makefile; then
285+
sed -i -E "s/^(${VERSION_VARIABLE}\s*(:=|\?=|=)).*/\1 ${FULL_MAKEFILE_VERSION}/" Makefile
286+
commit_and_push "Bump Makefile version to ${FULL_MAKEFILE_VERSION}" "Makefile" "${SOURCE_BRANCH}"
287+
else
288+
echo "Variable '${VERSION_VARIABLE}' not found. No changes pushed to ${SOURCE_BRANCH}."
289+
fi
290+
else
291+
echo "No Makefile found. No changes pushed to ${SOURCE_BRANCH}."
292+
fi
293+
294+
# change to hack/fake_minor_update.sh
295+
if [ -f "hack/fake_minor_update.sh" ]; then
296+
if grep -qE "^${VERSION_VARIABLE}=" hack/fake_minor_update.sh; then
297+
sed -i -E "s/^(${VERSION_VARIABLE}=).*/\1${NEW_VERSION}/" hack/fake_minor_update.sh
298+
commit_and_push "Bump fake_minor_update.sh version to ${NEW_VERSION}" "hack/fake_minor_update.sh" "${SOURCE_BRANCH}"
299+
else
300+
echo "Variable '${VERSION_VARIABLE}' not found. No changes pushed to ${SOURCE_BRANCH}."
301+
fi
302+
else
303+
echo "No hack/fake_minor_update.sh found. No changes pushed to ${SOURCE_BRANCH}."
304+
fi
305+
306+
# changes to .github/workflows/catalog-openstack-operator-upgrades.yaml
307+
WORKFLOW_FILE=".github/workflows/catalog-openstack-operator-upgrades.yaml"
308+
if [ -f "${WORKFLOW_FILE}" ]; then
309+
# 1. Find the index of the step to update
310+
index=$(yq '.jobs.build-catalog.steps | to_entries | .[] | select(.value.name == "Create the catalog index") | .key' ${WORKFLOW_FILE})
311+
312+
# 2. Check if an index was found
313+
if [ -z "$index" ]; then
314+
echo "Step 'Create the catalog index' not found."
315+
exit 1
316+
fi
317+
318+
echo "Found 'Create the catalog index' step at index: $index"
319+
CURRENT_VERSION=$(get_yaml_value "${WORKFLOW_FILE}" ".jobs.build-catalog.steps[$index].env.MAIN_VERSION")
320+
FULL_NEW_VERSION="${NEW_VERSION}.0"
321+
# 3. Use the index to update the 'image' field in place (-i)
322+
update_yaml_value "${WORKFLOW_FILE}" ".jobs.build-catalog.steps[$index].env.MAIN_VERSION" "${FULL_NEW_VERSION}"
323+
update_yaml_value "${WORKFLOW_FILE}" ".jobs.build-catalog.steps[$index].env.FEATURE_RELEASE_VERSION" "${CURRENT_VERSION}"
324+
update_yaml_value "${WORKFLOW_FILE}" ".jobs.build-catalog.steps[$index].env.FEATURE_RELEASE_BRANCH" "${BRANCH_NAME}"
325+
commit_and_push "Bump version in ${WORKFLOW_FILE} to ${FULL_NEW_VERSION}" "${WORKFLOW_FILE}" "${SOURCE_BRANCH}"
326+
else
327+
echo "No ${WORKFLOW_FILE} found. No changes pushed to ${SOURCE_BRANCH}."
328+
fi
329+
330+
cd .. && rm -rf "$TEMP_DIR"
331+
332+
- name: Update openstack-k8s-operators-ci workflows
333+
env:
334+
# Use the token generated from the GitHub App
335+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
336+
run: |
337+
set -e # Exit immediately if a command exits with a non-zero status.
338+
source ./workflow_functions.sh
339+
340+
echo "--- Checking REPOSITORY: openstack-k8s-operators-ci ---"
341+
TEMP_DIR=$(mktemp -d)
342+
echo "Cloning repository into ${TEMP_DIR}..."
343+
git clone "https://x-access-token:${GH_TOKEN}@github.com/${ORG_NAME}/openstack-k8s-operators-ci.git" "$TEMP_DIR"
344+
cd "$TEMP_DIR"
345+
346+
# Update workflows on the main branch
347+
git checkout main
348+
git pull origin main
349+
350+
update_yaml_value ".github/workflows/release-branch-sync.yaml" ".on.workflow_call.inputs.source_branch.default" "${BRANCH_NAME}"
351+
update_yaml_value ".github/workflows/force-bump-branches.yaml" ".jobs.trigger-jobs.strategy.matrix.branch" "${FORCE_BUMP_BRANCHES}"
352+
353+
# Commit and push changes if any were made
354+
commit_and_push "ci: Update workflow defaults for ${BRANCH_NAME}" ".github/workflows/" "${SOURCE_BRANCH}"
355+
356+
cd .. && rm -rf "$TEMP_DIR"
357+
echo "Workflow finished."
358+
359+
retag-and-push-rabbitmq-cluster-operator-index:
360+
if: inputs.DRY_RUN == false
361+
needs: create-branches
362+
uses: ./.github/workflows/rabbitmq-cluster-operator-index-feature-tag.yaml
363+
with:
364+
new_tag: "${{ inputs.BRANCH_NAME }}-latest"
365+
secrets:
366+
# Pass secrets as before
367+
IMAGENAMESPACE: ${{ secrets.IMAGENAMESPACE }}
368+
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
369+
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}

0 commit comments

Comments
 (0)