--- description: From a git.autonomic.zone review-PR URL, fetch the branch + tag locally and emit the commands to open the upstream PR on git.coopcloud.tech argument-hint: allowed-tools: [Bash, Read] --- # Recipe Upstream Take a review PR created by `/recipe-create-pr` on `git.autonomic.zone` (e.g. `https://git.autonomic.zone/recipe-maintainers/lasuite-docs/pulls/3`) and prepare everything needed to open the corresponding **upstream** PR on `git.coopcloud.tech` — i.e. the "Next steps" block printed at the end of `/recipe-upgrade-apply`. This sandbox has **no SSH push access to `git.coopcloud.tech`** (the `dev` remote uses `ssh://git@git.coopcloud.tech:2222`, which requires the maintainer's own key). So this command does everything it *can* locally — fetch the PR branch over the authenticated `git.autonomic.zone` remote and make sure the `origin`/`dev` remotes exist — and then prints the exact `git push` / PR commands plus the final `abra recipe release` command for you to run from a machine that has coopcloud SSH access. The release (version-label bump + tag + publish) is the **last** step, run after the upstream PR merges — it is not done in the PR. The argument is a git.autonomic.zone pull request URL: $ARGUMENTS ## Prerequisites Credentials are read from `test-ssh/.testenv` (same as `/recipe-create-pr`): - `GITEA_USERNAME` — bot account on git.autonomic.zone - `GITEA_PASSWORD` — bot password - `GITEA_URL` — Gitea host (e.g. `git.autonomic.zone`) The recipe must already be checked out at `~/.abra/recipes/`. If it isn't, tell the user to run `abra recipe fetch ` first and stop. ## Steps Run the following script with the PR URL substituted in for `PR_URL`: ```bash #!/usr/bin/env bash set -euo pipefail # Prevent ZSH_VERSION unbound variable error when sourcing zsh-aware files under bash ZSH_VERSION=${ZSH_VERSION:-} PR_URL="$ARGUMENTS" WORKSPACE="$(cd "$(dirname "$0")/../.." 2>/dev/null || echo "${HOME}/Documents/recipe-maintainer")" # Support both /workspace (sandbox) and the actual project directory for _d in /workspace "${HOME}/Documents/recipe-maintainer"; do [ -f "${_d}/test-ssh/.testenv" ] && { WORKSPACE="${_d}"; break; } done TESTENV="${WORKSPACE}/test-ssh/.testenv" # --- Load credentials --- [ -f "${TESTENV}" ] || { echo "ERROR: ${TESTENV} not found (tried /workspace and ~/Documents/recipe-maintainer)"; exit 1; } set -a; . "${TESTENV}"; set +a : "${GITEA_USERNAME:?missing in .testenv}" : "${GITEA_PASSWORD:?missing in .testenv}" : "${GITEA_URL:?missing in .testenv}" # --- Parse the PR URL: https://///pulls/ --- read -r HOST OWNER RECIPE PR_NUM < <(python3 - "$PR_URL" <<'PY' import sys, urllib.parse u = urllib.parse.urlparse(sys.argv[1]) parts = [p for p in u.path.split('/') if p] # expect: //pulls/ if len(parts) < 4 or parts[-2] not in ('pulls', 'pull'): sys.exit("ERROR: not a recognisable Gitea PR URL: %s" % sys.argv[1]) print(u.netloc, parts[0], parts[1], parts[-1]) PY ) echo "→ Parsed PR: host=${HOST} owner=${OWNER} recipe=${RECIPE} pr=#${PR_NUM}" if [ "${HOST}" != "${GITEA_URL}" ]; then echo " ! warning: PR host (${HOST}) differs from GITEA_URL (${GITEA_URL}); using credentials anyway" fi RECIPE_DIR="${HOME}/.abra/recipes/${RECIPE}" [ -d "${RECIPE_DIR}/.git" ] || { echo "ERROR: ${RECIPE_DIR} is not a git repo. Run 'abra recipe fetch ${RECIPE}' first."; exit 1; } PASS_ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "${GITEA_PASSWORD}") API="https://${HOST}/api/v1" AUTH=(-u "${GITEA_USERNAME}:${GITEA_PASSWORD}") # --- Fetch PR metadata from the autonomic Gitea API --- echo "→ Fetching PR metadata..." PR_JSON=$(mktemp) PR_STATUS=$(curl -s -o "${PR_JSON}" -w "%{http_code}" "${AUTH[@]}" "${API}/repos/${OWNER}/${RECIPE}/pulls/${PR_NUM}") if [ "${PR_STATUS}" != "200" ]; then echo "ERROR: could not fetch PR (HTTP ${PR_STATUS}):"; cat "${PR_JSON}"; rm -f "${PR_JSON}"; exit 1 fi read -r HEAD_REF BASE_REF PR_MERGED RELEASE_FLAG < <(python3 - "${PR_JSON}" <<'PY' import json, sys, re d = json.load(open(sys.argv[1])) body = d.get("body", "") or "" # The upgrade PR records the recommended release as an `abra recipe release -` line. m = re.search(r'abra recipe release\s+\S+\s+(-[xyz])', body, re.I) print(d["head"]["ref"], d["base"]["ref"], str(d.get("merged", False)).lower(), m.group(1) if m else "-") PY ) rm -f "${PR_JSON}" echo " head branch: ${HEAD_REF}" echo " base branch: ${BASE_REF}" echo " merged: ${PR_MERGED}" cd "${RECIPE_DIR}" # --- Ensure the 'gitea' (autonomic) remote exists with credentials, then fetch the PR branch --- GITEA_REMOTE_URL="https://${GITEA_USERNAME}:${PASS_ENC}@${HOST}/${OWNER}/${RECIPE}.git" if git remote get-url gitea >/dev/null 2>&1; then git remote set-url gitea "${GITEA_REMOTE_URL}" else git remote add gitea "${GITEA_REMOTE_URL}" fi echo "→ Fetching PR #${PR_NUM} head into local branch '${HEAD_REF}'..." git fetch gitea "+refs/pull/${PR_NUM}/head:refs/heads/${HEAD_REF}" # --- Determine the upstream (coop-cloud) namespace --- # Prefer deriving it from an existing origin/dev remote; otherwise default to coop-cloud. UPSTREAM_NS="coop-cloud" for r in origin dev; do if URL=$(git remote get-url "$r" 2>/dev/null); then NS=$(echo "$URL" | sed -nE 's#.*git\.coopcloud\.tech[:/0-9]*/([^/]+)/[^/]+(\.git)?$#\1#p') [ -n "$NS" ] && { UPSTREAM_NS="$NS"; break; } fi done echo "→ Upstream namespace: ${UPSTREAM_NS}" # --- Ensure origin (https, read) and dev (ssh, push) remotes exist --- ORIGIN_URL="https://git.coopcloud.tech/${UPSTREAM_NS}/${RECIPE}.git" DEV_URL="ssh://git@git.coopcloud.tech:2222/${UPSTREAM_NS}/${RECIPE}.git" if git remote get-url origin >/dev/null 2>&1; then echo " ✓ origin: $(git remote get-url origin)" else git remote add origin "${ORIGIN_URL}"; echo " + created origin -> ${ORIGIN_URL}" fi if git remote get-url dev >/dev/null 2>&1; then echo " ✓ dev: $(git remote get-url dev)" else git remote add dev "${DEV_URL}"; echo " + created dev -> ${DEV_URL}" fi # --- Determine the release command (run AT THE END, after the upstream PR merges) --- # The upgrade PR does NOT bump the coop-cloud version label. The release is cut last, # with a real `abra recipe release` — it bumps the label, commits, tags AND publishes # (pushes the tag, which generates the catalogue entry) in one step. The PR body records # the recommended semver bump as an `abra recipe release -` line; we # surface that as the final recommendation. (No --dry-run: that would only compute and # change nothing — here we want the operator to actually publish.) if [ "${RELEASE_FLAG}" != "-" ]; then RELEASE_CMD="abra recipe release ${RECIPE} ${RELEASE_FLAG}" echo "→ Recommended release command: ${RELEASE_CMD}" else RELEASE_CMD="abra recipe release ${RECIPE} -x|-y|-z # choose: -x major / -y minor / -z patch" echo "→ Could not parse a release bump from the PR body — operator picks -x/-y/-z." fi # --- Emit the next-step commands for a machine WITH coopcloud SSH access --- COMPARE_URL="https://git.coopcloud.tech/${UPSTREAM_NS}/${RECIPE}/compare/${BASE_REF}...${HEAD_REF}" cat < ${DEV_URL} Run these from a machine with push access to git.coopcloud.tech (needs ssh-agent loaded with the coopcloud key for steps 1 and 3): cd ~/.abra/recipes/${RECIPE} git checkout ${HEAD_REF} # 1. Push the branch to the upstream repo: git push dev HEAD:${HEAD_REF} # 2. Open the upstream PR (${HEAD_REF} -> ${BASE_REF}): # ${COMPARE_URL} # 3. AFTER the upstream PR is merged, publish the release (bumps the version # label, commits, tags AND pushes the tag upstream — all in one step): ${RELEASE_CMD} ──────────────────────────────────────────────────────────────────── EOF if [ "${PR_MERGED}" = "true" ]; then echo "ℹ Note: the autonomic review PR #${PR_NUM} is already marked merged." fi ``` After the script runs, report back to the user: - The recipe and the PR branch (`HEAD_REF`) that were prepared locally. - That the branch is staged locally but **not** pushed to coopcloud (no SSH access from here). - The upstream PR compare URL. - The three commands (push branch → open PR → `abra recipe release` after merge) verbatim, so the user can run them from a machine with `git.coopcloud.tech` push access. ## Notes - **The version bump happens at the END, not in the PR.** The upgrade PR only changes image tags + config; it deliberately does **not** touch the `coop-cloud.${STACK_NAME}.version` label. The release is cut last, after the upstream PR merges, with a real `abra recipe release -x|-y|-z` (NO `--dry-run`): that single command bumps the version label, commits, creates the tag, **and** pushes the tag upstream — which is what publishes the release to the Co-op Cloud catalogue. The recommended `-x`/`-y`/`-z` is read from the PR body's `abra recipe release …` line (recorded by `/recipe-upgrade-apply` from the plan's semver reasoning); if it can't be parsed, the operator picks the bump. - Run order matters: **push the branch and open the upstream PR first; run `abra recipe release` only after that PR merges.** In Co-op Cloud the catalogue is generated from git tags, and `abra recipe release` pushes the tag, so it publishes — do it last. - The `dev` remote is created pointing at `ssh://git@git.coopcloud.tech:2222//.git`. The namespace is derived from any existing `origin`/`dev` remote, defaulting to `coop-cloud`. - Re-running is safe: the `gitea` remote URL and the PR branch are force-fetched, and the tag is only created if missing (a mismatched existing tag is reported, never overwritten). - The `gitea` remote URL embeds the bot password (same trade-off as `/recipe-create-pr`); it's written into `~/.abra/recipes//.git/config`.