Skip to content

feat(DEVS-1547): support pushing images to multiple registries - #165

Merged
0x46616c6b merged 10 commits into
mainfrom
DEVS-1547-multi-registry-push
Sep 30, 2026
Merged

0x46616c6b merged 10 commits into
mainfrom
DEVS-1547-multi-registry-push

Conversation

@0x46616c6b

Copy link
Copy Markdown
Contributor

Ticket: https://mitarbeiterapp.atlassian.net/browse/DEVS-1547

Type of Change

  • Bugfix
  • Enhancement / new feature
  • Refactoring
  • Documentation

Description

Replaces the single docker-registry input with docker-registries, a newline-separated registry[|username[|password]] list. Lets a service dual-write during a Harbor → GAR migration and roll back without a rebuild: every build, merge and retag pushes to all configured registries, while GitOps manifests and release-retag lookups always use the first (primary) entry. A registry entry can also carry a path prefix after the host (e.g. GAR's europe-docker.pkg.dev/staffbase-artifacts/images-publish) for registries that address a project/repository as part of the push path.

Breaking change: docker-registry is removed. A single value still works unchanged via docker-registries (default registry.staffbase.com). gha-workflows/template_gitops.yml is already updated for this; direct callers of this action need to rename the input.

Verified end-to-end with a real dual-push to Harbor + GAR from backstage-app (run), pinned to this branch, then reverted.

Checklist

  • Write tests
  • Make sure all tests pass
  • Update documentation
  • Review the Contributing Guideline and sign CLA
  • Reference relevant issue(s) and close them after merging

🤖 Generated with Claude Code

0x46616c6b and others added 2 commits September 28, 2026 15:52
Replaces the single docker-registry input with docker-registries, a
newline-separated "registry[|username[|password]]" list. This lets a
service dual-write during a Harbor -> GAR migration and roll back
without a rebuild: every build, merge and retag pushes to all
configured registries, while GitOps manifests and release-retag
lookups always use the first (primary) entry.

Breaking change: docker-registry is removed. A single value still
works unchanged via docker-registries (default registry.staffbase.com).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ath prefix

A registry entry can carry a project/repository path after the host
(e.g. GAR's europe-docker.pkg.dev/staffbase-artifacts/images-publish),
needed so the pushed image ref includes it. docker login only accepts
a bare host, so strip to it before logging in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@0x46616c6b 0x46616c6b added the major Pull requests with breakable changes label Sep 28, 2026
@0x46616c6b
0x46616c6b marked this pull request as ready for review September 28, 2026 14:15
@0x46616c6b
0x46616c6b requested review from a team as code owners September 28, 2026 14:15
@soemo
soemo requested a lite review from Copilot September 28, 2026 14:28

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Critical workflow credential gating and primary-registry retagging issues remain unresolved.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Adds multi-registry Docker image publishing with per-registry credentials, primary-registry retagging, documentation, and tests.

Changes:

  • Replaces docker-registry with newline-separated docker-registries.
  • Adds registry parsing, login, tag generation, and retag replication.
  • Updates action wiring, documentation, and Bats coverage.
File Summary
tests/​retag-image.bats Tests retag replication and validation.
tests/​login-registries.bats Tests multi-registry authentication.
tests/​lib-registries.bats Tests registry parsing and credentials.
tests/​generate-tags.bats Tests multi-registry tag generation.
scripts/​retag-image.sh Retags and replicates images across registries.
scripts/​login-registries.sh Logs into configured registries.
scripts/​lib/​registries.sh Parses registry entries and credentials.
scripts/​generate-tags.sh Generates tags for configured registries.
README.md Documents configuration and migration behavior.
action.yml Defines inputs and workflow integration.

Unresolved issues affect credential-aware login/build execution and primary-registry retagging, including API host and path handling.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread action.yml Outdated
Comment thread scripts/retag-image.sh Outdated
Buildx setup, login and build were gated solely on top-level
docker-username/docker-password. A registry entry supplying its
credentials entirely inline (as documented) left both unset, so login
and build silently skipped with no image pushed. Gate on a new
has_credentials output instead, computed from every resolved registry
entry.

Retagging's manifest GET/PUT also always authenticated with the raw
top-level credentials, ignoring the primary registry's own resolved
ones. A primary configured with only inline credentials either failed
validation or sent the wrong credentials. It now authenticates with
the primary entry's resolved username/password.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Primary-registry API and credential fallback wiring remain unresolved, with incomplete credentials potentially causing partial pushes.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)
Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Comment incorrectly claims legacy registry input compatibility

scripts/​lib/​registries.sh:14

This comment claims action.yml synthesizes INPUT_DOCKER_REGISTRIES from the removed docker-registry input, but action.yml has no such input or fallback; the action only supplies the new input's default. That contradicts the documented breaking change and can mislead callers of these scripts into expecting legacy compatibility. Please update the comment to describe the actual docker-registries-only contract.

Comment thread action.yml
Comment thread action.yml
Comment thread scripts/retag-image.sh
Comment thread scripts/generate-tags.sh Outdated
…I from primary

Generate Tags never received the top-level docker-username/
docker-password, so has_credentials was always false for the
documented single-registry-plus-top-level-creds configuration,
silently skipping login and build.

docker-registry-api was a separate static input decoupled from
docker-registries, so reordering the list to make a different
registry primary (the documented rollback path) still retagged the
old primary. It now defaults to the standard v2 API form derived from
the primary entry's host, overridable for non-standard endpoints.

Also: a docker-registries list with some entries credentialed and
others not now fails fast instead of letting buildx attempt an
unauthenticated push to the entries login-registries.sh skipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Primary registry path prefixes break release-retag lookups and related tests must be corrected.

Review effort: Lite
Findings: 3 High severity

Open (3)
Resolved since last review (3)

Comment thread action.yml
Comment thread scripts/generate-tags.sh Outdated
…d retag API

The derived docker-registry-api stripped everything after the host,
so a primary with a path prefix (e.g. GAR's
europe-docker.pkg.dev/staffbase-artifacts/images-publish) lost it: the
manifest API's literal /v2/ segment sits right after the bare host,
but the path prefix is part of the <name> the API addresses, appended
after /v2/, not before it. retag-image.sh only appends
INPUT_DOCKER_IMAGE to this URL, so dropping the prefix 404s against
any registry that addresses a project/repository this way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🔵 Needs a closer look

Blank-line-only registry inputs need explicit validation before approval.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Reject blank-line-only registry input with clear error

scripts/​lib/​registries.sh:33

A non-empty input containing only blank lines leaves REGISTRIES empty because blank lines are skipped, but callers immediately dereference REGISTRIES[0] under set -u. This makes a valid blank-line-only list fail with REGISTRIES[0]: unbound variable instead of a clear input error. Validate that at least one registry was parsed (and return non-zero) after the loop.

resolve_registries silently left REGISTRIES empty, so the first
dereference of REGISTRIES[0] downstream crashed on "unbound variable"
under set -u instead of a clear input error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🔵 Needs a closer look

Moderate issues remain in malformed registry validation and multi-architecture secondary pushes.

Review effort: Lite
Findings: 2 High severity

Open (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Document path preservation in derived default endpoint

action.yml:11

The input description says the default is derived from the primary entry's host as https://<host>/v2/, but path-prefixed entries are actually derived as https://<host>/v2/<path>/. Document the preserved path here as well, otherwise the action metadata contradicts the implemented GAR behavior.

…derived default

The description said the derived default is always https://<host>/v2/,
but a path-prefixed primary (e.g. GAR) preserves that path after /v2/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🔵 Needs a closer look

Resolve duplicate-host credential handling; documentation nits also remain.

Review effort: Lite
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Document preserved registry path prefixes in derived API URLs

README.md:224

This input-table description says the derived API defaults to the primary registry's host only, but the implementation deliberately preserves the primary entry's path prefix (generate-tags.sh:24-34) and the action metadata documents that behavior as well. The current text is misleading for path-scoped registries and should describe the actual https://<host>/v2/<path>/ form.

… inputs table

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

Unresolved critical replication issues and a credential-handling defect block approval.

Review effort: Lite
Findings: 2 High severity

Open (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Rename test to reflect the default registry configuration it covers

tests/​generate-tags.bats:361

This test is named as if it verifies behavior when INPUT_DOCKER_REGISTRIES is unset, but setup() always exports that variable and the old docker-registry input has been intentionally removed. It therefore provides no coverage for the condition it claims to test and can mislead future changes; rename it to describe the default value it actually exercises.

Comment thread scripts/generate-tags.sh
Comment thread scripts/retag-image.sh
It was named as if it tested the removed docker-registry input's
fallback, but setup() always exports INPUT_DOCKER_REGISTRIES now — it
only covers the default value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

GAR retag authentication, duplicate-host credential handling, and empty registry validation remain unresolved.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (2)

Comment thread scripts/login-registries.sh
Comment thread scripts/retag-image.sh Outdated
…licate-host credentials

Release-retag's manifest GET/PUT always sent HTTP Basic auth. Harbor
accepts that directly, but Google Artifact/Container Registry's raw
registry API requires a Bearer token for the "oauth2accesstoken"
convention docker login/gcloud auth configure-docker use — Basic auth
with that username fails against GAR. Detect it and send an
Authorization: Bearer header instead.

Also: Docker's credential store is keyed by host alone, so two
docker-registries entries sharing a host with different credentials
silently overwrote each other on login, breaking whichever entry
logged in first. login-registries.sh now fails fast on that
conflict and skips a harmless repeat login when credentials match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

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.

Copilot review overview

🔵 Needs a closer look

Reject registry entries with an empty host before processing them.

Review effort: Lite
Findings: None

Resolved since last review (2)

Comment thread action.yml
inputs:
docker-registry:
description: 'Docker Registry'
docker-registries:

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.

Hi, as you changed now the input, does this works e.g. for krusty-krab now https://github.com/search?q=org%3AStaffbase+docker-registry+NOT+is%3Aarchived&type=code

Are you aware of it?

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.

They need to upgrade the action by changing the input parameters. This PR introduces breaking changes on purpose.

@0x46616c6b
0x46616c6b merged commit b791c3c into main Sep 30, 2026
11 checks passed
@0x46616c6b
0x46616c6b deleted the DEVS-1547-multi-registry-push branch September 30, 2026 09:49
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

feature major Pull requests with breakable changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants