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).
7.6 KiB
7.6 KiB
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> |
|
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 instanceGITEA_PASSWORD— bot passwordGITEA_URL— Gitea host (e.g.git.autonomic.zone)
Optional:
GITEA_NAMESPACE— owner under which to create/find repos. Defaults torecipe-maintainers(the org whoserecipe-maintainersteam 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
gitearemote is created/updated in~/.abra/recipes/<recipe>/.git/configwith the password embedded — this is acceptable here since.testenvalready 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
mainand 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 becomesupgrade-<version>; otherwise a timestamped name is used. - For upgrade PRs,
RECIPE_PR_BODYmust carry the upstream release-notes links (one line per upgraded service:**Upstream release notes:** <service> <old>→<new>: <url>, sourced fromrecipe-info/<recipe>/upstream.md)./recipe-upgrade-applycomposes the body with these links already; if you invoke this command directly for an upgrade, include them inRECIPE_PR_BODYyourself so the reviewer sees what changed upstream.