diff --git a/.circleci/config.yml b/.circleci/config.yml index 4379248b4..e4a23a266 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -400,10 +400,12 @@ jobs: name: Skip image generation command: | set -x - - # Push to Docker Hub for tagged releases and main=:dev. - if [ "$IS_RELEASE" != "true" ] && [ "$TAG_SUFFIX" != "dev" ]; then - echo "Skipping image publishing for TAG_SUFFIX=$TAG_SUFFIX (only tagged releases and main=:dev are published)." + + # Push to Docker Hub for tagged releases (datasqrl/*) and main=:dev (datasqrl/*). + # Push PR images to ghcr.io/datasqrl/* so downstream CI (e.g. datasqrl-examples) can + # validate examples against the snapshot built from this PR. + if [ "$IS_RELEASE" != "true" ] && [ "$TAG_SUFFIX" != "dev" ] && [[ "$TAG_SUFFIX" != pr-* ]]; then + echo "Skipping image publishing for TAG_SUFFIX=$TAG_SUFFIX (only tagged releases, main=:dev, and pr-* are published)." circleci step halt fi - restore_cache: @@ -650,4 +652,3 @@ workflows: ignore: /.*/ requires: - download-maven-dependencies - diff --git a/.github/workflows/bump-examples-version.yml b/.github/workflows/bump-examples-version.yml new file mode 100644 index 000000000..270512ed7 --- /dev/null +++ b/.github/workflows/bump-examples-version.yml @@ -0,0 +1,84 @@ +name: Bump SQRL version in datasqrl-examples + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: "SQRL version to set in datasqrl-examples (without leading v)" + required: true + +jobs: + open-bump-pr: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Resolve version + id: version + run: | + if [ -n "${{ inputs.version }}" ]; then + RAW="${{ inputs.version }}" + else + RAW="${{ github.event.release.tag_name }}" + fi + VERSION="${RAW#v}" + if [ -z "$VERSION" ]; then + echo "Empty version, aborting." >&2 + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Checkout datasqrl-examples + uses: actions/checkout@v4 + with: + repository: DataSQRL/datasqrl-examples + token: ${{ secrets.EXAMPLES_BOT_PAT }} + path: datasqrl-examples + + - name: Bump SQRL_VERSION and open PR + working-directory: datasqrl-examples + env: + GH_TOKEN: ${{ secrets.EXAMPLES_BOT_PAT }} + VERSION: ${{ steps.version.outputs.version }} + run: | + set -euo pipefail + + BRANCH="bump-sqrl-${VERSION}" + + git config user.name "datasqrl-bot" + git config user.email "bot@datasqrl.com" + + # If a PR for this version already exists, do nothing. + if gh pr list --state open --head "$BRANCH" --json number --jq 'length' | grep -q '^[1-9]'; then + echo "PR for branch $BRANCH already open; nothing to do." + exit 0 + fi + + # Recreate the branch fresh from default branch. + DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name') + git fetch origin "$DEFAULT_BRANCH" + git checkout -B "$BRANCH" "origin/${DEFAULT_BRANCH}" + + # Idempotent sed against the CI workflow env block. + sed -i -E "s/^( SQRL_VERSION: ).*/\1'${VERSION}'/" .github/workflows/build.yml + + if git diff --quiet .github/workflows/build.yml; then + echo "SQRL_VERSION already set to ${VERSION}; nothing to do." + exit 0 + fi + + git add .github/workflows/build.yml + git commit -s -m "Bump SQRL version to ${VERSION}" + git push --force-with-lease -u origin "$BRANCH" + + gh pr create \ + --title "Bump SQRL version to ${VERSION}" \ + --body "## Summary + - Bump \`SQRL_VERSION\` in CI workflow from previous value to \`${VERSION}\` + + Triggered automatically by the release of [\`${VERSION}\`](https://github.com/DataSQRL/sqrl/releases/tag/${VERSION}) in DataSQRL/sqrl. + + ## Test plan + - [ ] CI build matrix passes against \`datasqrl/cmd:${VERSION}\`" \ + --head "$BRANCH" diff --git a/.github/workflows/validate-examples.yml b/.github/workflows/validate-examples.yml new file mode 100644 index 000000000..7428ad7f0 --- /dev/null +++ b/.github/workflows/validate-examples.yml @@ -0,0 +1,94 @@ +name: Validate datasqrl-examples + +on: + workflow_dispatch: + inputs: + sqrl_image: + description: "Full SQRL CLI image reference to validate, for example ghcr.io/datasqrl/cmd:pr-123 or datasqrl/cmd:dev" + required: false + default: datasqrl/cmd:dev + examples_ref: + description: "The datasqrl-examples ref to run" + required: false + default: main + correlation_id: + description: "Optional correlation ID for the dispatched datasqrl-examples run" + required: false + default: "" + +jobs: + validate-examples: + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + steps: + - name: Dispatch datasqrl-examples build and wait + env: + GH_TOKEN: ${{ secrets.EXAMPLES_BOT_PAT }} + SQRL_IMAGE: ${{ inputs.sqrl_image }} + EXAMPLES_REF: ${{ inputs.examples_ref }} + INPUT_CORRELATION_ID: ${{ inputs.correlation_id }} + run: | + set -euo pipefail + + if [ -z "$GH_TOKEN" ]; then + echo "EXAMPLES_BOT_PAT is required to dispatch datasqrl-examples" >&2 + exit 1 + fi + + if [ -z "$SQRL_IMAGE" ]; then + echo "sqrl_image is required" >&2 + exit 1 + fi + + if [ -z "$EXAMPLES_REF" ]; then + echo "examples_ref is required" >&2 + exit 1 + fi + + if [ -n "$INPUT_CORRELATION_ID" ]; then + CORRELATION_ID="$INPUT_CORRELATION_ID" + else + CORRELATION_ID="sqrl-gha-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + fi + + if [[ ! "$CORRELATION_ID" =~ ^[A-Za-z0-9._:-]+$ ]]; then + echo "correlation_id may only contain letters, numbers, '.', '_', ':', and '-'" >&2 + exit 1 + fi + + EXPECTED_NAME="examples build [${CORRELATION_ID}]" + + echo "Dispatching datasqrl-examples build:" + echo " sqrl_image=$SQRL_IMAGE" + echo " examples_ref=$EXAMPLES_REF" + echo " correlation_id=$CORRELATION_ID" + + gh workflow run build.yml \ + --repo DataSQRL/datasqrl-examples \ + --ref "$EXAMPLES_REF" \ + --field sqrl_image="$SQRL_IMAGE" \ + --field correlation_id="$CORRELATION_ID" + + # workflow_dispatch is async and returns no run id, so locate by the + # run-name datasqrl-examples sets: "examples build []". + RUN_ID="" + for attempt in $(seq 1 30); do + RUN_ID=$(gh api \ + "/repos/DataSQRL/datasqrl-examples/actions/workflows/build.yml/runs?event=workflow_dispatch&per_page=100" \ + --jq "[.workflow_runs[] | select(.display_title == \"${EXPECTED_NAME}\") | .id][0] // \"\"") + + [ -n "$RUN_ID" ] && break + + echo "Run not yet visible (attempt $attempt/30); sleeping 10s..." + sleep 10 + done + + if [ -z "$RUN_ID" ]; then + echo "Could not locate dispatched run for '${EXPECTED_NAME}'" >&2 + exit 1 + fi + + echo "Watching https://github.com/DataSQRL/datasqrl-examples/actions/runs/${RUN_ID}" + gh run watch "$RUN_ID" --repo DataSQRL/datasqrl-examples --exit-status