Skip to content

Commit 4aacfa7

Browse files
committed
feat(ci): automate OperatorHub bundle submission in release workflow
Add OLM CSV template with version/date placeholders under config/olm/. Add generate-olm-bundle Makefile target to produce versioned bundles. Add hack/operatorhub-pr.sh to create/update PRs against k8s-operatorhub/community-operators from the release workflow. Add operatorhub-pr job to release.yaml (continue-on-error, never blocks). Each version gets its own branch and PR. Older version PRs are left open so customers on prior major/minor lines can still receive hotfixes via OLM semver-mode upgrade graph. Requires OPERATORHUB_TOKEN secret (PAT with public_repo scope). Closes #131 Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent da6913b commit 4aacfa7

6 files changed

Lines changed: 578 additions & 0 deletions

File tree

.github/workflows/release.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,40 @@ jobs:
353353
secrets:
354354
registry-password: ${{ secrets.GITHUB_TOKEN }}
355355

356+
# Submit an OLM bundle PR to k8s-operatorhub/community-operators.
357+
# Uses continue-on-error so OperatorHub failures never block releases.
358+
operatorhub-pr:
359+
name: OperatorHub PR
360+
runs-on: ubuntu-latest
361+
timeout-minutes: 10
362+
needs: [release]
363+
if: ${{ !cancelled() && needs.release.result == 'success' }}
364+
continue-on-error: true
365+
permissions:
366+
contents: read
367+
steps:
368+
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
369+
with:
370+
egress-policy: audit
371+
372+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
373+
with:
374+
ref: ${{ needs.release.outputs.tag }}
375+
376+
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
377+
with:
378+
go-version: ${{ env.GO_VERSION }}
379+
cache: true
380+
381+
- name: Generate OLM bundle and create PR
382+
env:
383+
VERSION: ${{ needs.release.outputs.tag }}
384+
GH_TOKEN: ${{ secrets.OPERATORHUB_TOKEN }}
385+
run: |
386+
# Strip 'v' prefix from tag
387+
export VERSION="${VERSION#v}"
388+
hack/operatorhub-pr.sh
389+
356390
# Sync docker/README.md to the Docker Hub repository description.
357391
dockerhub-readme:
358392
name: Docker Hub README

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,22 @@ build-crds: manifests ## Generate standalone CRDs bundle for manual upgrades
390390
mkdir -p dist
391391
cat charts/attune/crds/*.yaml > dist/crds.yaml
392392

393+
.PHONY: generate-olm-bundle
394+
generate-olm-bundle: manifests ## Generate OLM bundle for OperatorHub submission
395+
@VERSION=$${VERSION:-$(shell git describe --tags --abbrev=0 | sed "s/^v//")} && \
396+
DATE=$$(date -u +%Y-%m-%dT00:00:00Z) && \
397+
BUNDLE_DIR=dist/olm-bundle/$$VERSION && \
398+
echo "Generating OLM bundle for version $$VERSION..." && \
399+
mkdir -p "$$BUNDLE_DIR/manifests" "$$BUNDLE_DIR/metadata" && \
400+
sed "s/__VERSION__/$$VERSION/g; s/__DATE__/$$DATE/g" \
401+
config/olm/template/manifests/attune-operator.clusterserviceversion.yaml \
402+
> "$$BUNDLE_DIR/manifests/attune-operator.clusterserviceversion.yaml" && \
403+
cp config/olm/template/metadata/annotations.yaml "$$BUNDLE_DIR/metadata/" && \
404+
cp config/crd/bases/attune.io_attunepolicies.yaml "$$BUNDLE_DIR/manifests/" && \
405+
cp config/crd/bases/attune.io_attunedefaults.yaml "$$BUNDLE_DIR/manifests/" && \
406+
cp config/crd/bases/attune.io_attunenamespacedefaults.yaml "$$BUNDLE_DIR/manifests/" && \
407+
echo "OLM bundle generated at $$BUNDLE_DIR"
408+
393409
##@ Tools
394410

395411
# Version-aware tool installs: embed version in binary filename so a version

config/olm/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
updateGraph: semver-mode

config/olm/template/manifests/attune-operator.clusterserviceversion.yaml

Lines changed: 395 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
annotations:
3+
operators.operatorframework.io.bundle.mediatype.v1: registry+v1
4+
operators.operatorframework.io.bundle.manifests.v1: manifests/
5+
operators.operatorframework.io.bundle.metadata.v1: metadata/
6+
operators.operatorframework.io.bundle.package.v1: attune
7+
operators.operatorframework.io.bundle.channels.v1: stable
8+
operators.operatorframework.io.bundle.channel.default.v1: stable

hack/operatorhub-pr.sh

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env bash
2+
# Create or update a PR to k8s-operatorhub/community-operators for a new Attune release.
3+
#
4+
# Usage: VERSION=0.1.7 GH_TOKEN=<pat> hack/operatorhub-pr.sh
5+
#
6+
# Required env vars:
7+
# VERSION - Release version without 'v' prefix (e.g., 0.1.7)
8+
# GH_TOKEN - GitHub PAT with public_repo scope for the fork owner
9+
#
10+
# Optional env vars:
11+
# FORK_OWNER - GitHub user owning the fork (default: SebTardif)
12+
# GIT_USER_NAME - Git commit author name (default: github-actions[bot])
13+
# GIT_USER_EMAIL - Git commit author email (default: 41898282+github-actions[bot]@users.noreply.github.com)
14+
# BUNDLE_DIR - Pre-generated bundle path (default: generates via make)
15+
16+
set -Eeuo pipefail
17+
18+
: "${VERSION:?VERSION is required (e.g., 0.1.7)}"
19+
: "${GH_TOKEN:?GH_TOKEN is required}"
20+
21+
FORK_OWNER="${FORK_OWNER:-SebTardif}"
22+
GIT_USER_NAME="${GIT_USER_NAME:-github-actions[bot]}"
23+
GIT_USER_EMAIL="${GIT_USER_EMAIL:-41898282+github-actions[bot]@users.noreply.github.com}"
24+
UPSTREAM_REPO="k8s-operatorhub/community-operators"
25+
FORK_REPO="${FORK_OWNER}/community-operators"
26+
BRANCH="attune-v${VERSION}"
27+
OPERATOR_DIR="operators/attune"
28+
29+
# Generate the OLM bundle if not pre-generated
30+
BUNDLE_DIR="${BUNDLE_DIR:-dist/olm-bundle/${VERSION}}"
31+
if [ ! -d "${BUNDLE_DIR}/manifests" ]; then
32+
echo "Generating OLM bundle..."
33+
make generate-olm-bundle VERSION="${VERSION}"
34+
fi
35+
36+
# Verify bundle contents
37+
for f in \
38+
"${BUNDLE_DIR}/manifests/attune-operator.clusterserviceversion.yaml" \
39+
"${BUNDLE_DIR}/manifests/attune.io_attunepolicies.yaml" \
40+
"${BUNDLE_DIR}/manifests/attune.io_attunedefaults.yaml" \
41+
"${BUNDLE_DIR}/manifests/attune.io_attunenamespacedefaults.yaml" \
42+
"${BUNDLE_DIR}/metadata/annotations.yaml"; do
43+
if [ ! -f "$f" ]; then
44+
echo "ERROR: Missing bundle file: $f" >&2
45+
exit 1
46+
fi
47+
done
48+
echo "Bundle verified: ${BUNDLE_DIR}"
49+
50+
# Clone the fork (shallow is fine here since we reset to upstream/main)
51+
WORK_DIR=$(mktemp -d)
52+
trap 'rm -rf "${WORK_DIR}"' EXIT
53+
54+
echo "Cloning fork ${FORK_REPO}..."
55+
cd "${WORK_DIR}"
56+
git clone --depth=1 "https://x-access-token:${GH_TOKEN}@github.com/${FORK_REPO}.git" community-operators
57+
cd community-operators
58+
59+
git config user.name "${GIT_USER_NAME}"
60+
git config user.email "${GIT_USER_EMAIL}"
61+
62+
# Reset to upstream main for a clean base
63+
git remote add upstream "https://github.com/${UPSTREAM_REPO}.git"
64+
git fetch upstream main --depth=1
65+
git checkout -B "${BRANCH}" upstream/main
66+
67+
# Copy the bundle
68+
mkdir -p "${OPERATOR_DIR}/${VERSION}/manifests" "${OPERATOR_DIR}/${VERSION}/metadata"
69+
cp "${OLDPWD}/${BUNDLE_DIR}/manifests/"* "${OPERATOR_DIR}/${VERSION}/manifests/"
70+
cp "${OLDPWD}/${BUNDLE_DIR}/metadata/"* "${OPERATOR_DIR}/${VERSION}/metadata/"
71+
72+
# Ensure ci.yaml exists at the operator root
73+
if [ ! -f "${OPERATOR_DIR}/ci.yaml" ]; then
74+
echo "updateGraph: semver-mode" > "${OPERATOR_DIR}/ci.yaml"
75+
fi
76+
77+
# Commit with DCO sign-off
78+
git add "${OPERATOR_DIR}/"
79+
git commit -s -m "operator attune (${VERSION})"
80+
81+
# Force-push (creates or updates the branch)
82+
git push --force origin "${BRANCH}"
83+
echo "Pushed branch ${BRANCH} to ${FORK_REPO}"
84+
85+
# Create or update the PR
86+
PR_TITLE="operator [U] [CI] attune (${VERSION})"
87+
PR_BODY="### New Submission
88+
89+
**Operator:** attune
90+
**Version:** ${VERSION}
91+
92+
Update Attune operator to version ${VERSION}.
93+
94+
See [release notes](https://github.com/attune-io/attune/releases/tag/v${VERSION}) for changes.
95+
96+
---
97+
*This PR was automatically created by the Attune release workflow.*"
98+
99+
# Check if a PR already exists for this branch
100+
EXISTING_PR=$(gh pr list \
101+
--repo "${UPSTREAM_REPO}" \
102+
--head "${FORK_OWNER}:${BRANCH}" \
103+
--state open \
104+
--json number \
105+
--jq '.[0].number // empty' 2>/dev/null || true)
106+
107+
if [ -n "${EXISTING_PR}" ]; then
108+
echo "Updating existing PR #${EXISTING_PR}"
109+
gh pr edit "${EXISTING_PR}" \
110+
--repo "${UPSTREAM_REPO}" \
111+
--title "${PR_TITLE}" \
112+
--body "${PR_BODY}"
113+
echo "PR updated: https://github.com/${UPSTREAM_REPO}/pull/${EXISTING_PR}"
114+
else
115+
echo "Creating new PR..."
116+
PR_URL=$(gh pr create \
117+
--repo "${UPSTREAM_REPO}" \
118+
--head "${FORK_OWNER}:${BRANCH}" \
119+
--base main \
120+
--title "${PR_TITLE}" \
121+
--body "${PR_BODY}")
122+
echo "PR created: ${PR_URL}"
123+
fi

0 commit comments

Comments
 (0)