diff --git a/.woodpecker/main-fastlane.yml b/.woodpecker/main-fastlane.yml index ce989e2..10f888f 100644 --- a/.woodpecker/main-fastlane.yml +++ b/.woodpecker/main-fastlane.yml @@ -8,20 +8,19 @@ clone: settings: lfs: false +# Manual trigger on main. Optional parameters: FASTLANE_COMPONENTS or FASTLANE_BASE_SHA. +# With neither set, all three amd64 images are rebuilt to cover multiple pushes. when: - - event: push - branch: main - - event: pull_request_closed + - event: manual branch: main concurrency: 1 steps: - - name: publish-affected-amd64-images + - name: publish-manual-amd64-images image: bash environment: NUGET_PACKAGES: /home/justme/.nuget/packages - GITEA_BASE: http://127.0.0.1:3100 ZOT_REGISTRY: registry.oeo.one ZOT_USERNAME: justme ZOT_SECRET: diff --git a/ci/main-fastlane.py b/ci/main-fastlane.py index f3347f9..92fe987 100644 --- a/ci/main-fastlane.py +++ b/ci/main-fastlane.py @@ -35,7 +35,7 @@ IGNORED_PARTS = {".git", ".release", "artifacts", "bin", "obj", "TestResults", " def read_changed_files() -> list[str]: raw = os.environ.get("CI_PIPELINE_FILES") - if os.environ.get("FASTLANE_FILES_FROM_PR_API") == "true": + if os.environ.get("FASTLANE_FILES_ARE_JSON") == "true" or os.environ.get("FASTLANE_FILES_FROM_PR_API") == "true": value = json.loads(raw or "[]") return [str(path).replace("\\", "/") for path in value] if raw: diff --git a/ci/release-main-images.sh b/ci/release-main-images.sh index 6188ab9..60b6b59 100644 --- a/ci/release-main-images.sh +++ b/ci/release-main-images.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Build and push only the linux/amd64 OCI images affected by a main push. +# Manually build and push selected linux/amd64 OCI images from main. set -euo pipefail root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" @@ -11,31 +11,44 @@ export HOME="$buildah_home" export BUILDAH_HOME="$buildah_home" export REGISTRY_AUTH_FILE="$work/zot-auth.json" -if [[ "${CI_PIPELINE_EVENT:-}" == pull_request_closed ]]; then - [[ "${CI_COMMIT_TARGET_BRANCH:-}" == main && -n "${CI_COMMIT_PULL_REQUEST:-}" ]] || { - echo "ignoring closed pull request outside main" >&2 - exit 0 - } - gitea_base="${GITEA_BASE:-http://127.0.0.1:3100}" - api="$gitea_base/api/v1/repos/justme/MyMonitor" - pr_json="$(curl --fail-with-body --silent --show-error "$api/pulls/$CI_COMMIT_PULL_REQUEST")" - read -r merged base_branch <<< "$(python3 -c 'import json,sys; p=json.load(sys.stdin); print(str(p.get("merged", False)).lower(), p.get("base", {}).get("ref", ""))' <<< "$pr_json")" - if [[ "$merged" != true || "$base_branch" != main ]]; then - echo "pull request $CI_COMMIT_PULL_REQUEST was closed without merging to main; skipping images" - exit 0 - fi - - export FASTLANE_MERGED_PR=true - export FASTLANE_FILES_FROM_PR_API=true - export CI_PIPELINE_FILES="$(curl --fail-with-body --silent --show-error \ - "$api/pulls/$CI_COMMIT_PULL_REQUEST/files?limit=500" \ - | python3 -c 'import json,sys; print(json.dumps([name for item in json.load(sys.stdin) for name in (item["filename"], item.get("previous_filename")) if name]))')" - git fetch "$gitea_base/justme/MyMonitor.git" main - git checkout --detach FETCH_HEAD - export CI_COMMIT_BRANCH=main -elif [[ "${CI_PIPELINE_EVENT:-}" != push || "${CI_COMMIT_BRANCH:-}" != main ]]; then - echo "fast-lane images can only be published from a main push or a merged PR into main" >&2 +[[ "${CI_PIPELINE_EVENT:-}" == manual && "${CI_COMMIT_BRANCH:-}" == main ]] || { + echo "fast-lane images can only be published by a manual run on main" >&2 exit 2 +} + +manual_targets="" +if [[ -n "${FASTLANE_COMPONENTS:-}" && -n "${FASTLANE_BASE_SHA:-}" ]]; then + echo "set only one of FASTLANE_COMPONENTS or FASTLANE_BASE_SHA" >&2 + exit 2 +elif [[ -n "${FASTLANE_COMPONENTS:-}" ]]; then + manual_targets="$(python3 -c ' +import sys +allowed = {"backend", "agent", "probe"} +requested = {part.strip() for part in sys.argv[1].split(",") if part.strip()} +unknown = requested - allowed +if not requested or unknown: + raise SystemExit("invalid FASTLANE_COMPONENTS: " + ", ".join(sorted(unknown or {"empty list"}))) +print("\n".join(name for name in ("backend", "agent", "probe") if name in requested)) +' "$FASTLANE_COMPONENTS")" +elif [[ -n "${FASTLANE_BASE_SHA:-}" ]]; then + [[ "$FASTLANE_BASE_SHA" =~ ^[0-9a-fA-F]{7,40}$ ]] || { + echo "FASTLANE_BASE_SHA must be a commit SHA" >&2 + exit 2 + } + if ! git cat-file -e "${FASTLANE_BASE_SHA}^{commit}" 2>/dev/null; then + git fetch --no-tags origin "$FASTLANE_BASE_SHA" + fi + base_sha="$(git rev-parse "${FASTLANE_BASE_SHA}^{commit}")" + git merge-base --is-ancestor "$base_sha" "${CI_COMMIT_SHA:-HEAD}" || { + echo "FASTLANE_BASE_SHA must be an ancestor of the selected main commit" >&2 + exit 2 + } + export CI_PIPELINE_FILES="$(git diff --name-only "$base_sha" "${CI_COMMIT_SHA:-HEAD}" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read().splitlines()))')" + export FASTLANE_FILES_ARE_JSON=true +else + # A manual run may follow several pushes; rebuild all images unless the caller + # explicitly chooses components or supplies a base SHA for change detection. + manual_targets=$'backend\nagent\nprobe' fi [[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]] || { @@ -43,7 +56,11 @@ fi exit 2 } -targets="$(python3 "$root/ci/main-fastlane.py")" +if [[ -n "$manual_targets" ]]; then + targets="$manual_targets" +else + targets="$(python3 "$root/ci/main-fastlane.py")" +fi if [[ -z "$targets" ]]; then echo "no runtime image changes detected; skipping container builds" exit 0