Commit 2868d0d9 authored by xuwang's avatar xuwang
Browse files

fix(renovate): bot operator token honors RENOVATE_BOT_ADMIN_TOKEN over ambient



Found testing in som-irt-services (gitlab.med repo with a code.stanford.edu TF
backend): bot ops ran as the make-injected GITLAB_TOKEN (code server) against
gitlab.med and failed. Resolve the operator token as
RENOVATE_BOT_ADMIN_TOKEN > RENOVATE_BOT_ADMIN_TOKEN_FILE > GITLAB_TOKEN >
~/.gitlab-token (the explicit override wins over the ambient token), for all bot
ops. sa-create now uses that resolved token too. list reports API errors cleanly
instead of a jq crash.

Changelog: fixed
Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 32cce66f
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -216,8 +216,13 @@ want central control (Centralized) or explicit per-repo opt-in (Per-repo opt-in)

By default scans/CI authenticate as a person's PAT. For unattended use, provision
a dedicated **Renovate bot** (a GitLab access token whose bot user owns the MRs).
Created with your admin token (`GITLAB_TOKEN` / `~/.gitlab-token`, Owner/Maintainer
+ `api`) on the repo's own server.
The bot is managed with an **operator token** on the repo's own server (Owner/
Maintainer + `api`; instance admin for `sa-create`). Resolution:
`RENOVATE_BOT_ADMIN_TOKEN``RENOVATE_BOT_ADMIN_TOKEN_FILE` → ambient
`GITLAB_TOKEN``~/.gitlab-token`. Set `RENOVATE_BOT_ADMIN_TOKEN_FILE` when the
project's `GITLAB_TOKEN` targets a different server than the repo (e.g. a
code.stanford.edu Terraform backend on a gitlab.med repo) — it wins over that
ambient token.

| Target | What it does |
| --- | --- |
+21 −13
Original line number Diff line number Diff line
@@ -43,6 +43,16 @@ source "${THIS_DIR}/functions.sh"
_remote_url() { git config --get remote.origin.url 2>/dev/null || true; }
_host="$(_remote_url | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#[:/].*$##')"
[ -n "${_host}" ] && export GITLAB_SERVER="https://${_host}"
# Operator token for managing bot identities. An explicit RENOVATE_BOT_ADMIN_TOKEN
# [_FILE] wins over the ambient GITLAB_TOKEN, so a project whose GITLAB_TOKEN
# targets a DIFFERENT server (e.g. a code.stanford.edu Terraform backend while the
# repo is on gitlab.med) still manages bots on THIS repo's server. It needs
# Owner/Maintainer + api on the target (instance admin for sa-create).
if [ -n "${RENOVATE_BOT_ADMIN_TOKEN:-}" ]; then
    GITLAB_TOKEN="${RENOVATE_BOT_ADMIN_TOKEN}"
elif [ -n "${RENOVATE_BOT_ADMIN_TOKEN_FILE:-}" ] && [ -f "${RENOVATE_BOT_ADMIN_TOKEN_FILE}" ]; then
    GITLAB_TOKEN="$(cat "${RENOVATE_BOT_ADMIN_TOKEN_FILE}")"
fi
GITLAB_TOKEN="${GITLAB_TOKEN:-}"                 # predeclare for set -u in gitlab.sh
source "${THIS_DIR}/gitlab.sh"                   # GITLAB_API, get_project_id, token

@@ -86,9 +96,11 @@ cmd_create() {
}

cmd_list() {
    local resp; resp=$(curl -s "${hdr[@]}" "${api}")
    echo "${resp}" | jq -e 'type=="array"' >/dev/null 2>&1 \
        || err "API error on ${KIND} ${TARGET}: $(echo "${resp}" | jq -r '.message // .error // .' 2>/dev/null || echo "${resp}") — check the token's server/permissions (RENOVATE_BOT_ADMIN_TOKEN[_FILE])."
    echo "${NAME} tokens on ${KIND} ${TARGET}:"
    curl -s "${hdr[@]}" "${api}" \
        | jq -r --arg n "${NAME}" '.[]? | select(.name==$n) | "  id=\(.id) access=\(.access_level) active=\(.active) expires=\(.expires_at)"'
    echo "${resp}" | jq -r --arg n "${NAME}" '.[] | select(.name==$n) | "  id=\(.id) access=\(.access_level) active=\(.active) expires=\(.expires_at)"'
}

cmd_revoke() {
@@ -121,22 +133,18 @@ cmd_ci_set() {
# When sub-repos span multiple top-level groups, a single group/project token
# can't cover them. Use one bot USER (service account) added to each group.

# Create an instance service account + a PAT for it. Needs an INSTANCE-ADMIN token
# (RENOVATE_BOT_ADMIN_TOKEN[_FILE]) — the service-accounts API is admin-only.
# Create an instance service account + a PAT for it. The operator token (resolved
# above from RENOVATE_BOT_ADMIN_TOKEN[_FILE] or the ambient token) must be an
# INSTANCE ADMIN here — the service-accounts API is admin-only.
cmd_sa_create() {
    local atok ahdr resp sid suser tok
    atok="${RENOVATE_BOT_ADMIN_TOKEN:-}"
    [ -z "${atok}" ] && [ -n "${RENOVATE_BOT_ADMIN_TOKEN_FILE:-}" ] && [ -f "${RENOVATE_BOT_ADMIN_TOKEN_FILE}" ] \
        && atok="$(cat "${RENOVATE_BOT_ADMIN_TOKEN_FILE}")"
    [ -n "${atok}" ] || err "sa-create needs an INSTANCE-ADMIN token: set RENOVATE_BOT_ADMIN_TOKEN or RENOVATE_BOT_ADMIN_TOKEN_FILE (api scope)."
    ahdr=(--header "PRIVATE-TOKEN: ${atok}")
    resp=$(curl -s "${ahdr[@]}" -X POST "${GITLAB_API}/service_accounts" \
    local resp sid suser tok
    resp=$(curl -s "${hdr[@]}" -X POST "${GITLAB_API}/service_accounts" \
        --data-urlencode "name=${RENOVATE_BOT_SA_NAME:-Renovate Bot}" \
        --data "username=${RENOVATE_BOT_USER:-${NAME}}")
    sid=$(echo "${resp}" | jq -r '.id // empty')
    suser=$(echo "${resp}" | jq -r '.username // empty')
    [ -n "${sid}" ] || err "service-account create failed (needs instance admin): $(echo "${resp}" | jq -r '.message // .error // .')"
    resp=$(curl -s "${ahdr[@]}" -X POST "${GITLAB_API}/service_accounts/${sid}/personal_access_tokens" \
    [ -n "${sid}" ] || err "service-account create failed (needs an INSTANCE-ADMIN token via RENOVATE_BOT_ADMIN_TOKEN[_FILE]): $(echo "${resp}" | jq -r '.message // .error // .')"
    resp=$(curl -s "${hdr[@]}" -X POST "${GITLAB_API}/service_accounts/${sid}/personal_access_tokens" \
        --data "name=${NAME}" --data "scopes[]=api" --data "scopes[]=write_repository" --data "expires_at=${EXPIRES}")
    tok=$(echo "${resp}" | jq -r '.token // empty')
    [ -n "${tok}" ] || err "SA token create failed: $(echo "${resp}" | jq -r '.message // .error // .')"