Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cb3c68c
Add bundle action and reusable workflow
cotti Mar 26, 2026
fa8354f
Merge branch 'main' into feature/bundle_action
cotti Mar 26, 2026
c31000b
Add README
cotti Mar 26, 2026
4de3e84
Split action into bundle generation and PR submission. Readjusted nam…
cotti Mar 26, 2026
6bfcfd9
Merge remote-tracking branch 'origin/main' into feature/bundle_action
cotti Mar 27, 2026
e43c658
Security fixes
cotti Mar 27, 2026
f86c77c
Use --force-with-lease instead of -f
cotti Mar 27, 2026
1df7f08
Merge remote-tracking branch 'origin' into feature/bundle_action
cotti Mar 31, 2026
06f4322
Add --prs support
cotti Mar 31, 2026
803d979
Add support for profile-based bundling
cotti Mar 31, 2026
7982c5f
Use env vars for docker run
cotti Mar 31, 2026
2e773dc
Validate paths
cotti Mar 31, 2026
0e5b77c
Update README
cotti Mar 31, 2026
3f83498
Merge branch 'main' into feature/bundle_action
cotti Apr 1, 2026
2d8fd32
Merge branch 'main' into feature/bundle_action
cotti Apr 1, 2026
687861d
Adjust action to use docs-builder changelog bundle --plan
cotti Apr 2, 2026
f7750eb
Set outputs instead
cotti Apr 2, 2026
20bacab
Update README
cotti Apr 2, 2026
ab2fcc3
Merge branch 'main' into feature/bundle_action
cotti Apr 2, 2026
b2e6a14
Fix README
cotti Apr 2, 2026
5bc0760
Harden input validation
cotti Apr 7, 2026
40e06dd
Pin docs-builder version using attestation-verify
cotti Apr 7, 2026
c21dbdc
Add domain allowlist for report URLs
cotti Apr 7, 2026
07c29bc
revert tackling ssrf in the action
cotti Apr 7, 2026
974256d
Merge branch 'main' into feature/bundle_action
cotti Apr 7, 2026
8a22206
Update README
cotti Apr 7, 2026
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
98 changes: 98 additions & 0 deletions .github/workflows/changelog-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Changelog bundle

on:
workflow_call:
inputs:
config:
description: 'Path to changelog.yml configuration file'
type: string
default: 'docs/changelog.yml'
profile:
description: >
Bundle profile name from bundle.profiles in changelog.yml.
Mutually exclusive with release-version and prs.
type: string
version:
description: >
Version string for profile mode (e.g. 9.2.0). Only valid with profile.
type: string
release-version:
description: >
GitHub release tag used as the PR filter source (e.g. v9.2.0).
Mutually exclusive with profile, report, and prs.
type: string
report:
description: >
Buildkite promotion report HTTPS URL or local file path.
Mutually exclusive with release-version and prs in option mode.
In profile mode, passed as a positional argument.
type: string
prs:
description: >
Comma-separated PR URLs or numbers, or a path to a newline-delimited file.
Mutually exclusive with profile, release-version, and report.
type: string
output:
description: >
Output file path for the bundle (e.g. docs/releases/v9.2.0.yaml).
Optional. When not provided, the path is determined by the config
(bundle.output_directory) and discovered automatically after generation.
type: string
base-branch:
description: 'Base branch for the pull request (defaults to repository default branch)'
type: string
repo:
description: 'GitHub repository name; falls back to bundle.repo in changelog.yml'
type: string
owner:
description: 'GitHub repository owner; falls back to bundle.owner in changelog.yml'
type: string
docs-builder-version:
description: >
docs-builder version to use (e.g. 0.1.100, latest, edge).
Non-edge versions are attestation-verified by the setup action.
type: string
default: 'edge'

permissions: {}

concurrency:
group: changelog-bundle-${{ inputs.output || inputs.profile }}
cancel-in-progress: false

jobs:
generate:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
outputs:
output: ${{ steps.bundle.outputs.output }}
steps:
- id: bundle
uses: elastic/docs-actions/changelog/bundle-create@v1
with:
config: ${{ inputs.config }}
profile: ${{ inputs.profile }}
version: ${{ inputs.version }}
release-version: ${{ inputs.release-version }}
report: ${{ inputs.report }}
prs: ${{ inputs.prs }}
output: ${{ inputs.output }}
repo: ${{ inputs.repo }}
owner: ${{ inputs.owner }}
docs-builder-version: ${{ inputs.docs-builder-version }}
github-token: ${{ github.token }}

create-pr:
needs: generate
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: elastic/docs-actions/changelog/bundle-pr@v1
with:
output: ${{ needs.generate.outputs.output }}
base-branch: ${{ inputs.base-branch }}
github-token: ${{ github.token }}
220 changes: 220 additions & 0 deletions changelog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,223 @@ If a human edits the changelog file directly (i.e., the last commit to the chang
## Output

Each PR produces a file at `docs/changelog/{filename}.yaml` on the PR branch (where the filename is determined by the `docs-builder changelog add` command). These files are consumed by `docs-builder` during documentation builds to produce a rendered changelog page.

## Bundling changelogs

Individual changelog files accumulate on the default branch as PRs merge. The bundle action generates a fully-resolved YAML file containing only the changelog entries that match a given filter. The action supports two modes:

**Option mode** — you specify the filter and output path directly:

- **GitHub release version** (`release-version`) — pulls PR references directly from GitHub release notes. Used for stack and product releases triggered by `on: release`.
- **Buildkite promotion report** (`report`) — extracts PR URLs from a promotion report. Used for serverless releases discovered by a scheduled workflow.
- **PR list** (`prs`) — an explicit list of PR URLs or numbers (comma-separated), or a path to a newline-delimited file. Used when the caller already knows which PRs to include.

Exactly one filter source must be provided. The `output` path is optional — when not provided, the action runs `docs-builder changelog bundle --plan` to resolve the output path from the config (`bundle.output_directory`) before generating the bundle.

**Profile mode** — all configuration comes from `bundle.profiles` in `changelog.yml`:

- **Profile** (`profile`) — a named profile that defines the product filter, output filename pattern, and other settings. The `version` input provides the value for `{version}` substitution in profile patterns.
- An optional `report` can be passed as a positional argument to filter by promotion report.
- The `output` path is optional — if not provided, it's resolved from `bundle.output_directory` in the config via the `--plan` step.
- `bundle.resolve: true` must be set in the config (it cannot be forced via CLI in profile mode).

The bundle always includes the full content of each matching entry, so downstream consumers can render changelogs without access to the original files.

### Prerequisites

Your `docs/changelog.yml` must include a `bundle` section so docs-builder knows where to find changelog files. Setting `bundle.repo` and `bundle.owner` ensures PR and issue links are generated correctly in the bundle output.

```yaml
bundle:
directory: docs/changelog
```

The reusable workflow splits into two jobs with separate permissions: `generate` (read-only, produces the bundle artifact) and `create-pr` (write access, opens a pull request with the bundle file).

### Setup

The bundle action supports multiple trigger patterns depending on your release process.

#### Stack / product releases (`on: release`)

When a GitHub release is published, the action uses `--release-version` to pull PR references directly from the release notes and filter changelog entries accordingly. The release tag provides the version for the output filename.

**`.github/workflows/changelog-bundle.yml`**

```yaml
name: changelog-bundle

on:
release:
types: [published]

permissions:
contents: write
pull-requests: write

jobs:
bundle:
uses: elastic/docs-actions/.github/workflows/changelog-bundle.yml@v1
with:
release-version: ${{ github.event.release.tag_name }}
output: docs/releases/${{ github.event.release.tag_name }}.yaml
```

The `github.event.release.tag_name` (e.g. `v9.2.0`) is passed as the release version filter and used to construct the output filename. If you prefer to strip the `v` prefix, you can do so in an earlier job step and pass the result as an input.

#### Serverless / scheduled releases (`on: schedule`)

When a Buildkite promotion report provides the list of PRs in a release, a scheduled workflow discovers the report and passes it to the bundle action. The output filename typically uses a date or timestamp.

**`.github/workflows/changelog-bundle.yml`**

```yaml
name: changelog-bundle

on:
schedule:
# At 08:00 AM, Monday through Friday
- cron: '0 8 * * 1-5'
workflow_dispatch:
inputs:
report:
description: 'Buildkite promotion report URL'
required: true
output:
description: 'Output file path for the bundle'
required: true

permissions:
contents: write
pull-requests: write

jobs:
discover-report:
runs-on: ubuntu-latest
outputs:
report-url: ${{ steps.discover.outputs.report-url }}
release-date: ${{ steps.discover.outputs.release-date }}
steps:
- id: discover
run: echo "# your logic to find the latest promotion report"

bundle:
needs: discover-report
uses: elastic/docs-actions/.github/workflows/changelog-bundle.yml@v1
with:
report: ${{ needs.discover-report.outputs.report-url }}
output: docs/releases/${{ needs.discover-report.outputs.release-date }}.yaml
```

#### Explicit PR list

When the caller already knows which PRs to include, pass them directly. The `prs` input accepts either comma-separated values or a path to a newline-delimited file. PR numbers can be used instead of full URLs when `bundle.repo` and `bundle.owner` are set in the changelog config.

**Inline PR numbers** — pass them directly in the workflow call:

```yaml
name: changelog-bundle

on:
workflow_dispatch:
inputs:
prs:
description: 'Comma-separated PR URLs or numbers'
required: true
output:
description: 'Output file path for the bundle'
required: true

permissions:
contents: write
pull-requests: write

jobs:
bundle:
uses: elastic/docs-actions/.github/workflows/changelog-bundle.yml@v1
with:
prs: ${{ inputs.prs }}
output: ${{ inputs.output }}
```

For example, triggering this workflow with `prs: "12345,67890"` bundles the changelog entries whose `prs` field matches those PR numbers.

**File-based PR list** — when a prior step produces the list, write it to a file (one PR URL per line) and pass the path:

```yaml
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
prs-file: ${{ steps.generate.outputs.prs-file }}
steps:
- id: generate
run: |
echo "https://github.com/elastic/my-repo/pull/12345" > prs.txt
echo "https://github.com/elastic/my-repo/pull/67890" >> prs.txt
echo "prs-file=prs.txt" >> "$GITHUB_OUTPUT"

bundle:
needs: prepare
uses: elastic/docs-actions/.github/workflows/changelog-bundle.yml@v1
with:
prs: ${{ needs.prepare.outputs.prs-file }}
output: docs/releases/my-bundle.yaml
```

When using a file, every line must be a fully-qualified GitHub PR URL (e.g. `https://github.com/owner/repo/pull/123`). Bare numbers are only supported in the comma-separated format.

#### Profile-based bundling

When your repository has `bundle.profiles` configured in `changelog.yml`, the profile drives which changelogs to include and where to write the bundle. Set `bundle.resolve: true` in the config so entry contents are inlined.

```yaml
bundle:
directory: docs/changelog
output_directory: docs/releases
resolve: true
repo: my-repo
owner: elastic
profiles:
my-release:
products: "my-product {version} {lifecycle}"
output: "{version}.yaml"
```

**`.github/workflows/changelog-bundle.yml`**

```yaml
name: changelog-bundle

on:
release:
types: [published]

permissions:
contents: write
pull-requests: write

jobs:
bundle:
uses: elastic/docs-actions/.github/workflows/changelog-bundle.yml@v1
with:
profile: my-release
version: ${{ github.event.release.tag_name }}
```

The `output` input is not needed — the action resolves the output path from `bundle.output_directory` and the profile's `output` pattern via the `--plan` step. If a promotion report is also needed, pass it via the `report` input.

#### Custom config path

If your changelog configuration is not at `docs/changelog.yml`, pass the path explicitly:

```yaml
with:
config: path/to/changelog.yml
release-version: ${{ github.event.release.tag_name }}
output: docs/releases/${{ github.event.release.tag_name }}.yaml
```

### Output

The reusable workflow opens a pull request on a branch named `changelog-bundle/<bundle-name>` (e.g. `changelog-bundle/v9.2.0`). The PR contains the fully-resolved bundle file at the path specified by the `output` input. If a PR already exists for that branch, the bundle is updated in place. If the generated bundle is identical to what's already in the repository, no commit or PR is created.
52 changes: 52 additions & 0 deletions changelog/bundle-create/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!-- Generated by https://github.com/reakaleek/gh-action-readme -->
# <!--name-->Changelog bundle create<!--/name-->
<!--description-->
Checks out the repository, runs docs-builder changelog bundle in Docker to generate a fully-resolved bundle file, and uploads the result as an artifact. Supports option-based filtering (release-version, report, prs) and profile-based bundling. Uses --network none where possible.
<!--/description-->

## Inputs
<!--inputs-->
| Name | Description | Required | Default |
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------|
| `config` | Path to changelog.yml configuration file | `false` | `docs/changelog.yml` |
| `profile` | Bundle profile name from bundle.profiles in changelog.yml. Mutually exclusive with release-version and prs. When set, all paths and filters come from the config.<br> | `false` | ` ` |
| `version` | Version string for profile mode (e.g. 9.2.0, 2026-03). Used for {version} substitution in profile patterns. Only valid with profile.<br> | `false` | ` ` |
| `release-version` | GitHub release tag used as the PR filter source (e.g. v9.2.0). Mutually exclusive with profile, report, and prs.<br> | `false` | ` ` |
| `report` | Buildkite promotion report URL or local file path used as the PR filter source. In option mode, mutually exclusive with release-version and prs. In profile mode, passed as a positional argument. Local paths must be relative to the repo root.<br> | `false` | ` ` |
| `prs` | Comma-separated PR URLs or numbers, or a path to a newline-delimited file. Mutually exclusive with profile, release-version, and report. Bare numbers require repo/owner to be set in changelog.yml or inputs.<br> | `false` | ` ` |
| `output` | Output file path for the bundle, relative to the repo root. Optional in both modes. When not provided, docs-builder writes to the path determined by the config (bundle.output_directory) and the plan resolves the generated file path automatically.<br> | `false` | ` ` |
| `repo` | GitHub repository name. Falls back to bundle.repo in changelog.yml.<br> | `false` | ` ` |
| `owner` | GitHub repository owner. Falls back to bundle.owner in changelog.yml, then to elastic.<br> | `false` | ` ` |
| `docs-builder-version` | docs-builder version to use (e.g. 0.1.100, latest, edge). Non-edge versions are attestation-verified by the setup action.<br> | `false` | `edge` |
| `artifact-name` | Name for the uploaded artifact (must match bundle-pr artifact-name) | `false` | `changelog-bundle` |
| `github-token` | GitHub token (needed for release-version and source: github_release profiles) | `false` | `${{ github.token }}` |
<!--/inputs-->

## Outputs
<!--outputs-->
| Name | Description |
|----------|------------------------------------------|
| `output` | Resolved output file path for the bundle |
<!--/outputs-->

## Usage
<!--usage action="elastic/docs-actions/changelog/bundle-create" version="v1"-->

Option mode:
```yaml
steps:
- uses: elastic/docs-actions/changelog/bundle-create@v1
with:
release-version: v9.2.0
output: docs/releases/v9.2.0.yaml
```

Profile mode:
```yaml
steps:
- uses: elastic/docs-actions/changelog/bundle-create@v1
with:
profile: elasticsearch-release
version: 9.2.0
```
<!--/usage-->
Loading
Loading