Three-part fix that unblocks the SME tenant pipeline post-Day-2- Independence cutover. Live-reproduced on otech103 — POST /api/v1/sme/ tenants succeeds (HTTP 202) but the first reconcile fails with "gitops token unconfigured" → after wiring the env, fails with `exec: "git": executable file not found in $PATH` → after fixing the URL hardcoding, would still 401 against local Gitea because the basic-auth username is hardcoded "x-access-token". Part A — code (marketplace_settings.go + sme_tenant_gitops.go): - Add gitOpsConfig.User (loaded from CATALYST_GITOPS_USER env, default "x-access-token" for back-compat with GitHub PATs). - New injectTokenIntoURLWithUser(rawURL, user, token) — variant of injectTokenIntoURL that takes a configurable basic-auth username. - Update all 3 call sites in marketplace_settings.go + sme_tenant_gitops.go to use the new variant with cfg.User. Part B — Containerfile: - apk add git in the runtime stage. The SME tenant pipeline (#804) and marketplace-settings GitOps writer both shell out to git clone/commit/push; without the binary every first reconcile fails. Part C — chart (api-deployment.yaml): - Wire CATALYST_GITOPS_USER + CATALYST_GITOPS_TOKEN envs on catalyst-api Deployment, sourced from the local `gitea-admin-secret` (already mirrored into catalyst-system via bp-reflector annotation per #866). optional=true so Catalyst-Zero (contabo) keeps using its existing GitHub PAT path. Bump bp-catalyst-platform 1.4.10 -> 1.4.11 + lockstep slot 13 pin. Closes #878 Co-authored-by: hatiyildiz <hatice.yildiz@openova.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8e4c88fd28
commit
7bdd14fcb1
@ -104,7 +104,7 @@ spec:
|
||||
# /auth/send-pin → SendMagicLink (and /auth/verify-pin →
|
||||
# VerifyMagicLink) so the UI's PIN-naming reaches the existing
|
||||
# backend handler.
|
||||
version: 1.4.10
|
||||
version: 1.4.11
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: bp-catalyst-platform
|
||||
|
||||
@ -77,13 +77,18 @@ FROM docker.io/library/alpine:3.20
|
||||
ARG KUBECTL_VERSION=v1.31.4
|
||||
ARG HELM_VERSION=v3.16.3
|
||||
|
||||
RUN apk add --no-cache ca-certificates curl bash \
|
||||
RUN apk add --no-cache ca-certificates curl bash git \
|
||||
&& curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl \
|
||||
&& chmod +x /usr/local/bin/kubectl \
|
||||
&& curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" | tar xz -C /tmp \
|
||||
&& mv /tmp/linux-amd64/helm /usr/local/bin/helm \
|
||||
&& rm -rf /tmp/linux-amd64 \
|
||||
&& chmod +x /usr/local/bin/helm
|
||||
# git is required by the SME tenant provisioning pipeline (#804) and the
|
||||
# marketplace-settings GitOps writer — both shell out to `git clone /
|
||||
# commit / push` against the catalyst-zero / Sovereign GitOps repo.
|
||||
# Without git on PATH the pipeline fails with `exec: "git": executable
|
||||
# file not found` at the first reconcile (issue #878).
|
||||
|
||||
# tofu CLI from the verified-checksum builder stage. Installed mode 0755 so
|
||||
# every user (including UID 65534 from runAsUser) can execute it.
|
||||
|
||||
@ -172,12 +172,19 @@ func (h *Handler) HandleSetMarketplace(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// gitOpsConfig captures the clone-target URL, branch, token, and committer
|
||||
// identity for the GitOps push. Every field is runtime-configurable per
|
||||
// INVIOLABLE-PRINCIPLES.md #4.
|
||||
// gitOpsConfig captures the clone-target URL, branch, user/token, and
|
||||
// committer identity for the GitOps push. Every field is runtime-
|
||||
// configurable per INVIOLABLE-PRINCIPLES.md #4.
|
||||
type gitOpsConfig struct {
|
||||
RepoURL string
|
||||
Branch string
|
||||
RepoURL string
|
||||
Branch string
|
||||
// User is the basic-auth username embedded in the clone URL. GitHub
|
||||
// PATs accept any username (canonical "x-access-token"); Gitea
|
||||
// requires the real account name. Wired via CATALYST_GITOPS_USER so
|
||||
// the SAME catalyst-api binary works against both GitHub (Catalyst-
|
||||
// Zero pre-cutover) and the Sovereign-side local Gitea (post-Day-2-
|
||||
// Independence). Issue #878.
|
||||
User string
|
||||
Token string
|
||||
CommitterName string
|
||||
CommitterMail string
|
||||
@ -187,6 +194,7 @@ func loadGitOpsConfig() gitOpsConfig {
|
||||
return gitOpsConfig{
|
||||
RepoURL: envOr("CATALYST_GITOPS_REPO_URL", "https://github.com/openova-io/openova"),
|
||||
Branch: envOr("CATALYST_GITOPS_BRANCH", "main"),
|
||||
User: envOr("CATALYST_GITOPS_USER", "x-access-token"),
|
||||
Token: os.Getenv("CATALYST_GITOPS_TOKEN"),
|
||||
CommitterName: envOr("CATALYST_GITOPS_COMMITTER_NAME", "catalyst-api"),
|
||||
CommitterMail: envOr("CATALYST_GITOPS_COMMITTER_EMAIL", "ops@openova.io"),
|
||||
@ -208,7 +216,7 @@ func envOr(key, fallback string) string {
|
||||
func writeMarketplaceOverlay(ctx context.Context, cfg gitOpsConfig, sovereignFQDN string, body SetMarketplaceRequest, log *slog.Logger) (string, error) {
|
||||
// Build the authenticated clone URL once so the token is never echoed
|
||||
// to a subprocess argument list (visible to /proc/<pid>/cmdline).
|
||||
authURL, err := injectTokenIntoURL(cfg.RepoURL, cfg.Token)
|
||||
authURL, err := injectTokenIntoURLWithUser(cfg.RepoURL, cfg.User, cfg.Token)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rewrite repo URL: %w", err)
|
||||
}
|
||||
@ -501,27 +509,44 @@ func runGitOutput(ctx context.Context, dir string, args ...string) (string, erro
|
||||
}
|
||||
|
||||
// injectTokenIntoURL rewrites https://github.com/foo into
|
||||
// https://x-access-token:<TOKEN>@github.com/foo so `git clone` can
|
||||
// authenticate against GitHub without an SSH key. Returns an error if
|
||||
// the URL is not http/https.
|
||||
// https://<USER>:<TOKEN>@github.com/foo so `git clone` can authenticate
|
||||
// without an SSH key. The user is configurable so the same code path
|
||||
// works for:
|
||||
// - GitHub PATs — user="x-access-token" (default; GitHub ignores
|
||||
// the username when the token is a PAT)
|
||||
// - Local Gitea — user="gitea_admin" (Gitea checks the username on
|
||||
// basic auth; "x-access-token" returns 401)
|
||||
//
|
||||
// Returns an error if the URL is not http/https.
|
||||
func injectTokenIntoURL(rawURL, token string) (string, error) {
|
||||
return injectTokenIntoURLWithUser(rawURL, "x-access-token", token)
|
||||
}
|
||||
|
||||
// injectTokenIntoURLWithUser is the configurable variant. Issue #878 —
|
||||
// post-cutover Sovereign uses local Gitea, which requires the real
|
||||
// admin username (default GitHub PAT username "x-access-token" returns
|
||||
// 401). Loaded from CATALYST_GITOPS_USER env via gitOpsConfig.User.
|
||||
func injectTokenIntoURLWithUser(rawURL, user, token string) (string, error) {
|
||||
if token == "" {
|
||||
return rawURL, nil
|
||||
}
|
||||
if user == "" {
|
||||
user = "x-access-token"
|
||||
}
|
||||
if strings.HasPrefix(rawURL, "https://") {
|
||||
// Strip any pre-existing userinfo, then re-inject.
|
||||
stripped := strings.TrimPrefix(rawURL, "https://")
|
||||
if at := strings.IndexByte(stripped, '@'); at >= 0 {
|
||||
stripped = stripped[at+1:]
|
||||
}
|
||||
return "https://x-access-token:" + token + "@" + stripped, nil
|
||||
return "https://" + user + ":" + token + "@" + stripped, nil
|
||||
}
|
||||
if strings.HasPrefix(rawURL, "http://") {
|
||||
stripped := strings.TrimPrefix(rawURL, "http://")
|
||||
if at := strings.IndexByte(stripped, '@'); at >= 0 {
|
||||
stripped = stripped[at+1:]
|
||||
}
|
||||
return "http://x-access-token:" + token + "@" + stripped, nil
|
||||
return "http://" + user + ":" + token + "@" + stripped, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported repo URL scheme: %s", rawURL)
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ func (w DefaultSMETenantGitOpsWriter) WriteTenantOverlay(ctx context.Context, re
|
||||
}
|
||||
}()
|
||||
|
||||
authURL, err := injectTokenIntoURL(cfg.RepoURL, cfg.Token)
|
||||
authURL, err := injectTokenIntoURLWithUser(cfg.RepoURL, cfg.User, cfg.Token)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rewrite repo URL: %w", err)
|
||||
}
|
||||
@ -174,7 +174,7 @@ func (w DefaultSMETenantGitOpsWriter) DeleteTenantOverlay(ctx context.Context, r
|
||||
}
|
||||
defer os.RemoveAll(scratch)
|
||||
|
||||
authURL, err := injectTokenIntoURL(cfg.RepoURL, cfg.Token)
|
||||
authURL, err := injectTokenIntoURLWithUser(cfg.RepoURL, cfg.User, cfg.Token)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("rewrite repo URL: %w", err)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
apiVersion: v2
|
||||
name: bp-catalyst-platform
|
||||
version: 1.4.10
|
||||
appVersion: 1.4.10
|
||||
version: 1.4.11
|
||||
appVersion: 1.4.11
|
||||
description: |
|
||||
Catalyst Platform — the unified Catalyst control plane umbrella chart for Catalyst-Zero.
|
||||
Composes the catalyst-{ui,api}, console, admin, marketplace UI modules and the marketplace-api backend.
|
||||
@ -614,6 +614,24 @@ description: |
|
||||
pipeline) but ultimately point at the Sovereign's public FQDN.
|
||||
optional=true since Catalyst-Zero (contabo) doesn't run the SME
|
||||
tenant pipeline. Lockstep slot 13 pin bumps to 1.4.10. 2026-05-05.
|
||||
|
||||
1.4.11 (issue #878): wire CATALYST_GITOPS_USER + CATALYST_GITOPS_TOKEN
|
||||
env on the catalyst-api Deployment, sourced from the local Gitea
|
||||
admin secret (`gitea-admin-secret`, keys `username` + `password`).
|
||||
Without these, the SME tenant pipeline (#804) and the marketplace-
|
||||
settings GitOps writer fail at the first reconcile with "gitops
|
||||
token unconfigured" (post-cutover Sovereign has no GitHub PAT — the
|
||||
GitOps target is the local Gitea). optional=true so Catalyst-Zero
|
||||
(contabo) keeps using the existing GitHub PAT path. Pairs with a
|
||||
catalyst-api code change (marketplace_settings.go +
|
||||
sme_tenant_gitops.go): injectTokenIntoURL now takes a configurable
|
||||
username (was hardcoded "x-access-token"; GitHub PAT-only) so the
|
||||
same code path works for both GitHub and Gitea. Also adds `git` to
|
||||
the catalyst-api Containerfile (Alpine 3.20 base + apk add git) —
|
||||
the pipeline shells out to git clone/commit/push, and without the
|
||||
binary the first reconcile fails with `exec: "git": executable
|
||||
file not found in $PATH`. Lockstep slot 13 pin bumps to 1.4.11.
|
||||
2026-05-05.
|
||||
type: application
|
||||
|
||||
# Opt-out from the blueprint-release hollow-chart guard (issue #181 / #510).
|
||||
|
||||
@ -524,6 +524,30 @@ spec:
|
||||
name: sovereign-fqdn
|
||||
key: fqdn
|
||||
optional: true
|
||||
# CATALYST_GITOPS_USER + CATALYST_GITOPS_TOKEN — basic-auth
|
||||
# credentials embedded in the GitOps clone URL (issue #878).
|
||||
# Pre-cutover (Catalyst-Zero): User=x-access-token, Token=GitHub
|
||||
# PAT (already wired via separate CATALYST_GITOPS_TOKEN secret on
|
||||
# contabo). Post-cutover (Sovereign): User=gitea_admin,
|
||||
# Token=<gitea-admin-password> from the local Gitea admin secret.
|
||||
# The same secret (`gitea-admin-secret`) is mirrored into
|
||||
# catalyst-system via the bp-reflector annotation block on
|
||||
# bp-gitea (issue #866), so this Sovereign-side wiring works
|
||||
# post-Day-2-Independence without a manual mirror step.
|
||||
# optional=true: Catalyst-Zero (contabo) does not run the SME
|
||||
# tenant pipeline.
|
||||
- name: CATALYST_GITOPS_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-admin-secret
|
||||
key: username
|
||||
optional: true
|
||||
- name: CATALYST_GITOPS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-admin-secret
|
||||
key: password
|
||||
optional: true
|
||||
# CATALYST_HANDOVER_KEY_PATH — path to the RS256 PRIVATE key
|
||||
# catalyst-api uses to mint magic-link + handover JWTs. The
|
||||
# signer auto-generates the keypair on first start if absent.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user