From 70e6701858437810e527d3e59ec3203c48cab242 Mon Sep 17 00:00:00 2001 From: alix-graylog Date: Fri, 21 Aug 2026 14:41:43 -0600 Subject: [PATCH 1/3] ci: publish the chart from release-please, retire chart-releaser release-please now creates the tag and the release, and a new reusable workflow packages the chart, attaches the tarball and merges a new entry into the Helm repository index on gh-pages. chart-releaser is deleted in the same commit: leaving it active would put two things on the same graylog-X.Y.Z name. The publish job is a workflow_call from inside the release-please run, not a release- or tag-triggered workflow. Refs created with GITHUB_TOKEN raise no workflow events, so those triggers would never fire. The index is merged, never regenerated. Packages live as release assets, so gh-pages holds no tarballs and regenerating from that directory would drop every historical entry and rewrite every URL. A guard records each (version, url) pair before the merge and fails before pushing if any would disappear or change, exempting the version being published. The artifacthub.io/changes annotation is generated here, before packaging, from the CHANGELOG.md of the tag being published, and is never committed. That is what the removal of the release-PR sync step was waiting on. --- .github/workflows/release-graylog.yaml | 166 +++++++++++++++++++++++++ .github/workflows/release-please.yaml | 25 ++-- .github/workflows/release.yaml | 30 ----- 3 files changed, 184 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/release-graylog.yaml delete mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release-graylog.yaml b/.github/workflows/release-graylog.yaml new file mode 100644 index 0000000..a14c565 --- /dev/null +++ b/.github/workflows/release-graylog.yaml @@ -0,0 +1,166 @@ +name: Release Graylog Chart + +# Packages the chart for a release release-please has already cut, attaches the +# tarball to that release, and merges a new entry into the Helm repository index +# on gh-pages. +# +# Deliberately not chart-releaser: cr's default release name is +# "{{ .Name }}-{{ .Version }}", identical to the graylog-X.Y.Z tags release-please +# owns, so cr finds the release already present and - with skip_existing - +# silently declines to upload anything. A green run, and no chart published. +# +# Called from release-please.yaml rather than triggered by a tag or release +# event: refs created with GITHUB_TOKEN raise no workflow events, so +# `on: release: published` and tag-push triggers never fire. Do not "simplify" +# this into a release-triggered workflow. + +on: + workflow_call: + inputs: + tag: + description: Release tag to attach the packaged chart to + required: true + type: string + workflow_dispatch: + inputs: + tag: + description: Existing release tag to attach the packaged chart to (e.g. graylog-2.0.0) + required: true + type: string + +permissions: + contents: write + +concurrency: + # Serialise: publishes read-modify-write a single shared index.yaml. + group: publish-chart + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + env: + CHART_DIR: charts/graylog + PACKAGE_DIR: .cr-release-packages + TAG: ${{ inputs.tag }} + steps: + # This workflow writes release assets and rewrites the published Helm + # index. A fork running it via workflow_dispatch would publish into its own + # releases while claiming the upstream index URL, so refuse outright. + - name: Guard against publishing outside the chart repository + run: | + if [[ "${{ github.repository }}" != "Graylog2/graylog-helm" ]]; then + echo "::error::refusing to publish charts from ${{ github.repository }}" + exit 1 + fi + echo "publishing within ${{ github.repository }}" + + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: Check out the Helm repository index + uses: actions/checkout@v6 + with: + ref: gh-pages + path: gh-pages + + - uses: azure/setup-helm@v5 + with: + version: v3.16.4 + + # release-please renders release notes only as Markdown, so no updater can + # write changelog content into a YAML field. The annotation is generated + # here, from the CHANGELOG.md of the tag being published, and never + # committed: ArtifactHub reads it from the packaged tarball it fetches + # through index.yaml. CHANGELOG.md therefore stays the single source of + # truth, editable on the release PR like the rest of the release. + - name: Generate the artifacthub.io/changes annotation + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python3 .github/scripts/artifacthub_changes.py \ + --chart-dir "$CHART_DIR" --repo "$GITHUB_REPOSITORY" + helm show chart "$CHART_DIR" | yq e '.annotations."artifacthub.io/changes"' - + + - name: Package the chart + run: | + helm package "$CHART_DIR" --destination "$PACKAGE_DIR" + ls -l "$PACKAGE_DIR" + + # The tag encodes the version release-please released; the chart must agree, + # or the index would advertise a version the package does not contain. + - name: Verify the packaged version matches the tag + run: | + chart_version="$(helm show chart "$CHART_DIR" | yq e '.version' -)" + tag_version="${TAG#graylog-}" + if [[ "$chart_version" != "$tag_version" ]]; then + echo "::error::Chart.yaml is $chart_version but the tag says $tag_version" + exit 1 + fi + echo "version=$chart_version" >> "$GITHUB_ENV" + + - name: Attach the package to the release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload "$TAG" "$PACKAGE_DIR"/*.tgz --clobber + + # The index is merged, never regenerated. Packages are served from the + # release assets they are attached to, so gh-pages holds no tarballs - + # regenerating the index from that directory would drop every historical + # entry. `--merge` keeps entries whose tarball is absent and refreshes the + # URL of the version being published. + - name: Merge the new version into the Helm repository index + run: | + index=gh-pages/index.yaml + before="$(yq e '.entries.graylog[] | .version + " " + .urls[0]' "$index" | sort | sed '/^$/d')" + + helm repo index "$PACKAGE_DIR" \ + --merge "$index" \ + --url "https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}" + + after="$(yq e '.entries.graylog[] | .version + " " + .urls[0]' "$PACKAGE_DIR/index.yaml" | sort | sed '/^$/d')" + + # Every pre-existing (version, url) pair must survive verbatim. The + # version being published is exempt: its URL is refreshed on purpose. + # This is the backstop for the one mistake that cannot be walked back + # quietly - regenerating the index instead of merging it, which drops + # every version whose tarball is not on disk, and none of them are. + lost="$(comm -23 <(printf '%s\n' "$before" | grep -v "^${version} " || true) <(printf '%s\n' "$after") || true)" + if [[ -n "$lost" ]]; then + echo "::error::index.yaml would lose or rewrite existing entries:" + printf '%s\n' "$lost" + exit 1 + fi + + cp "$PACKAGE_DIR/index.yaml" "$index" + printf '%s\n' "$after" | sed 's/^/ /' + + - name: Publish the index + run: | + cd gh-pages + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # index.yaml only. artifacthub-repo.yml carries ArtifactHub ownership + # and nothing here should touch it. + git add index.yaml + if git diff --cached --quiet; then + echo "Helm repository already publishes ${version}" + exit 0 + fi + git commit -m "chore: publish graylog ${version} to the Helm repository" + git push + + - name: Summary + run: | + { + echo "### Published \`graylog\` ${version}" + echo + echo '```' + echo "helm repo add graylog https://graylog2.github.io/graylog-helm" + echo "helm repo update graylog" + echo "helm install graylog graylog/graylog --version ${version}" + echo '```' + echo + echo "Tarball attached to [\`${TAG}\`](${{ github.server_url }}/${{ github.repository }}/releases/tag/${TAG})." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml index 0c5d263..4288b39 100644 --- a/.github/workflows/release-please.yaml +++ b/.github/workflows/release-please.yaml @@ -17,6 +17,12 @@ concurrency: jobs: release-please: runs-on: ubuntu-latest + outputs: + # Per-package outputs are keyed by package path. + graylog-released: ${{ steps.release.outputs['charts/graylog--release_created'] }} + graylog-tag: ${{ steps.release.outputs['charts/graylog--tag_name'] }} + graylog-version: ${{ steps.release.outputs['charts/graylog--version'] }} + paths-released: ${{ steps.release.outputs.paths_released }} steps: - uses: googleapis/release-please-action@v5 id: release @@ -25,13 +31,6 @@ jobs: config-file: release-please-config.json manifest-file: .release-please-manifest.json target-branch: main - # PR-only mode: chart-releaser (release.yaml) still owns the tag, the - # release and the index entry. Both use the name graylog-X.Y.Z, so if - # release-please created the release first, chart-releaser would find - # it present and — running with skip_existing: true — silently decline - # to upload the chart package. Removed in the publishing cutover, when - # chart-releaser is retired. - skip-github-release: true # No artifacthub.io/changes step here on purpose. The annotation is # generated from CHANGELOG.md when the chart is packaged, not committed @@ -39,3 +38,15 @@ jobs: # force-push of the branch, and it would make hand edits to the annotation # depend on being the last change before the merge. See # .github/scripts/artifacthub_changes.py. + + # Linked to the release rather than to a path filter or a tag push: refs + # created with GITHUB_TOKEN raise no events, so only the release-please run + # itself knows a release happened. + publish-chart: + needs: release-please + if: needs.release-please.outputs.graylog-released == 'true' + uses: ./.github/workflows/release-graylog.yaml + permissions: + contents: write + with: + tag: ${{ needs.release-please.outputs.graylog-tag }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 688930a..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Release Graylog Chart - -on: - push: - branches: - - main - paths: - - charts/graylog/Chart.yaml - workflow_dispatch: - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Configure Git - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.7.0 - with: - skip_existing: true - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" From 2df8e3a1323bc10bf0acb6f08e9ca4d108d5515d Mon Sep 17 00:00:00 2001 From: alix-graylog Date: Fri, 21 Aug 2026 16:35:21 -0600 Subject: [PATCH 2/3] ci(release): Updating publish cut over --- .github/workflows/release-graylog.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-graylog.yaml b/.github/workflows/release-graylog.yaml index a14c565..a637311 100644 --- a/.github/workflows/release-graylog.yaml +++ b/.github/workflows/release-graylog.yaml @@ -55,17 +55,17 @@ jobs: fi echo "publishing within ${{ github.repository }}" - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.tag }} - name: Check out the Helm repository index - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: gh-pages path: gh-pages - - uses: azure/setup-helm@v5 + - uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.16.4 From f7eeaaad9decdff44ba41000fde189fd6105c8d6 Mon Sep 17 00:00:00 2001 From: alix-graylog Date: Thu, 27 Aug 2026 13:35:14 -0600 Subject: [PATCH 3/3] docs(ci): reword the release workflow comments --- .github/workflows/release-graylog.yaml | 64 +++++++++++++------------- .github/workflows/release-please.yaml | 14 +++--- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/.github/workflows/release-graylog.yaml b/.github/workflows/release-graylog.yaml index 6528922..aa65c47 100644 --- a/.github/workflows/release-graylog.yaml +++ b/.github/workflows/release-graylog.yaml @@ -1,18 +1,18 @@ name: Release Graylog Chart # Packages the chart for a release release-please has already cut, attaches the -# tarball to that release, and merges a new entry into the Helm repository index -# on gh-pages. +# tarball to that release, and merges one entry into the Helm repository index on +# gh-pages. # -# Deliberately not chart-releaser: cr's default release name is -# "{{ .Name }}-{{ .Version }}", identical to the graylog-X.Y.Z tags release-please -# owns, so cr finds the release already present and - with skip_existing - -# silently declines to upload anything. A green run, and no chart published. +# This does not use chart-releaser. chart-releaser names its release +# "{{ .Name }}-{{ .Version }}", which is exactly the graylog-X.Y.Z tag +# release-please already owns, so it finds the release present and, running with +# skip_existing, declines to upload anything. The run goes green and no chart +# ships. # -# Called from release-please.yaml rather than triggered by a tag or release -# event: refs created with GITHUB_TOKEN raise no workflow events, so -# `on: release: published` and tag-push triggers never fire. Do not "simplify" -# this into a release-triggered workflow. +# release-please.yaml calls this workflow. A tag or release trigger would never +# fire, because refs created with GITHUB_TOKEN raise no workflow events. Do not +# turn this into a release-triggered workflow. on: workflow_call: @@ -32,7 +32,7 @@ permissions: contents: write concurrency: - # Serialise: publishes read-modify-write a single shared index.yaml. + # Publishes read-modify-write one shared index.yaml, so they must not overlap. group: publish-chart cancel-in-progress: false @@ -45,8 +45,9 @@ jobs: TAG: ${{ inputs.tag }} steps: # This workflow writes release assets and rewrites the published Helm - # index. A fork running it via workflow_dispatch would publish into its own - # releases while claiming the upstream index URL, so refuse outright. + # index. A fork running it through workflow_dispatch would publish into its + # own releases while claiming the upstream index URL, so it refuses to run + # anywhere else. - name: Guard against publishing outside the chart repository run: | if [[ "${{ github.repository }}" != "Graylog2/graylog-helm" ]]; then @@ -69,12 +70,12 @@ jobs: with: version: v3.16.4 - # release-please renders release notes only as Markdown, so no updater can - # write changelog content into a YAML field. The annotation is generated - # here, from the CHANGELOG.md of the tag being published, and never - # committed: ArtifactHub reads it from the packaged tarball it fetches - # through index.yaml. CHANGELOG.md therefore stays the single source of - # truth, editable on the release PR like the rest of the release. + # release-please renders release notes as Markdown only, so no updater can + # write changelog content into a YAML field. This step generates the + # annotation from the CHANGELOG.md of the tag being published and never + # commits it. ArtifactHub reads it out of the packaged tarball it fetches + # through index.yaml. CHANGELOG.md stays the single source of truth, and + # stays editable on the release PR like the rest of the release. - name: Generate the artifacthub.io/changes annotation env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -88,8 +89,8 @@ jobs: helm package "$CHART_DIR" --destination "$PACKAGE_DIR" ls -l "$PACKAGE_DIR" - # The tag encodes the version release-please released; the chart must agree, - # or the index would advertise a version the package does not contain. + # The tag encodes the version release-please released. The chart must + # agree, or the index advertises a version the package does not contain. - name: Verify the packaged version matches the tag run: | chart_version="$(helm show chart "$CHART_DIR" | yq e '.version' -)" @@ -105,11 +106,11 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh release upload "$TAG" "$PACKAGE_DIR"/*.tgz --clobber - # The index is merged, never regenerated. Packages are served from the - # release assets they are attached to, so gh-pages holds no tarballs - - # regenerating the index from that directory would drop every historical - # entry. `--merge` keeps entries whose tarball is absent and refreshes the - # URL of the version being published. + # This merges the index and never regenerates it. The release assets serve + # the packages, so gh-pages holds no tarballs, and regenerating the index + # from that directory would drop every historical entry. `--merge` keeps + # entries whose tarball is absent and refreshes the URL of the version + # being published. - name: Merge the new version into the Helm repository index run: | index=gh-pages/index.yaml @@ -121,11 +122,12 @@ jobs: after="$(yq e '.entries.graylog[] | .version + " " + .urls[0]' "$PACKAGE_DIR/index.yaml" | sort | sed '/^$/d')" - # Every pre-existing (version, url) pair must survive verbatim. The - # version being published is exempt: its URL is refreshed on purpose. - # This is the backstop for the one mistake that cannot be walked back - # quietly - regenerating the index instead of merging it, which drops - # every version whose tarball is not on disk, and none of them are. + # Every version and URL pair that already exists must survive verbatim. + # The version being published is the one exception, because its URL is + # refreshed on purpose. This backstops the one mistake that cannot be + # walked back quietly, regenerating the index instead of merging it, + # which drops every version whose tarball is not on disk. None of them + # are. lost="$(comm -23 <(printf '%s\n' "$before" | grep -v "^${version} " || true) <(printf '%s\n' "$after") || true)" if [[ -n "$lost" ]]; then echo "::error::index.yaml would lose or rewrite existing entries:" diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml index 9a01cea..2dca29c 100644 --- a/.github/workflows/release-please.yaml +++ b/.github/workflows/release-please.yaml @@ -32,14 +32,14 @@ jobs: manifest-file: .release-please-manifest.json target-branch: main - # No artifacthub.io/changes step here on purpose. The annotation is - # generated from CHANGELOG.md when the chart is packaged, not committed - # onto the release PR: a commit here would race release-please's own - # force-push of the branch, and it would make hand edits to the annotation - # depend on being the last change before the merge. See - # .github/scripts/artifacthub_changes.py. + # No artifacthub.io/changes step here on purpose. release-graylog.yaml + # generates the annotation from CHANGELOG.md when it packages the chart, + # rather than committing it onto the release PR. A commit here would race + # release-please's own force-push of the branch, and it would make any hand + # edit to the annotation depend on being the last change before the merge. + # See .github/scripts/artifacthub_changes.py. - # Linked to the release rather than to a path filter or a tag push: refs + # Linked to the release rather than to a path filter or a tag push. Refs # created with GITHUB_TOKEN raise no events, so only the release-please run # itself knows a release happened. publish-chart: