Skip to content
Draft
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
67 changes: 67 additions & 0 deletions .github/workflows/changelog-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Changelog bundle
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is a reusable workflow necessary if it has only one step?

it would be necessary, if we split it into 2 jobs (https://github.com/elastic/docs-actions/pull/42/changes#r2997494919)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. I've split them with their specific permissions now.


on:
workflow_call:
inputs:
config:
description: 'Path to changelog.yml configuration file'
type: string
default: 'docs/changelog.yml'
release-version:
description: >
GitHub release tag used as the PR filter source (e.g. v9.2.0).
Mutually exclusive with report.
type: string
report:
description: >
Buildkite promotion report HTTPS URL or local file path.
Mutually exclusive with release-version.
type: string
output:
description: 'Output file path for the bundle (e.g. docs/releases/v9.2.0.yaml)'
type: string
required: true
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

permissions: {}

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

jobs:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Missing permissions: {} at workflow scope. Both changelog-validate.yml (line 11) and changelog-submit.yml (line 15) deny all permissions at the top level and then grant only what each job needs. Without this, the workflow inherits the repository default token permissions — the generate job silently runs with write access it does not need.

Add before jobs::

permissions: {}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changes from this report:

.github/workflows/changelog-bundle.yml

  • permissions: {} added at workflow scope
  • concurrency block added, keyed on inputs.output with cancel-in-progress: false. Two calls for the same output path will now queue instead of racing.
  • base-branch input propagated to bundle-pr action.
  • report description updated to specify HTTPS.

changelog/bundle-create/action.yml

  • HTTPS-only for report URLs — http:// URLs are now rejected with an error.
  • artifact-name input added (default changelog-bundle) — the upload step uses it instead of a hardcoded string, preventing collisions in matrix scenarios.

changelog/bundle-pr/action.yml

  • Output path validation step added: rejects absolute paths, .. traversal, and files not ending in .yml/.yaml.
  • Branch name validation using ^[a-zA-Z0-9._+-]+$ — matches the guard in changelog/submit/action.yml lines 72-77.
  • basename strips both .yaml and .yml extensions to handle either.
  • --base flag on gh pr create when base-branch input is provided.
  • artifact-name input added to match the bundle-create parameterization.
  • Force-push has been changed to --force-with-lease.

generate:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: elastic/docs-actions/changelog/bundle-create@v1
with:
config: ${{ inputs.config }}
release-version: ${{ inputs.release-version }}
report: ${{ inputs.report }}
output: ${{ inputs.output }}
repo: ${{ inputs.repo }}
owner: ${{ inputs.owner }}
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: ${{ inputs.output }}
base-branch: ${{ inputs.base-branch }}
github-token: ${{ github.token }}
110 changes: 110 additions & 0 deletions changelog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,113 @@ 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. Two filter sources are supported:

- **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.

Exactly one filter source must be provided per invocation. The bundle always includes the full content of each matching entry (`--resolve`), 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 two 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'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
- cron: '0 8 * * 1-5'
# At 08:00 AM, Monday through Friday
- cron: '0 8 * * 1-5'

I can never read cron syntax :D

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
```

#### 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.
35 changes: 35 additions & 0 deletions changelog/bundle-create/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- 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 filtering by GitHub release version or Buildkite promotion report. Uses --network none for report mode.
<!--/description-->

## Inputs
<!--inputs-->
| Name | Description | Required | Default |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------|
| `config` | Path to changelog.yml configuration file | `false` | `docs/changelog.yml` |
| `release-version` | GitHub release tag used as the PR filter source (e.g. v9.2.0). Mutually exclusive with report. Requires repo to be set in changelog.yml or via repo input. | `false` | |
| `report` | Buildkite promotion report URL or local file path used as the PR filter source. Mutually exclusive with release-version. Local paths must be relative to repo root. | `false` | |
| `output` | Output file path for the bundle, relative to the repo root (e.g. docs/releases/v9.2.0.yaml). Must end in .yml or .yaml. | `true` | |
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.

It might be nice (in follow-up PR?) to make the github action support profiles. Then more of these options could be picked up automatically from the config file (e.g. output, output_products, etc)

| `repo` | GitHub repository name. Falls back to bundle.repo in changelog.yml. | `false` | |
| `owner` | GitHub repository owner. Falls back to bundle.owner in changelog.yml, then to elastic. | `false` | |
| `github-token` | GitHub token (needed for release-version to access GitHub API) | `false` | `${{ github.token }}` |
<!--/inputs-->

## Outputs
<!--outputs-->
| Name | Description |
|------|-------------|
<!--/outputs-->

## Usage
<!--usage action="elastic/docs-actions/changelog/bundle-create" version="v1"-->
```yaml
steps:
- uses: elastic/docs-actions/changelog/bundle-create@v1
with:
release-version: v9.2.0
output: docs/releases/v9.2.0.yaml
```
<!--/usage-->
100 changes: 100 additions & 0 deletions changelog/bundle-create/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Changelog bundle create
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 filtering by GitHub release version or Buildkite
promotion report. Uses --network none for report mode.

inputs:
config:
description: 'Path to changelog.yml configuration file'
default: 'docs/changelog.yml'
release-version:
description: >
GitHub release tag used as the PR filter source (e.g. v9.2.0).
Mutually exclusive with report. Requires repo to be set in
changelog.yml (bundle.repo) or passed via the repo input.
report:
description: >
Buildkite promotion report URL or local file path used as the
PR filter source. Mutually exclusive with release-version.
Local paths must be relative to the repo root.
output:
description: >
Output file path for the bundle, relative to the repo root
(e.g. docs/releases/v9.2.0.yaml). Must end in .yml or .yaml.
required: true
repo:
description: >
GitHub repository name. Falls back to bundle.repo in changelog.yml.
owner:
description: >
GitHub repository owner. Falls back to bundle.owner in changelog.yml,
then to elastic.
artifact-name:
description: 'Name for the uploaded artifact (must match bundle-pr artifact-name)'
default: 'changelog-bundle'
github-token:
description: 'GitHub token (needed for release-version to access GitHub API)'
default: '${{ github.token }}'

runs:
using: composite
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Generate changelog bundle
shell: bash
env:
CONFIG: ${{ inputs.config }}
RELEASE_VERSION: ${{ inputs.release-version }}
REPORT: ${{ inputs.report }}
OUTPUT: ${{ inputs.output }}
REPO: ${{ inputs.repo }}
OWNER: ${{ inputs.owner }}
GITHUB_TOKEN: ${{ inputs.github-token }}
run: |
if [ -n "$RELEASE_VERSION" ] && [ -n "$REPORT" ]; then
echo "::error::Only one of 'release-version' or 'report' may be provided"
exit 1
fi

if [ -z "$RELEASE_VERSION" ] && [ -z "$REPORT" ]; then
echo "::error::Either 'release-version' or 'report' must be provided"
exit 1
fi

docker_args=(--rm -v "${PWD}:/github/workspace" -w /github/workspace)
bundle_args=(changelog bundle --config "$CONFIG" --resolve --output "$OUTPUT")

if [ -n "$RELEASE_VERSION" ]; then
bundle_args+=(--release-version "$RELEASE_VERSION")
docker_args+=(-e GITHUB_TOKEN)
if [ -n "$REPO" ]; then bundle_args+=(--repo "$REPO"); fi
if [ -n "$OWNER" ]; then bundle_args+=(--owner "$OWNER"); fi
fi

if [ -n "$REPORT" ]; then
if [[ "$REPORT" == https://* ]]; then
curl -fsSL "$REPORT" -o .bundle-report.html
REPORT=".bundle-report.html"
elif [[ "$REPORT" == http://* ]]; then
echo "::error::Report URL must use HTTPS: ${REPORT}"
exit 1
fi
bundle_args+=(--report "$REPORT")
docker_args+=(--network none)
fi

docker run "${docker_args[@]}" ghcr.io/elastic/docs-builder:edge "${bundle_args[@]}"

- name: Upload bundle artifact
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact-name }}
path: ${{ inputs.output }}
if-no-files-found: error
retention-days: 1
29 changes: 29 additions & 0 deletions changelog/bundle-pr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!-- Generated by https://github.com/reakaleek/gh-action-readme -->
# <!--name-->changelog/bundle-pr<!--/name-->
<!--description-->
Downloads a changelog bundle artifact and opens a pull request to add it to the repository. If a PR already exists for the same bundle, it is updated in place.
<!--/description-->

## Inputs
<!--inputs-->
| Name | Description | Required | Default |
|----------------|----------------------------------------------------------------------------------------------------------------------|----------|-----------------------|
| `output` | Output file path for the bundle, relative to the repo root (e.g. docs/releases/v9.2.0.yaml). Must match the bundle-create action. | `true` | |
| `github-token` | GitHub token with contents:write and pull-requests:write permissions | `false` | `${{ github.token }}` |
<!--/inputs-->

## Outputs
<!--outputs-->
| Name | Description |
|------|-------------|
<!--/outputs-->

## Usage
<!--usage action="elastic/docs-actions/changelog/bundle-pr" version="v1"-->
```yaml
steps:
- uses: elastic/docs-actions/changelog/bundle-pr@v1
with:
output: docs/releases/v9.2.0.yaml
```
<!--/usage-->
Loading
Loading