Skip to content

Add staging-push workflow to push stagings to ghcr - #7

Open
alexadereyko wants to merge 2 commits into
mainfrom
jira/TBBAS-3292-staging-push
Open

Add staging-push workflow to push stagings to ghcr#7
alexadereyko wants to merge 2 commits into
mainfrom
jira/TBBAS-3292-staging-push

Conversation

@alexadereyko

@alexadereyko alexadereyko commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Brief

Add staging-push, a reusable workflow that takes a run's build artifacts and pushes them to an OCI registry (ghcr).

Description

It's a pure publish primitive: it knows a package only as a path, and pushes whatever artifacts it was handed there.

A package is its path. A container registry can hold several variants of one package under a single name — the mechanism Docker uses to serve one image across platforms; staging-push uses it to serve a staging's per-platform builds. The path's tag points at an OCI index (manifest list) — the only tagged manifest — and each build is an untagged child of it, reachable only through the index. Schematically, the index looks like this:

ghcr.io/<org>/path/to/package : <sha>, <short-sha>, latest
  └─ sha256:index…
     ├─ sha256:a1…  title=hello-core-1.0.0-x86_64-linux-gcc-11-release.tar.gz
     │              com.opendaq.os=Linux    arch=x86_64  compiler=gcc   compiler.version=11
     └─ sha256:b2…  title=hello-core-1.0.0-x86_64-windows-msvc-143-release.tar.gz
                    com.opendaq.os=Windows  arch=x86_64  compiler=msvc  compiler.version=194

Children are told apart by their image title — the file name of the published tarball (org.opencontainers.image.title) — which on its own is enough to pick a variant: read the index, choose an entry, and pull it by digest, a single request.

Resolving by build settings is optional, and driven by a staging-meta.json shipped alongside the artifact. It has two fields — settings and custom, key sets in the spirit of Conan — which the workflow turns into com.opendaq.<key> and com.opendaq.custom.<key> annotations on the child's descriptor. A consumer can then select by those (os, arch, compiler, …) instead of by the file name.

The workflow pushes the children first — they land untagged — and then the index that lists them, so the index only ever references what already exists in the registry.

The caller drives this through the workflow input — for each package, a path and a list of artifact globs; every artifact whose name matches a glob is published under the path as a child. test-staging publishes hello-core's staging and installer like this:

stagings: >-
  [
    { "path": "ghcr.io/opendaq/test/staging/hello-core",   "artifacts": ["core-staging-*"],   "tags": ["latest"] },
    { "path": "ghcr.io/opendaq/test/installer/hello-core", "artifacts": ["core-package-*"],   "tags": ["latest"] }
  ]

@alexadereyko alexadereyko self-assigned this Jul 15, 2026
@alexadereyko
alexadereyko force-pushed the jira/TBBAS-3292-staging-push branch 2 times, most recently from e228694 to f2e92d2 Compare July 17, 2026 15:09
@alexadereyko
alexadereyko requested a review from JakaMohorko July 20, 2026 09:13
@alexadereyko
alexadereyko force-pushed the jira/TBBAS-3292-staging-push branch 2 times, most recently from 9333098 to bf3412e Compare July 20, 2026 14:02
staging-push is a reusable workflow that publishes a run's artifacts to an
OCI registry. The caller gives a full package path and globs; every matched
artifact becomes an untagged child of one OCI index, and the index is the
only tagged manifest. A set publishes atomically -- the children are
unreachable by name until the index lands -- and the registry enforces the
index's references. Children are told apart by their file name and, when the
artifact carried staging-meta.json, by com.opendaq.* descriptor annotations;
`platform` is left empty so no docker-style client believes it can auto-select
between compilers. The workflow authenticates with github.token and needs only
packages: write -- package names are constructible, so no PAT is required.

test-staging now publishes hello-core and hello-module -- stagings and
installers, four packages -- to the test channel through staging-push, and
manylinux builds a DEB so its staging has a matching installer.
@alexadereyko
alexadereyko force-pushed the jira/TBBAS-3292-staging-push branch from bf3412e to e483851 Compare July 22, 2026 12:48

@JakaMohorko JakaMohorko left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks generally good. Hard to evaluate, however, without seeing it used in the openDAQ main or module repositories.

shell: python3 {0}
env:
STAGINGS: ${{ inputs.staging-packages }}
TOKEN: ${{ github.token }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are we ever going to run this on fork PRs? I think the token is not available in that case.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

On a fork PR the token is read-only, so it can't push to our container registry namespace. A couple of ways to handle it:

  • The registry namespace is owner-relative -- the path is ghcr.io/<owner>/... where <owner> is ${{github.repository_owner}} -- so it can be set up so each repo publishes to its own namespace (a fork's own runs would then publish to the fork's namespace, with the fork's own token).
  • Alternatively, disable push on fork PRs (gate the publish job, or a separate PR-triggered workflow without it) -- fork PRs then run pull + build / tests and never push.

For the pull side either way:

  • Make the staging packages public, so fork PRs can pull them anonymously (public ghcr needs no token).
  • Make the oras login in staging-pull non-fatal (warn + continue) -- trusted contexts still log in for private pulls, and fork PRs fall back to anonymous pull of the public packages.

We'll have to validate this on a real fork: anonymous pull of our public packages from a fork PR, staging-pull continuing past an absent/failed login, and repository_owner behaving as expected in the fork context.

@alexadereyko alexadereyko Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Also worth noting: the reusable workflow -- the build engine -- only deals with GitHub artifacts, that's its isolated interface, it never touches the registry. staging-pull bridges the two: it pulls the packages from ghcr and re-uploads them as artifacts, which the build engine then consumes like any other.

So the fork handling stays confined to staging-pull; the build engine doesn't care where the artifacts came from, it just consumes them.

packages: write
steps:
- name: Download stagings
uses: actions/download-artifact@v7

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are we going to publish all artifacts downloaded?

@alexadereyko alexadereyko Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No, we don't publish everything downloaded, only the artifacts matching the artifacts globs of each staging-packages entry:

dirs = {d for g in staging["artifacts"] for d in glob.glob(f".artifacts/{g}") if os.path.isdir(d)}

download-artifact grabs the whole run's artifacts into .artifacts/ for convenience, and the script then filters by those globs -- anything unmatched is ignored (and if nothing matches, the job fails). The selection lives in the input (several entries, each with its own globs), which doesn't map cleanly to download-artifact's single pattern, so it's simpler to download all and filter in the script.

Add org.opencontainers.image.created to the index annotations so a
consumer can read each staging's age straight from the registry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants