Files
recipe-maintainer/.claude/commands/recipe-create-pr.md
T
autonomic-bot f283a371bb recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer.
- Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders.
- Removed plans/ and planned-updates/ (deployment-planning docs) so no client/
  deployment domains appear in the public repo.
- All other secret stores were already gitignored.
- docs.coopcloud.tech retained as a submodule (public upstream).
2026-06-16 20:18:24 +00:00

7.6 KiB
Raw Blame History

description, argument-hint, allowed-tools
description argument-hint allowed-tools
Push local recipe commits to git.autonomic.zone and open a PR against an upstream-synced main branch <recipe-name>
Bash
Read

Recipe Create PR

Take the local commits made to a recipe (e.g. during /recipe-upgrade-apply) and open a pull request on the Gitea instance at git.autonomic.zone. The Gitea repo's main branch is force-synced from the recipe's upstream main so the PR diff shows only the local changes.

The recipe name is: $ARGUMENTS

Prerequisites

Credentials are read from test-ssh/.testenv. The following variables must be set:

  • GITEA_USERNAME — bot account on the Gitea instance
  • GITEA_PASSWORD — bot password
  • GITEA_URL — Gitea host (e.g. git.autonomic.zone)

Optional:

  • GITEA_NAMESPACE — owner under which to create/find repos. Defaults to recipe-maintainers (the org whose recipe-maintainers team auto-grants access to all repos within it).

The recipe must be checked out at ~/.abra/recipes/$ARGUMENTS with at least one commit beyond origin/main.

Steps

Run the following script with the recipe name substituted in:

#!/usr/bin/env bash
set -euo pipefail

RECIPE="$ARGUMENTS"
WORKSPACE="/workspace"
RECIPE_DIR="${HOME}/.abra/recipes/${RECIPE}"
TESTENV="${WORKSPACE}/test-ssh/.testenv"

# --- Load credentials ---
[ -f "${TESTENV}" ] || { echo "ERROR: ${TESTENV} not found"; exit 1; }
set -a; . "${TESTENV}"; set +a
: "${GITEA_USERNAME:?missing in .testenv}"
: "${GITEA_PASSWORD:?missing in .testenv}"
: "${GITEA_URL:?missing in .testenv}"
NAMESPACE="${GITEA_NAMESPACE:-recipe-maintainers}"

PASS_ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "${GITEA_PASSWORD}")
API="https://${GITEA_URL}/api/v1"
AUTH=(-u "${GITEA_USERNAME}:${GITEA_PASSWORD}")

# --- Validate recipe checkout ---
[ -d "${RECIPE_DIR}/.git" ] || { echo "ERROR: ${RECIPE_DIR} is not a git repo. Run 'abra recipe fetch ${RECIPE}' first."; exit 1; }
cd "${RECIPE_DIR}"

# --- Fetch upstream main ---
echo "→ Fetching upstream main from origin..."
git fetch origin main

# --- Find diverged commits ---
DIVERGED=$(git log --oneline origin/main..HEAD 2>/dev/null || true)
if [ -z "${DIVERGED}" ]; then
  echo "ERROR: HEAD has no commits beyond origin/main. Nothing to PR."
  exit 1
fi
echo "→ Local commits to PR:"
echo "${DIVERGED}" | sed 's/^/    /'

# --- Determine PR branch name from the most recent commit ---
LATEST_MSG=$(git log -1 --pretty=%s HEAD)
if echo "${LATEST_MSG}" | grep -qiE "upgrade to [0-9]"; then
  VERSION=$(echo "${LATEST_MSG}" | grep -oiE "upgrade to [0-9][^[:space:]]+" | awk '{print $NF}')
  BRANCH="upgrade-${VERSION}"
else
  BRANCH="pr-$(date -u +%Y%m%d-%H%M%S)"
fi
echo "→ PR branch: ${BRANCH}"

# --- Check / create Gitea repo ---
REPO_URL_API="${API}/repos/${NAMESPACE}/${RECIPE}"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH[@]}" "${REPO_URL_API}")

if [ "${STATUS}" = "404" ]; then
  echo "→ Repo ${NAMESPACE}/${RECIPE} does not exist; creating..."
  CREATE_BODY=$(python3 -c "import json;print(json.dumps({'name':'${RECIPE}','private':True,'default_branch':'main','auto_init':False}))")

  # Try org namespace first
  CREATE_OUT=$(mktemp)
  CREATE_STATUS=$(curl -s -o "${CREATE_OUT}" -w "%{http_code}" "${AUTH[@]}" \
    -H "Content-Type: application/json" \
    -X POST "${API}/orgs/${NAMESPACE}/repos" \
    -d "${CREATE_BODY}")

  if [ "${CREATE_STATUS}" != "201" ]; then
    echo "  ! create under org ${NAMESPACE} returned HTTP ${CREATE_STATUS} — falling back to user namespace ${GITEA_USERNAME}"
    cat "${CREATE_OUT}" >&2
    echo "" >&2
    CREATE_STATUS=$(curl -s -o "${CREATE_OUT}" -w "%{http_code}" "${AUTH[@]}" \
      -H "Content-Type: application/json" \
      -X POST "${API}/user/repos" \
      -d "${CREATE_BODY}")
    if [ "${CREATE_STATUS}" != "201" ]; then
      echo "ERROR: failed to create repo (HTTP ${CREATE_STATUS}):"
      cat "${CREATE_OUT}"
      rm -f "${CREATE_OUT}"
      exit 1
    fi
    NAMESPACE="${GITEA_USERNAME}"
  fi
  rm -f "${CREATE_OUT}"
  echo "  ✓ created ${NAMESPACE}/${RECIPE}"
elif [ "${STATUS}" = "200" ]; then
  echo "→ Repo ${NAMESPACE}/${RECIPE} already exists"
else
  echo "ERROR: unexpected HTTP ${STATUS} when checking ${REPO_URL_API}"
  exit 1
fi

# --- Set up the gitea remote with credentials embedded ---
REMOTE_URL="https://${GITEA_USERNAME}:${PASS_ENC}@${GITEA_URL}/${NAMESPACE}/${RECIPE}.git"
if git remote | grep -qx gitea; then
  git remote set-url gitea "${REMOTE_URL}"
else
  git remote add gitea "${REMOTE_URL}"
fi

# --- Force-sync Gitea main with origin/main so the PR diff is clean ---
echo "→ Force-syncing gitea/main from origin/main..."
git push --force gitea "refs/remotes/origin/main:refs/heads/main"

# --- Push local commits as the PR branch ---
echo "→ Pushing local commits as branch '${BRANCH}'..."
git push --force gitea "HEAD:refs/heads/${BRANCH}"

# --- Create the PR ---
# NOTE: for an upgrade PR the body (passed in via RECIPE_PR_BODY) MUST link the upstream release notes
# — one explicit line per upgraded image/service, e.g.
#   **Upstream release notes:** <service> <old>→<new>: <url>
# pulling each URL from recipe-info/<recipe>/upstream.md (between the current → new version). These links
# belong in the PR body itself, NOT only in a side report, so the reviewer sees what changed upstream.
PR_TITLE="${LATEST_MSG}"
if [ -n "${RECIPE_PR_BODY:-}" ]; then
  PR_BODY="${RECIPE_PR_BODY}"
else
  PR_BODY=$(printf "Local commits on top of upstream main:\n\n%s\n" "$(git log origin/main..HEAD --pretty='- %h %s')")
fi
PR_BODY="${PR_BODY}

cc @trav @notplants"
PR_PAYLOAD=$(python3 -c "
import json, sys
print(json.dumps({
    'title': sys.argv[1],
    'body': sys.argv[2],
    'head': sys.argv[3],
    'base': 'main',
    'reviewers': ['trav', 'notplants'],
}))" "${PR_TITLE}" "${PR_BODY}" "${BRANCH}")

PR_RESPONSE=$(mktemp)
PR_STATUS=$(curl -s -o "${PR_RESPONSE}" -w "%{http_code}" "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -X POST "${API}/repos/${NAMESPACE}/${RECIPE}/pulls" \
  -d "${PR_PAYLOAD}")

if [ "${PR_STATUS}" = "201" ]; then
  PR_URL=$(python3 -c "import json;print(json.load(open('${PR_RESPONSE}'))['html_url'])")
  echo ""
  echo "✓ PR created: ${PR_URL}"
elif [ "${PR_STATUS}" = "409" ] || grep -q "pull request already exists" "${PR_RESPONSE}" 2>/dev/null; then
  echo ""
  echo " A PR for branch '${BRANCH}' already exists. See:"
  echo "  https://${GITEA_URL}/${NAMESPACE}/${RECIPE}/pulls"
else
  echo "ERROR: PR creation failed (HTTP ${PR_STATUS}):"
  cat "${PR_RESPONSE}"
  rm -f "${PR_RESPONSE}"
  exit 1
fi

rm -f "${PR_RESPONSE}"

After the script runs, report back the PR URL (or the existing-PR list URL if Gitea returned 409).

Notes

  • The gitea remote is created/updated in ~/.abra/recipes/<recipe>/.git/config with the password embedded — this is acceptable here since .testenv already stores the password in plaintext, but be aware the remote URL is now stored in that local file.
  • Re-running the skill is safe: the script force-pushes both the synced main and the PR branch, and reports gracefully if a PR for that branch already exists.
  • The PR branch name is derived from the most recent commit message — if it matches upgrade to <version>, the branch becomes upgrade-<version>; otherwise a timestamped name is used.
  • For upgrade PRs, RECIPE_PR_BODY must carry the upstream release-notes links (one line per upgraded service: **Upstream release notes:** <service> <old>→<new>: <url>, sourced from recipe-info/<recipe>/upstream.md). /recipe-upgrade-apply composes the body with these links already; if you invoke this command directly for an upgrade, include them in RECIPE_PR_BODY yourself so the reviewer sees what changed upstream.