Compare commits

...

2 Commits

Author SHA1 Message Date
Pat Sukprasert a8dbb75c0f Apply ruff format to reconcile_targets.py (wrap long comprehension) 2026-06-16 23:27:13 +08:00
Pat Sukprasert a6a5cbb60e Add reconcile_floating dispatch to repoint :latest / :latest-rc
Adds a `reconcile_floating` workflow_dispatch input and a reconcile-floating
job. When dispatched, it computes max(release,rc) and max(final release) from
the tag list (PEP 440 ordering via a new reconcile_targets.py) and retags
:latest-rc and :latest onto those existing version images with
`imagetools create` — no rebuild. The build job is skipped on this dispatch,
like force_nightly.

This gives a UI ("Run workflow") path to backfill :latest-rc for releases cut
before the floating-tag scheme (e.g. point :latest-rc at v0.1.1) without a
local write:packages token, and doubles as an idempotent "fix floating tags if
they drift" button.
2026-06-16 23:13:46 +08:00
2 changed files with 112 additions and 3 deletions
@@ -0,0 +1,43 @@
"""Pick which version tag each floating release tag should point at, using PEP
440 ordering. Reads ALL_TAGS (the repo's tag names, newline-separated) from the
environment and prints one line: "<rc_tag> <latest_tag>" where
rc_tag = max(release, rc) -> the image :latest-rc should reference
latest_tag = max(final release) -> the image :latest should reference
Either field is "-" when no qualifying tag exists. The original tag string
(e.g. "v0.1.1") is preserved so the caller can reference the matching image
tag. Used by the reconcile-floating job in
.github/workflows/oss-publish-images.yml to retag :latest / :latest-rc onto the
correct existing images without a rebuild.
"""
import os
from packaging.version import InvalidVersion, Version
def parse(name):
try:
return Version(name.strip().removeprefix("v"))
except InvalidVersion:
return None
def main():
pairs = [
(v, t.strip()) for t in os.environ.get("ALL_TAGS", "").splitlines() if (v := parse(t))
]
if not pairs:
print("- -")
return
# Tie-break on the raw tag string so the choice is deterministic.
_, rc_tag = max(pairs, key=lambda p: (p[0], p[1]))
finals = [p for p in pairs if not p[0].is_prerelease]
latest_tag = max(finals, key=lambda p: (p[0], p[1]))[1] if finals else "-"
print(f"{rc_tag} {latest_tag}")
if __name__ == "__main__":
main()
+69 -3
View File
@@ -54,6 +54,10 @@ on:
description: 'Promote :latest-dev -> :latest-nightly now (runs only the nightly job). Off by default.'
type: boolean
default: false
reconcile_floating:
description: 'Repoint :latest and :latest-rc onto the correct existing version images (no rebuild). Runs only the reconcile job. Off by default.'
type: boolean
default: false
# Read-only at the top level; write scopes live on the jobs below.
permissions:
@@ -70,9 +74,9 @@ jobs:
contents: read
packages: write # push the image to GHCR via GITHUB_TOKEN
# Gated to this repository; inert in forks and mirrors. Skip the (re)build
# on schedule and on a force_nightly dispatch — those only drive
# promote-nightly.
if: github.repository == 'omnigent-ai/omnigent' && github.event_name != 'schedule' && !inputs.force_nightly
# on schedule, force_nightly, and reconcile_floating dispatches — those only
# drive the promote-nightly / reconcile-floating jobs.
if: github.repository == 'omnigent-ai/omnigent' && github.event_name != 'schedule' && !inputs.force_nightly && !inputs.reconcile_floating
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -220,3 +224,65 @@ jobs:
echo "::warning::${img}:latest-dev not found yet; skipping nightly promotion"
fi
done
reconcile-floating:
# Manual reconcile (workflow_dispatch with reconcile_floating=true): repoint
# :latest and :latest-rc onto the correct EXISTING version images, computed
# from the tag list with PEP 440 ordering. Retags via imagetools (no
# rebuild). Idempotent — also a "fix the floating tags if they ever drift"
# button, and the way to backfill them for releases cut before this scheme.
if: github.repository == 'omnigent-ai/omnigent' && inputs.reconcile_floating
permissions:
contents: read
packages: write # retag within GHCR via GITHUB_TOKEN
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Set up uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
with:
enable-cache: false
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Reconcile :latest and :latest-rc
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
ALL_TAGS=$(gh api "repos/${GH_REPO}/tags" --paginate --jq '.[].name')
read -r RC_TAG LATEST_TAG < <(ALL_TAGS="${ALL_TAGS}" \
uv run --with packaging --no-project python .github/scripts/oss-publish-images/reconcile_targets.py)
echo "targets: latest-rc<-${RC_TAG} latest<-${LATEST_TAG}"
# dst=floating tag, src=version tag (skip when src is "-" or its image is absent).
retag() {
local img="$1" dst="$2" src="$3"
if [ "${src}" = "-" ]; then
echo "::warning::no source for ${img}:${dst}; skipping"
return
fi
if docker buildx imagetools inspect "${img}:${src}" >/dev/null 2>&1; then
docker buildx imagetools create -t "${img}:${dst}" "${img}:${src}"
echo "set ${img}:${dst} -> ${src}"
else
echo "::warning::${img}:${src} image not found; skipping ${img}:${dst}"
fi
}
for img in ghcr.io/omnigent-ai/omnigent-server ghcr.io/omnigent-ai/omnigent-host; do
retag "${img}" "latest-rc" "${RC_TAG}"
retag "${img}" "latest" "${LATEST_TAG}"
done