Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .chloggen/summary.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,25 @@
{{- end }}
## {{ .Version }}

This release includes version XX.YY.ZZ of the upstream Collector components.
<!-- upstream-start -->
This release includes version <!-- upstream-version --> of the upstream Collector components.

The individual upstream Collector changelogs can be found here:

vXX.YY.ZZ:
<!-- upstream-collector-versions -->

- <https://github.com/open-telemetry/opentelemetry-collector/releases/tag/vXX.YY.ZZ>
- <https://github.com/open-telemetry/opentelemetry-collector-contrib/releases/tag/vXX.YY.ZZ>
<!-- upstream-breaking-changes -->

<details>
<summary>Highlights from the upstream Collector changelog</summary>

<!-- upstream-other-changes -->

---

</details>

<!-- upstream-end -->
{{- if or .BreakingChanges .Deprecations .NewComponents .BugFixes .Enhancements }}

#### Dynatrace distribution changelog:
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/upstream-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Generate Changelog

on:
workflow_dispatch:
inputs:
pr_urls:
description: >
Space-separated "prepare release" PR URLs (core and/or contrib).
Leave empty if there is no upstream release for this dist release.
Example: https://github.com/open-telemetry/opentelemetry-collector/pull/14515 https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/45836
required: false
type: string
dist_version:
description: 'New dist version (e.g. 0.45.0 — leave empty to auto-bump minor)'
required: false
type: string

env:
GO_VERSION: "1.25.7"

jobs:
generate-changelog:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Bump dist.version in manifest.yaml and generate downstream changelog
run: |
if [ -n "${{ inputs.dist_version }}" ]; then
NEW_VERSION="${{ inputs.dist_version }}"
else
CURRENT=$(grep ' version:' manifest.yaml | head -1 | awk '{print $2}')
NEW_VERSION=$(echo "$CURRENT" | awk -F. '{printf "%d.%d.0", $1, $2+1}')
fi
sed -i "s/ version: .*/ version: $NEW_VERSION/" manifest.yaml
echo "DIST_VERSION=$NEW_VERSION" >> "$GITHUB_ENV"

make chlog-update VERSION="$NEW_VERSION"

- name: Build changelog generator
run: go build -o ./bin/changelog-generator ./internal/changelog-generator

- name: Fill upstream changelog placeholders
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./bin/changelog-generator ${{ inputs.pr_urls }}

- name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: changelog/v${{ env.DIST_VERSION }}
title: "[chore] Prepare release v${{ env.DIST_VERSION }}"
body: |
Auto-generated changelog for collector version v${{ env.DIST_VERSION }}.

**Source PRs:**
${{ inputs.pr_urls != '' && inputs.pr_urls || '_No upstream release for this dist release._' }}

> ⚠️ Please review the generated changelog carefully before merging.
> The automation filters entries based on components in `manifest.yaml` and the allow/denylist in
> `internal/changelog-generator/config.yaml`. Some entries may need manual adjustment.
labels: Skip Changelog
commit-message: "chore: prepare release v${{ env.DIST_VERSION }}"

32 changes: 25 additions & 7 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,31 @@ Before starting a release, make a PR against to verify and update the following:
Collector version. It is okay for bugfix versions to differ.
3. Verify that no relevant bugs affecting the new versions were reported in the
upstream Collector repositories.
4. Ensure the `dist.version` key in `manifest.yaml` has the version of the
Dynatrace Collector you would like to release.
5. Ensure the changelog has been generated by running `make chlog-update
VERSION="vX.X.X"` with your desired version.
6. Update the upstream changelog links to the relevant upstream release.
7. Look through the upstream release(s) and add any highlights to the
corresponding section(s).
~~4. Ensure the `dist.version` key in `manifest.yaml` has the version of the
Dynatrace Collector you would like to release.~~
~~5. Ensure the changelog has been generated by running `make chlog-update
VERSION="vX.X.X"` with your desired version.~~
6. ~~Update the upstream changelog links to the relevant upstream release.~~
7. ~~Look through the upstream release(s) and add any highlights to the
corresponding section(s).~~

**Steps 4–7 are now automated.** Run the
[Generate Upstream Changelog](../.github/workflows/upstream-changelog.yml)
workflow (`Actions → Generate Upstream Changelog → Run workflow`) with the
upstream "prepare release" PR URLs as input. The workflow will:
- Fetch the structured `.chloggen/*.yaml` entries from the upstream PRs
- Filter them to components present in `manifest.yaml` (plus the
allowlist in `internal/changelog-generator/config.yaml`)
- Insert the formatted upstream section into `CHANGELOG.md`
- Bump `dist.version` in `manifest.yaml` and `OTEL_UPSTREAM_VERSION` in
`Makefile`
- Open a PR for review

Review the generated PR carefully before merging — some entries may need
manual adjustment. See
[`internal/changelog-generator/README.md`](../internal/changelog-generator/README.md)
for details.

8. Ensure all `go.mod` and `go.sum` files are up-to-date by running `make gotidy`

## Making a production release
Expand Down
2 changes: 2 additions & 0 deletions internal/changelog-generator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
changelog-generator

134 changes: 134 additions & 0 deletions internal/changelog-generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# changelog-generator

A Go tool that fills the upstream changelog section in `CHANGELOG.md` during a release.

`make chlog-update` (via `chloggen`) generates a new version section with placeholder comments.
This tool fetches the upstream `.chloggen/*.yaml` entry files from the upstream "prepare release" PRs,
filters them to components included in this distribution, and replaces those placeholders with real content.

## Usage

```sh
# Upstream release: fill placeholders with content from the upstream PRs
GITHUB_TOKEN=$(gh auth token) ./bin/changelog-generator \
https://github.com/open-telemetry/opentelemetry-collector/pull/14515 \
https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/45836

# Multi-version upgrade: pass all upstream PR URLs
GITHUB_TOKEN=$(gh auth token) ./bin/changelog-generator \
https://github.com/open-telemetry/opentelemetry-collector/pull/14515 \
https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/45836

# Distro-only release (no upstream bump): removes the upstream placeholder section entirely
GITHUB_TOKEN=$(gh auth token) ./bin/changelog-generator

# Dry-run: print the generated pieces to stdout without modifying any files
GITHUB_TOKEN=$(gh auth token) ./bin/changelog-generator -dry-run \
https://github.com/open-telemetry/opentelemetry-collector/pull/14515 \
https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/45836
```

### Flags

| Flag | Default | Description |
|------|---------|-------------|
| `-manifest` | `manifest.yaml` | Path to `manifest.yaml` |
| `-config` | `internal/changelog-generator/config.yaml` | Path to allow/denylist config |
| `-changelog` | `CHANGELOG.md` | Path to `CHANGELOG.md` |
| `-dry-run` | `false` | Print to stdout without modifying files |

## Building

```sh
go build -o ./bin/changelog-generator ./internal/changelog-generator
```

## How It Works

1. **`make chlog-update VERSION="vX.Y.Z"`** runs `chloggen`, which consumes distro `.chloggen/*.yaml` entries and
writes a new version section into `CHANGELOG.md` using `summary.tmpl`. The template includes
named placeholder comments (`<!-- upstream-version -->`, `<!-- upstream-collector-versions -->`,
`<!-- upstream-breaking-changes -->`, `<!-- upstream-other-changes -->`) wrapped in
`<!-- upstream-start -->`/`<!-- upstream-end -->` boundary markers.

2. **This tool** then fills those placeholders:
- Reads `manifest.yaml` to build the set of included upstream component IDs.
- For each supplied PR URL, fetches `.chloggen/*.yaml` files from the PR's **base commit** via
the GitHub Contents API (entries live at the base because the PR deletes them on merge), and
reads `versions.yaml` from the **head commit** to determine the upstream release version.
- Filters entries against the component set and the allow/denylist from `config.yaml`.
- Renders filtered entries into the four placeholder slots and removes the boundary markers.
- When no PR URLs are provided, removes the entire `<!-- upstream-start -->`…`<!-- upstream-end -->` block.

## Configuration (`config.yaml`)

```yaml
# Always include entries for these component identifiers (exact or prefix match).
allowlist:
- "pkg/ottl"
- "all" # entries with component "all" affect every component

# Always exclude. Denylist takes precedence over allowlist and manifest components.
# Supports glob suffix (*) for prefix matching.
denylist:
- "internal/*"
- "cmd/*"
```

- **To add a shared package**: add it to `allowlist` (e.g. `pkg/newpackage`).
- **To suppress a component** even if it's in `manifest.yaml`: add it to `denylist`.

## Component Name Mapping

Component IDs are derived from `manifest.yaml` gomod paths:

| `manifest.yaml` gomod | Upstream `component` ID |
|---|---|
| `go.opentelemetry.io/collector/receiver/otlpreceiver` | `receiver/otlp` |
| `.../receiver/filelogreceiver` | `receiver/filelog` |
| `.../processor/resourcedetectionprocessor` | `processor/resourcedetection` |
| `.../extension/storage/filestorage` | `extension/filestorage` |

The type suffix is stripped from the last path segment (e.g. `filelogreceiver` → `filelog`).

