diff --git a/.github/workflows/release-graylog.yaml b/.github/workflows/release-graylog.yaml new file mode 100644 index 0000000..aa65c47 --- /dev/null +++ b/.github/workflows/release-graylog.yaml @@ -0,0 +1,168 @@ +name: Release Graylog Chart + +# Packages the chart for a release release-please has already cut, attaches the +# tarball to that release, and merges one entry into the Helm repository index on +# gh-pages. +# +# 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. +# +# 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: + 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: + # Publishes read-modify-write one shared index.yaml, so they must not overlap. + 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 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 + echo "::error::refusing to publish charts from ${{ github.repository }}" + exit 1 + fi + echo "publishing within ${{ github.repository }}" + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.tag }} + + - name: Check out the Helm repository index + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: gh-pages + path: gh-pages + + - uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 + with: + version: v3.16.4 + + # 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 }} + 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 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' -)" + 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 + + # 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 + 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 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:" + 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 96a447e..2dca29c 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@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5 id: release @@ -25,17 +31,22 @@ 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 - # 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 + # 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 d2c43a0..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@v7 - 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 }}"