From 93d18fc49a55d1724a8572d2f4ed764b97a5a80c Mon Sep 17 00:00:00 2001 From: "Philip K. Warren" Date: Wed, 22 Apr 2026 13:22:42 -0500 Subject: [PATCH] Use per-package instead of org endpoints With the `${github.token}` or an app token, it appears that the org level package endpoints can't be retrieved. It is not worth switching this to a PAT. Update the release workflow to call each package's endpoints directly (~81 calls vs 1) which is still a marked improvement from before (~1700 OCI registry calls). --- .github/workflows/release.yml | 12 ++------ internal/cmd/release/candidates.go | 46 ++++++++++++++---------------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 190273dde..233aafda1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ on: default: '' permissions: - contents: read + contents: write id-token: write issues: write packages: read @@ -26,14 +26,6 @@ jobs: if: github.repository == 'bufbuild/plugins' runs-on: ubuntu-latest steps: - - name: Generate token - id: generate_token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 - with: - client-id: ${{ secrets.TOKEN_EXCHANGE_GH_APP_CLIENT_ID }} - private-key: ${{ secrets.TOKEN_EXCHANGE_GH_APP_PRIVATE_KEY }} - permission-contents: write - permission-packages: read - name: Checkout repository code uses: actions/checkout@v6 with: @@ -52,7 +44,7 @@ jobs: check-latest: true - name: Create Release env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + GITHUB_TOKEN: ${{ github.token }} MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} MINISIGN_PRIVATE_KEY_PASSWORD: ${{ secrets.MINISIGN_PRIVATE_KEY_PASSWORD }} PLUGINS: ${{ inputs.plugins }} diff --git a/internal/cmd/release/candidates.go b/internal/cmd/release/candidates.go index 0d66aeafc..f8bd047b0 100644 --- a/internal/cmd/release/candidates.go +++ b/internal/cmd/release/candidates.go @@ -5,6 +5,8 @@ import ( "context" "fmt" "log/slog" + "maps" + "net/http" "os" "slices" "strings" @@ -133,9 +135,11 @@ func pluginKeyFromPath(path string) (pluginNameVersion, bool) { // addGHCRCandidates adds (name, version) pairs for every container package // version whose image was updated after since. // -// The list-packages endpoint returns every container package owned by the org -// (a few dozen), and per-package version listings are only fetched for packages -// that were touched after since. +// Each known package (derived from allPlugins) is fetched individually via the +// per-package endpoint, which works with a fine-grained token. The org-wide +// list endpoint is avoided because it requires organization-level permissions +// that fine-grained tokens can't grant. Per-package version listings are only +// fetched for packages that were touched after since. func (c *command) addGHCRCandidates( ctx context.Context, ghClient *release.Client, @@ -149,33 +153,25 @@ func (c *command) addGHCRCandidates( packageToPlugin[pkg] = p.Identity.Owner() + "/" + p.Identity.Plugin() } var added []pluginNameVersion - opts := &github.PackageListOptions{ - PackageType: new("container"), - ListOptions: github.ListOptions{PerPage: 100}, - } - for { - pkgs, resp, err := ghClient.GitHub.Organizations.ListPackages(ctx, string(release.GithubOwnerBufbuild), opts) + for _, pkgName := range slices.Sorted(maps.Keys(packageToPlugin)) { + pkg, resp, err := ghClient.GitHub.Organizations.GetPackage(ctx, string(release.GithubOwnerBufbuild), "container", pkgName) if err != nil { - return nil, fmt.Errorf("list packages: %w", err) - } - for _, pkg := range pkgs { - if pkg.GetUpdatedAt().Before(since) { - continue - } - pluginName, ok := packageToPlugin[pkg.GetName()] - if !ok { + // A 404 means the image hasn't been pushed yet (new plugin version + // whose CI run is still in flight). The git diff pass already flags + // it as a candidate, so skip quietly. + if resp != nil && resp.StatusCode == http.StatusNotFound { continue } - pkgAdded, err := c.addPackageVersionCandidates(ctx, ghClient, pkg.GetName(), pluginName, since, candidates) - if err != nil { - return nil, err - } - added = append(added, pkgAdded...) + return nil, fmt.Errorf("get package %q: %w", pkgName, err) } - if resp.NextPage == 0 { - break + if pkg.GetUpdatedAt().Before(since) { + continue } - opts.Page = resp.NextPage + pkgAdded, err := c.addPackageVersionCandidates(ctx, ghClient, pkgName, packageToPlugin[pkgName], since, candidates) + if err != nil { + return nil, err + } + added = append(added, pkgAdded...) } return sortedKeys(added), nil }