## GitHub Actions Workflow

The tool is wrapped by `.github/workflows/upstream-changelog.yml`, which:

1. Runs `make chlog-update` to consume distro entries and write the scaffold.
2. Runs this tool to fill the upstream placeholders.
3. Bumps `dist.version` in `manifest.yaml` and `OTEL_UPSTREAM_VERSION` in `Makefile`.
4. Opens a PR labelled `Skip Changelog` for human review.

```
Actions → "Generate Upstream Changelog" → Run workflow

Inputs:
core_pr_url (required) upstream core "prepare release" PR URL
contrib_pr_url (required) upstream contrib "prepare release" PR URL
extra_pr_urls (optional) comma-separated additional PR URLs for multi-version upgrades
dist_version (optional) e.g. 0.46.0 — auto-bumps minor version if omitted
```

## Running Tests

```sh
cd internal/changelog-generator
go test ./...
```

## Troubleshooting

**`GITHUB_TOKEN` not set / 403 errors**
```sh
export GITHUB_TOKEN=$(gh auth token)
```

**"no valid upstream versions found"**
Each PR must have a `versions.yaml` at its head commit with a valid semver. The tool reads the
version from `module-sets.beta.version` (core) or `module-sets.contrib-base.version` (contrib).

**Entry not appearing in output**
Run with `-dry-run` and check the `info:` lines on stderr. If a component is missing, add it to
`allowlist` in `config.yaml`.

83 changes: 83 additions & 0 deletions internal/changelog-generator/changelog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package main

import (
"fmt"
"os"
"strings"
)

const (
upstreamStartMarker = "<!-- upstream-start -->"
upstreamEndMarker = "<!-- upstream-end -->"
upstreamVersionMarker = "<!-- upstream-version -->"
upstreamVersionsMarker = "<!-- upstream-collector-versions -->"
upstreamBreakingMarker = "<!-- upstream-breaking-changes -->"
upstreamOtherMarker = "<!-- upstream-other-changes -->"
)

// FillUpstreamPlaceholders reads the CHANGELOG.md at path and either fills the
// upstream placeholder comments with content from UpstreamContent, or removes
// the entire upstream section when UpstreamContent is zero-valued (no upstream
// release).
//
// The expected scaffold (produced by `make chlog-update`) contains:
//
// <!-- upstream-start -->
// This release includes version <!-- upstream-version --> ...
// <!-- upstream-collector-versions -->
// <!-- upstream-breaking-changes -->
// <details>
// <!-- upstream-other-changes -->
// </details>
// <!-- upstream-end -->
//
// After this function runs, the boundary markers are removed and only the
// rendered content (or nothing) remains.
func FillUpstreamPlaceholders(path string, content UpstreamContent) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("reading changelog: %w", err)
}

s := string(data)

if content.VersionIntro == "" {
s = removeUpstreamSection(s)
} else {
s = strings.ReplaceAll(s, upstreamVersionMarker, content.VersionIntro)
s = fillOrRemovePlaceholder(s, upstreamVersionsMarker, content.CollectorVersions)
s = fillOrRemovePlaceholder(s, upstreamBreakingMarker, content.BreakingChanges)
s = fillOrRemovePlaceholder(s, upstreamOtherMarker, content.OtherChanges)
// Remove the boundary markers now that the content is in place.
s = strings.ReplaceAll(s, upstreamStartMarker+"\n", "")
s = strings.ReplaceAll(s, "\n\n"+upstreamEndMarker, "\n")
}

return os.WriteFile(path, []byte(s), 0o644)
}

// removeUpstreamSection removes everything between <!-- upstream-start --> and
// <!-- upstream-end --> (both inclusive) and normalizes the surrounding
// whitespace to a single blank line.
func removeUpstreamSection(s string) string {
startIdx := strings.Index(s, upstreamStartMarker)
endIdx := strings.Index(s, upstreamEndMarker)
if startIdx == -1 || endIdx == -1 {
return s
}
endIdx += len(upstreamEndMarker)

before := strings.TrimRight(s[:startIdx], " \t\n")
after := strings.TrimLeft(s[endIdx:], " \t\n")
return before + "\n\n" + after
}

// fillOrRemovePlaceholder replaces a placeholder comment that lives on its own
// line (surrounded by blank lines) with content. When content is empty the
// placeholder line is removed entirely to avoid stray blank lines.
func fillOrRemovePlaceholder(s, placeholder, content string) string {
if content == "" {
return strings.ReplaceAll(s, "\n\n"+placeholder+"\n", "\n")
}
return strings.ReplaceAll(s, placeholder, content)
}
Loading
Loading