Skip to content

ci(artifacthub): structured changelog with link on major - #1925

Merged
traefiker merged 8 commits into
traefik:masterfrom
mloiseleur:ci/artifacthub
Aug 5, 2026
Merged

ci(artifacthub): structured changelog with link on major#1925
traefiker merged 8 commits into
traefik:masterfrom
mloiseleur:ci/artifacthub

Conversation

@mloiseleur

@mloiseleur mloiseleur commented Jul 8, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Emit the structured artifacthub.io/changes annotation from hack/changelog.sh:

  • Patch/minor → per-kind entries (feat→added, fix→fixed, breaking or anything else→changed), gitmoji shortcodes stripped, release commit dropped.
  • Major (X.0.0) → single changed entry linking to the release Upgrade Notes.
Generated output — last 5 versions
# 41.1.1 (patch)
  artifacthub.io/changes: |
    - kind: fixed
      description: "fix: update hardened image registry"

# 41.1.0 (minor)
  artifacthub.io/changes: |
    - kind: fixed
      description: "fix: support enabling only kubernetesIngressNGINX with rbac.namespaced"
    - kind: fixed
      description: "fix(rbac): align namespaced RBAC with kubernetesIngressNGINX namespace scoping"
    - kind: fixed
      description: "fix(ports): support IPv6 hostIP values"
    - kind: fixed
      description: "fix(deployment): accept named port for healthchecksPort"
    - kind: added
      description: "feat(rbac): grant configmaps write for hub api management"
    - kind: added
      description: "feat(hub): add hardened image variant"
    - kind: added
      description: "feat(hub): add hub.enabled to run Traefik Hub in proxy mode"
    - kind: added
      description: "feat(deps): update traefik docker tag to v3.7.9"
    - kind: added
      description: "feat(deps): update traefik docker tag to v3.7.8"
    - kind: added
      description: "feat(deps): update traefik docker tag to v3.7.7"
    - kind: added
      description: "feat(deps): update ghcr.io/traefik/traefik-hub docker tag to v3.20.7"
    - kind: added
      description: "feat(CRDs): update Traefik Hub to v1.33.0"
    - kind: changed
      description: "docs: change metrics.prometheus labels types from string to bool"
    - kind: changed
      description: "docs(values): fix rendered types for nullable fields"
    - kind: changed
      description: "chore: file provider scrambled output"

# 41.0.2 (patch)
  artifacthub.io/changes: |
    - kind: fixed
      description: "fix(crds): add missing hub.traefik.io_uplinks to kustomization"
    - kind: added
      description: "feat(deps): update traefik docker tag to v3.7.6"
    - kind: added
      description: "feat(deps): update ghcr.io/traefik/traefik-hub docker tag to v3.20.6"

# 41.0.1 (patch)
  artifacthub.io/changes: |
    - kind: fixed
      description: "fix(ingressroute): fail fast on uppercase ingressRoute keys (RFC 1123)"
    - kind: added
      description: "feat(hub): support traefik hub v3.20.5"
    - kind: changed
      description: "docs(hub): deprecate inline literal token in values"

# 41.0.0 (major)
  artifacthub.io/changes: |
    - kind: changed
      description: "This is a major release with breaking changes. Read the upgrade notes before upgrading."
      links:
        - name: Upgrade Notes
          url: https://github.com/traefik/traefik-helm-chart/releases/tag/v41.0.0

Rewriting the annotation deletes only its own block, so an annotation sorting after artifacthub.io/changes is no longer dropped. An empty result aborts instead of writing a placeholder: it means Chart.yaml and Changelog.md are out of sync, and no release is changeless.

Covered by golden tests in .github/fixtures/artifacthub-changes/, run by make test-changelog, which the Test workflow calls next to make test-ns. Each case pins one behaviour: kind mapping, escaping and block replacement on a patch, the upgrade notes link on a major, a first release that is X.0.0 without being breaking, and a release whose only commit is the release itself, which is refused. The tests also parse the emitted annotation and check every kind against the ones Artifact Hub accepts, so regenerating the golden files cannot bless a broken one.

Reading the chart version moved from yq to sed: yq -r is python-yq only and mikefarah yq has no -r flag, so the script could not run on a GitHub runner. The tests do use yq, in syntax both flavours run.

Motivation

