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).
192 lines
7.6 KiB
Markdown
192 lines
7.6 KiB
Markdown
---
|
||
description: Push local recipe commits to git.autonomic.zone and open a PR against an upstream-synced main branch
|
||
argument-hint: <recipe-name>
|
||
allowed-tools: [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:
|
||
|
||
```bash
|
||
#!/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.
|