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
195 changes: 0 additions & 195 deletions .github/workflows/update-plugin-index.yaml

This file was deleted.

8 changes: 3 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ Validates workflow YAML files using actionlint. Runs automatically on workflow f

Finds and validates all `kustomization.yaml` files in the repository using `kustomize build --enable-helm`.

### 6. Update Plugin Index (`.github/workflows/update-plugin-index.yaml`)
### 6. Update Plugin Index (`update-plugin-index/action.yml` — composite action)

Opens a PR against a datumctl plugin catalog (index repo) that bumps a plugin's manifest to a newly published release — setting `spec.version`, rewriting each platform's download `uri` to the new tag, and refreshing each `sha256` from the release's `checksums.txt`.
Opens a PR against a datumctl plugin catalog (index repo) that bumps a plugin's manifest to a newly published release — setting `spec.version`, rewriting each platform's download `uri` to the new tag, and refreshing each `sha256` from the release's `checksums.txt`. It is a **composite action** (not a reusable workflow) so the caller mints the cross-repo token and runs this in the same job — a GitHub App token doesn't survive being passed across jobs.

**Inputs:**
- `index-repo` (required): Catalog repo to open the PR against (e.g. `milo-os/cli-plugins`)
Expand All @@ -109,9 +109,7 @@ Opens a PR against a datumctl plugin catalog (index repo) that bumps a plugin's
- `version` (required): Release tag including leading `v` (e.g. `v0.2.0`)
- `release-repo` (optional): Repo that published the release assets (default: calling repo)
- `base-branch` (optional): Index repo branch to base the PR on (default `main`)

**Secrets:**
- `PLUGIN_INDEX_TOKEN` (required): PAT / GitHub App token with `contents:write` + `pull-requests:write` on `index-repo`. The built-in `GITHUB_TOKEN` cannot push or open a PR cross-repo.
- `token` (required): PAT / GitHub App token with `contents:write` + `pull-requests:write` on `index-repo`, minted in the same job. The built-in `GITHUB_TOKEN` cannot push or open a PR cross-repo.

The archive→checksum mapping is driven off the basenames of the manifest's existing `platforms[].uri` values, so it works for any plugin. Regenerating `index.yaml` is left to the index repo's own generator. See [`docs/update-plugin-index/`](docs/update-plugin-index/README.md).

Expand Down
76 changes: 48 additions & 28 deletions docs/update-plugin-index/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
# Update Plugin Index

The `.github/workflows/update-plugin-index.yaml` reusable GitHub Action opens a
pull request against a [datumctl](https://github.com/datum-cloud/datumctl)
plugin catalog (the "index repo") whenever a service repo publishes a release.
It bumps a single plugin's manifest (`plugins/<plugin-name>.yaml`) to the new
version: it sets `spec.version` and, for every platform, rewrites the download
`uri` to point at the new tag and refreshes the `sha256` from the release's
`checksums.txt`.
The `update-plugin-index` composite action opens a pull request against a
[datumctl](https://github.com/datum-cloud/datumctl) plugin catalog (the "index
repo") whenever a service repo publishes a release. It bumps a single plugin's
manifest (`plugins/<plugin-name>.yaml`) to the new version: it sets
`spec.version` and, for every platform, rewrites the download `uri` to point at
the new tag and refreshes the `sha256` from the release's `checksums.txt`.

This replaces the manual step of hand-editing catalog manifests after each
release.

## Why a composite action (not a reusable workflow)

Cross-repo PRs need a token the caller's built-in `GITHUB_TOKEN` can't provide
(it only reaches its own repo), so callers mint a GitHub App installation token.
A GitHub App token minted in one job and handed to a **reusable workflow** (a
separate job) is scrubbed to an empty string — masked values do not survive
job-to-job outputs. A **composite action** runs inside the caller's job, so the
mint step and this action share one job and the token is passed directly as an
input. Mint and use must live in the same job; that's the whole reason this is
an action rather than a reusable workflow.

## Inputs

- **index-repo** (required): The plugin catalog repository to open the PR
Expand All @@ -25,22 +35,18 @@ release.
Defaults to the calling repository (`github.repository`).
- **base-branch** (optional): Branch in the index repo to base the PR on.
Defaults to `main`.

## Secrets

- **PLUGIN_INDEX_TOKEN** (required): A token with `contents:write` and
`pull-requests:write` permission on `index-repo`. A caller's built-in
`GITHUB_TOKEN` is scoped to its own repository and **cannot** push a branch or
open a PR on a different repository, so a cross-repo credential — a Personal
Access Token or a GitHub App installation token — is required. Store it as a
repository or organization secret on the calling repo.
- **token** (required): A token with `contents:write` and `pull-requests:write`
on `index-repo`. Mint it **in the same job** (e.g. with
`actions/create-github-app-token`) and pass it here. The caller's built-in
`GITHUB_TOKEN` cannot push a branch or open a PR on a different repository.

## How archives are mapped to checksums

The mapping is fully generic and never hardcodes a plugin name or archive
prefix:

1. The release's `checksums.txt` is downloaded. Each line is
1. The release's `checksums.txt` is downloaded (using the caller's
`GITHUB_TOKEN`, since the release lives in the calling repo). Each line is
`<sha256> <filename>`; entries are keyed by the filename's **basename**.
2. For each entry in the manifest's existing `spec.platforms[]`, the archive
name is taken from the **basename of that platform's current `uri`**.
Expand All @@ -51,17 +57,18 @@ prefix:
`https://github.com/<release-repo>/releases/download/<version>/<basename>`.

If `checksums.txt` is missing, or any expected archive basename is not listed in
it, the workflow fails loudly.
it, the action fails loudly.

## What it does not do

Regenerating `index.yaml` is left to the index repo's own generator, which
reconciles `index.yaml` from `plugins/*.yaml` on merge to its default branch.
This workflow only edits `plugins/<plugin-name>.yaml`.
This action only edits `plugins/<plugin-name>.yaml`.

## Workflow Example

Add a job that runs when a release is published:
Mint the App token and call the action in a **single job**, gated on the release
event:

```yaml
name: Release
Expand All @@ -77,21 +84,34 @@ jobs:
update-plugin-index:
needs: [publish-plugin]
if: github.event_name == 'release'
uses: datum-cloud/actions/.github/workflows/update-plugin-index.yaml@v1
with:
index-repo: milo-os/cli-plugins
plugin-name: ipam
version: ${{ github.event.release.tag_name }}
secrets:
PLUGIN_INDEX_TOKEN: ${{ secrets.PLUGIN_INDEX_TOKEN }}
runs-on: ubuntu-latest
steps:
- name: Mint catalog token from a GitHub App
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.PLUGIN_INDEX_APP_ID }}
private-key: ${{ secrets.PLUGIN_INDEX_APP_PRIVATE_KEY }}
owner: milo-os
repositories: cli-plugins

- name: Open the catalog PR
uses: datum-cloud/actions/update-plugin-index@v1
with:
index-repo: milo-os/cli-plugins
plugin-name: ipam
version: ${{ github.event.release.tag_name }}
token: ${{ steps.app-token.outputs.token }}
```

## Best Practices

- Always reference a tagged version (e.g. `@v1`) to prevent unexpected breaking
changes.
- Mint the token in the **same job** and pass it via `token:` — never across
jobs.
- Depend on the release-asset-publishing job (`needs:`) so `checksums.txt`
exists before this workflow reads it.
exists before this action reads it.
- Trigger on `release: published` only (or gate with
`if: github.event_name == 'release'`) so the update runs exactly once per
release.
Loading