Artifact Hub buried the v41 breaking changes in a flat commit list (#1898). Artifact Hub has no "breaking" kind, so a major collapses to one entry pointing at the upgrade guide — impossible to miss.

More

  • Yes, I updated the tests accordingly
  • Yes, I updated the schema accordingly
  • Yes, I ran make test-changelog and all the tests passed

@mloiseleur mloiseleur changed the title ci(changelog): structured Artifact Hub changelog with breaking-change link on major ci(artifacthub): structured changelog with breaking-change link on major Jul 8, 2026
@mloiseleur mloiseleur changed the title ci(artifacthub): structured changelog with breaking-change link on major ci(artifacthub): structured changelog with link on major Jul 8, 2026
@darkweaver87

Copy link
Copy Markdown
Contributor

Nice one — the breaking-change link on major is exactly what we were missing after #1898. Few things I'd tweak before it goes in:

The gitmoji stripping doesn't fully work — the regex only catches text shortcodes like :rocket:, but our changelog already has the rendered unicode emoji, so the output still shows chore(release): 🚀 publish 41.0.2. Since the description is plain text on Artifact Hub, might as well strip the unicode ones too.

Also, every release ends up listing its own chore(release): publish X commit as a changed badge — pure noise, we can filter it out.

Small hardening: esc only escapes ", so a stray backslash would break the YAML string. Won't happen with conventional commits realistically, but it's a one-liner to cover.

Last one, and the bigger one: right now everything that isn't feat/fix falls into changed, so we only use 2 of the 6 kinds. Could map a bit more:

  • feat → added
  • fix → fixed
  • perf → changed
  • revert → removed
  • else → changed

deprecated and security don't have a conventional-commit type, so let's just skip those two.

@darkweaver87

darkweaver87 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Pushed 8c922f1 with all four points, ready for another pair of eyes while @mloiseleur is away.

  • Unicode gitmoji: GNU sed can't match multibyte ranges, so the shortcode sed became one perl -CSD pass covering both forms. Perl ships by default where GNU sed doesn't, so no extra install.
  • Release commits: filtered with grep -v '^\* chore(release)'.
  • Escaping: backslash first, then quote.
  • Kinds: perf → changed, revert → removed; deprecated/security have no conventional-commit type.

Re-ran against the real Changelog.md for 41.0.2 / 41.0.1 / 41.0.0 (matches the PR description minus the filtered release lines), plus a synthetic one for escaping and accented text. The emitted annotation round-trips through yq.

@darkweaver87 darkweaver87 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@darkweaver87

Copy link
Copy Markdown
Contributor

@bpsoraggi could you have a look ?

@bpsoraggi bpsoraggi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

mloiseleur and others added 3 commits August 5, 2026 12:01
Emit the structured artifacthub.io/changes annotation so Artifact Hub renders
per-kind badges (feat->added, fix->fixed, else changed) and strips gitmoji
shortcodes.

On a major release (X.0.0), collapse the annotation to a single 'changed' entry
that links to the release Upgrade Notes, so the breaking-change signal is not
buried in a long commit list.
The major-release shortcut assumed a single chart with a bare `v<version>`
tag. With hub-manager in the loop, its 1.0.0 initial release was flagged as
breaking and linked to a tag that does not exist.

Scope release links per chart, restrict the major-release shortcut to charts
that already have a previous release, and drop the release commit in both the
`chore(release):` and plain `chore:` forms.
A version whose only commit is the release commit had every line filtered
out, leaving `artifacthub.io/changes: |` with no entries: not a valid change
list. Skip the chart instead.

Drop the `perf` and `revert` cases: `perf` was identical to the default, and
Artifact Hub's `removed` means a feature was removed, which a revert is not.
Both fall through to `changed`.
Artifact Hub renders unicode emoji, so there is nothing to gain from stripping
it, and matching it needed `perl -CSD` with `\p{Extended_Pictographic}`: an
undeclared dependency, while the script otherwise guards its tooling. Only
`:shortcode:` still has to go, since that one shows up verbatim.

Stripping is now plain GNU sed, which the script already requires. The release
commit filter no longer runs on emoji-free input, so it tolerates a leading
token: `chore(release): <emoji> publish ...`. Checked against the full history,
826 commit lines, no legitimate commit dropped.
Four defects in the structured artifacthub.io/changes generation:

- The gitmoji shortcode strip was global and unanchored, so any colon
  pair was eaten: "handle note⚠️ markers" became "handle
  notemarkers", and a URL lost part of its path. It now only strips
  shortcodes where gitmoji actually sits, leading or right after the
  conventional commit type.
- Skipping a chart with no extractable change left the previous
  version's annotation in place, advertising stale changes on the new
  version. It now falls back to a single entry naming the release.
- The release commit filter allowed any word between the type and the
  release keyword, so "chore(deps): update release process docs" was
  dropped. That slot is now restricted to a rendered gitmoji.
- A breaking marker mapped to added. Artifact Hub has no breaking kind,
  so it falls back to changed.
hack/changelog.sh had no coverage. Each case under
.github/fixtures/artifacthub-changes/ pins one behaviour: kind mapping
and escaping on a patch, the upgrade notes link on a major, a first
release that is X.0.0 without being breaking, and a release whose only
commit is the release itself.

Run them with `make test-changelog`, refresh them with
`REGENERATE=1 ./hack/test-changelog.sh`. The Test workflow calls the
same make target, next to the other shell checks, so there is one way to
run them and no extra runner for a test that needs nothing but bash.

Reading the chart version now uses sed instead of yq: `yq -r` is
python-yq only, mikefarah yq has no -r flag, so the script could not run
on a GitHub runner. It drops a dependency from `make changelog` too.
Rewriting the annotation deleted everything from artifacthub.io/changes to
the end of Chart.yaml, so any annotation sorting after it was silently
dropped. Delete only the block instead.

An empty changes list now fails rather than falling back to a "Release X"
entry. It means Chart.yaml and Changelog.md are out of sync, or the version
holds nothing but its own release commit, and no release is changeless.

The golden tests only compared bytes, so REGENERATE happily blessed a
broken annotation. They now parse it and check every kind against the ones
Artifact Hub accepts, in syntax both mikefarah yq and python-yq run.
@mloiseleur mloiseleur added status/3-needs-merge area/documentation Improvements or additions to documentation labels Aug 5, 2026
@traefiker
traefiker merged commit 3af87d9 into traefik:master Aug 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants