Skip to content

Commit 2ac481c

Browse files
committed
fix(git): system-git fallback for Azure DevOps repositories (#288)
go-git v5 strips MultiACK/MultiACKDetailed from its capability advertisement. ADO rejects any upload-pack request that omits multi_ack with HTTP 400 (TF401041: "Clients must support multi-ack."). This change adds a per-URL system-git fallback: any remote whose URL contains dev.azure.com, visualstudio.com, or ssh.dev.azure.com is routed through exec.Command("git", ...) instead of go-git. All other providers continue to use the existing go-git path unchanged. Three entry points now delegate to system git for ADO URLs: - SmartFetch → systemGitSmartFetch (git ls-remote + git fetch) - PushAtomic → systemGitPushAtomic (git ls-remote + git push --force-with-lease) - CheckRepo → systemGitCheckRepo (git ls-remote --symref) Credentials are never passed via argv: - BasicAuth: GIT_CONFIG_GLOBAL with http.extraHeader = Authorization: Basic <base64> - TokenAuth: GIT_CONFIG_GLOBAL with http.extraHeader = Authorization: Bearer <token> - SSH: GIT_SSH_COMMAND with -i <keyfile> and optional known_hosts Dockerfile: adds a git-bundle Alpine stage that collects git and all its musl shared-library dependencies via ldd, then copies them into the distroless final image. The distroless/static:debug base already includes /bin/sh (busybox), which is required to execute the GIT_ASKPASS script. Proper fix: go-git PR #1204 (full multi_ack implementation) is targeted for v6.0.0, which is still in alpha as of 2026-07-28. Drop this fallback and upgrade to go-git v6 once a stable release lands.
1 parent aed4af9 commit 2ac481c

9 files changed

Lines changed: 907 additions & 35 deletions

File tree

Dockerfile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,33 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
4949
-ldflags "-X main.version=${VERSION} -X main.gitCommit=${GIT_COMMIT} -X main.gitDirty=${GIT_DIRTY} -X main.buildDate=${BUILD_DATE}" \
5050
-o manager ./cmd
5151

52+
# Bundle git and all its shared-library deps (musl libc, openssl, curl, etc.) so that
53+
# the final distroless image can shell out to git for Azure DevOps repositories (which
54+
# require multi_ack capability that go-git v5 does not implement).
55+
FROM alpine:3.24@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS git-bundle
56+
RUN set -eux; \
57+
apk add --no-cache git openssh-client; \
58+
mkdir -p /bundle/bin /bundle/lib /bundle/usr/lib /bundle/usr/libexec; \
59+
cp -L /usr/bin/git /bundle/bin/; \
60+
cp -L /usr/bin/ssh /bundle/bin/; \
61+
cp -rL /usr/libexec/git-core /bundle/usr/libexec/; \
62+
cp -L /lib/ld-musl*.so* /bundle/lib/; \
63+
for f in /usr/bin/git \
64+
/usr/bin/ssh \
65+
/usr/libexec/git-core/git-remote-http \
66+
/usr/libexec/git-core/git-remote-https; do \
67+
[ -f "$f" ] || continue; \
68+
ldd "$f" 2>/dev/null \
69+
| awk '/ => /{ print $3 }' \
70+
| while read -r so; do \
71+
[ -f "$so" ] || continue; \
72+
case "$so" in \
73+
/lib/*) cp -Ln "$so" /bundle/lib/ 2>/dev/null || true ;; \
74+
/usr/lib/*) cp -Ln "$so" /bundle/usr/lib/ 2>/dev/null || true ;; \
75+
esac; \
76+
done; \
77+
done
78+
5279
FROM alpine:3.24@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS sops-downloader
5380
ARG TARGETARCH
5481
# Keep current: the CI image-scan gate fails on fixable CRITICALs in this
@@ -69,6 +96,15 @@ FROM gcr.io/distroless/static:debug@sha256:e741251ccc55dd6cec4a99ff21c0766df3189
6996
WORKDIR /
7097
COPY --from=builder /workspaces/manager .
7198
COPY --from=sops-downloader /usr/local/bin/sops /usr/local/bin/sops
99+
# git, ssh, and their musl-linked deps for the ADO system-git fallback.
100+
# busybox provides /bin/sh so git can invoke GIT_SSH_COMMAND via shell parsing.
101+
# Alpine's busybox is musl-linked and needs only the libs already copied below.
102+
COPY --from=git-bundle /bin/busybox /bin/sh
103+
COPY --from=git-bundle /bundle/bin/git /usr/bin/git
104+
COPY --from=git-bundle /bundle/bin/ssh /usr/bin/ssh
105+
COPY --from=git-bundle /bundle/usr/libexec/git-core /usr/libexec/git-core
106+
COPY --from=git-bundle /bundle/lib/ /lib/
107+
COPY --from=git-bundle /bundle/usr/lib/ /usr/lib/
72108
USER 65532:65532
73109

74110
ENTRYPOINT ["/manager"]

docs/configuration.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,29 @@ spec:
9292
- main
9393
```
9494
95+
### Azure DevOps repositories
96+
97+
Azure DevOps (`dev.azure.com`, `*.visualstudio.com`, `ssh.dev.azure.com`) is supported.
98+
Use **HTTPS with a Personal Access Token** as the recommended credential. ADO PATs must be
99+
sent as HTTP Basic auth with an empty username and the PAT as the password:
100+
101+
```yaml
102+
spec:
103+
url: https://dev.azure.com/<org>/<project>/_git/<repo>
104+
secretRef:
105+
name: ado-creds # Secret with keys: username (empty string) and password (your PAT)
106+
```
107+
108+
Microsoft Entra ID (OAuth) access tokens use the `bearerToken` Secret key instead.
109+
SSH is also supported using the `ssh.dev.azure.com` URL format with standard `ssh-privatekey` and `known_hosts` credentials.
110+
111+
> **Implementation note:** go-git v5 does not implement the `multi_ack` capability that ADO
112+
> requires (ADO rejects requests without it with HTTP 400). The operator automatically routes
113+
> ADO URLs through the system `git` binary instead. This is transparent — no configuration
114+
> change is needed — but requires `git` to be present in the container image. The published
115+
> image includes it. This fallback will be removed once go-git v6 ships with full `multi_ack`
116+
> support (tracked in go-git PR #1204).
117+
95118
### `GitProvider.spec.secretRef`: the credentials Secret
96119

97120
The referenced Secret holds the Git credentials. The examples use the **Kubernetes-native** keys,

0 commit comments

Comments
 (0)