Commit 5248d486 authored by xuwang's avatar xuwang
Browse files

fix(renovate): derive endpoint from remote host; scrub internal RENOVATE_* env



Two bugs found testing against a gitlab.med consumer (som-irt-services):

- Endpoint was hardcoded to ${GITLAB_SERVER:-code.stanford.edu} while the repo
  path was derived from the git remote, so a repo on another server (gitlab.med)
  got the wrong endpoint. Default GITLAB_SERVER to the remote's host instead
  (explicit GITLAB_SERVER still wins; code.stanford.edu is the last-resort fallback).
- Renovate reads every RENOVATE_* env var as config, so exporting RENOVATE_CONFIG
  (our config-file path) made Renovate parse the literal "renovate.json" as inline
  JSON and abort. Invoke Renovate in a subshell that unsets the OTICA-internal
  RENOVATE_* knobs and exports only token/platform/endpoint.

Changelog: fixed
Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent a8de2699
Loading
Loading
Loading
Loading
+43 −14
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@
#   RENOVATE_DOCKER_IMAGE default: renovate/renovate
#   RENOVATE_TOKEN        GitLab token (default: GITLAB_TOKEN / ~/.gitlab-token)
#                         scope: 'api' for real runs, 'read_api' for dry-runs
#   GITLAB_SERVER         default: https://code.stanford.edu
#   GITLAB_SERVER         default: the origin remote's host (else code.stanford.edu)
#   GITLAB_TOKEN[_FILE]   consumer GitLab token / file (default ~/.gitlab-token)
#
# We resolve the token/server here rather than sourcing gitlab.sh, which is built
@@ -39,7 +39,31 @@ RENOVATE_ARGS="${RENOVATE_ARGS:-}"
RENOVATE_AUTODISCOVER_FILTER="${RENOVATE_AUTODISCOVER_FILTER:-}"
RENOVATE_DOCKER_IMAGE="${RENOVATE_DOCKER_IMAGE:-renovate/renovate}"

# Parse the origin remote (https, scp-style, or ssh://) once.
_remote_url() { git config --get remote.origin.url 2>/dev/null || true; }

# "namespace/project" from the remote: strip scheme:// , user@ , host[:port][:/] , .git
derive_repo() {
    local url; url="$(_remote_url)"
    [ -n "${url}" ] || err "Cannot derive repo: no git remote 'origin'. Set RENOVATE_REPO=namespace/project."
    echo "${url}" | sed -E -e 's#^[a-z]+://##' -e 's#^[^@/]*@##' -e 's#^[^/:]+[:/]##' -e 's#\.git$##'
}

# "https://<host>" from the remote, so the endpoint targets the server the repo
# actually lives on (e.g. gitlab.med) instead of a hardcoded default.
derive_host() {
    local url; url="$(_remote_url)"
    [ -n "${url}" ] || return 1
    echo "${url}" | sed -E -e 's#^[a-z]+://##' -e 's#^[^@/]*@##' -e 's#[:/].*$##'
}

# Server precedence: explicit GITLAB_SERVER > the remote's host > code.stanford.edu.
GITLAB_SERVER="${GITLAB_SERVER:-}"
if [ -z "${GITLAB_SERVER}" ]; then
    _host="$(derive_host || true)"
    GITLAB_SERVER="${_host:+https://${_host}}"
    GITLAB_SERVER="${GITLAB_SERVER:-https://code.stanford.edu}"
fi
GITLAB_TOKEN_FILE="${GITLAB_TOKEN_FILE:-${HOME}/.gitlab-token}"
GITLAB_TOKEN="${GITLAB_TOKEN:-}"
if [ -z "${GITLAB_TOKEN}" ] && [ -f "${GITLAB_TOKEN_FILE}" ]; then
@@ -48,15 +72,6 @@ fi
RENOVATE_TOKEN="${RENOVATE_TOKEN:-${GITLAB_TOKEN}}"
RENOVATE_ENDPOINT="${RENOVATE_ENDPOINT:-${GITLAB_SERVER%/}/api/v4}"

# Derive "namespace/project" from the origin remote (https, scp-style, or ssh://).
derive_repo() {
    local url
    url="$(git config --get remote.origin.url 2>/dev/null || true)"
    [ -n "${url}" ] || err "Cannot derive repo: no git remote 'origin'. Set RENOVATE_REPO=namespace/project."
    # strip scheme:// , user@ , host[:port][:/] , trailing .git
    echo "${url}" | sed -E -e 's#^[a-z]+://##' -e 's#^[^@/]*@##' -e 's#^[^/:]+[:/]##' -e 's#\.git$##'
}

require_token() {   # require_token <scope>
    [ -n "${RENOVATE_TOKEN}" ] || err \
"RENOVATE_TOKEN (or GITLAB_TOKEN / ${GITLAB_TOKEN_FILE}) is required for '${MODE}' (scope: ${1}).
@@ -68,11 +83,25 @@ log_context() {
}

# ── Runtime invocation ────────────────────────────────────────────────────────
# Renovate reads EVERY `RENOVATE_*` env var as configuration, so the OTICA-internal
# knobs (RENOVATE_CMD/ARGS/CONFIG/REPO/DOCKER_IMAGE/AUTODISCOVER_FILTER) must not
# leak into Renovate's environment — RENOVATE_CONFIG in particular is Renovate's
# *inline JSON config* and a stray value aborts the run. `invoke` runs the command
# in a subshell that scrubs them and exports only the genuine Renovate settings
# (filter/repo/dry-run are passed as CLI flags instead).
invoke() {
    (
        unset RENOVATE_CMD RENOVATE_ARGS RENOVATE_CONFIG RENOVATE_REPO \
              RENOVATE_DOCKER_IMAGE RENOVATE_AUTODISCOVER_FILTER
        export RENOVATE_TOKEN RENOVATE_PLATFORM RENOVATE_ENDPOINT
        exec "$@"
    )
}

# Runs Renovate with the given args, auto-detecting the runtime unless
# RENOVATE_CMD overrides it. Token/platform/endpoint pass via the environment
# (token must never go on the command line).
run_renovate() {
    export RENOVATE_TOKEN RENOVATE_PLATFORM RENOVATE_ENDPOINT
    local -a base
    if [ -n "${RENOVATE_CMD}" ]; then
        read -r -a base <<< "${RENOVATE_CMD}"
@@ -89,7 +118,7 @@ run_renovate() {
        err "No Renovate runtime found. Install Node 20+ (renovate/npx) or Docker, or set RENOVATE_CMD."
    fi
    echo "+ ${base[*]} $*" >&2
    "${base[@]}" "$@"
    invoke "${base[@]}" "$@"
}

# Validates a renovate config file via renovate-config-validator.
@@ -110,7 +139,7 @@ run_validator() {
        err "No Renovate runtime found for validation. Install Node 20+ or Docker, or set RENOVATE_CMD."
    fi
    echo "+ ${base[*]} ${config}" >&2
    "${base[@]}" "${config}"
    invoke "${base[@]}" "${config}"
}

# ── Dispatch ──────────────────────────────────────────────────────────────────