feat(artifact-registry): resolve artifact registries in the Docker credential helper
Step 3 of artifact-registry#222. Depends on step 1 and step 2 (both merged into main), so this MR targets sshen/ar-get-token and stacks on top of it.
glab auth docker-helper get now tries an Artifact Registry token exchange before falling back to the existing container-registry credentials, so docker pull/push against an artifact-registry-backed domain authenticates without a new command. Precedence: artifact registry first; container registry only as a fallback; a domain configured only for artifact registry gets its real exchange error rather than "unknown domain".
Adds artifact_registry_domains, a per-host config key mirroring container_registry_domains but with no shipped default: glab config set artifact_registry_domains <domain> --host <host> is how a domain gets associated with a GitLab instance for this path.
A few things worth calling out up front:
- The API client must not resolve the token from the environment. Docker runs this helper as a subprocess that inherits the user's shell environment, so a stray
GITLAB_TOKEN/GITLAB_ACCESS_TOKENmust not decide which identity mints the artifact-registry token.api.WithoutTokenFromEnvironment()is a new option onNewClientFromConfigfor this.job_token/CI_JOB_TOKENis untouched. readDomains/findAssociatedHostnameare now generalized intofindHostnameByDomain, parameterized by config key, so the container-registry and artifact-registry lookups share one host-iteration/error-collection loop instead of a third near-copy of it.- Read errors are reported as themselves, not as "unknown domain". A key that is simply unset reads back as
""with no error; an error fromGetWithSourceis a real read failure (broken YAML, a locked keyring), and one broken host's entry doesn't hide a match on a different host. artifactregistry.DefaultGetTokenDurationis used only by this credential helper, notget-token(which validates and uses its own--durationflag defaulting toMinDuration). Docker gives the helper no way to pass a duration, and a token expiring mid-pull would hard-fail it, hence a longer default thanMinDuration.