Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/actions/find-artifacts-run/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Find Run with Artifacts
description: >
Searches prior workflow runs for the given commit and returns the ID of the
first run that contains all specified non-expired artifacts.
When run-id is provided, only that specific run is checked instead of
searching all prior runs.

inputs:
required-artifacts:
description: Newline-separated list of artifact names that must all be present.
required: true
token:
description: GitHub token with actions:read permission.
required: true
workflow-file:
description: >
Workflow filename to search when no run-id is provided (e.g. ci.yml).
Ignored when run-id is set.
required: false
default: ci.yml
run-id:
description: >
If set, check only this specific run rather than searching all prior runs
for the commit.
required: false
default: ""

outputs:
run-id:
description: >
Run ID of the eligible run that contains all required artifacts, or empty
string if none was found.
value: ${{ steps.find.outputs.run_id }}

runs:
using: composite
steps:
- name: Find run with all artifacts
id: find
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
REQUIRED_ARTIFACTS: ${{ inputs.required-artifacts }}
WORKFLOW_FILE: ${{ inputs.workflow-file }}
CHECK_RUN_ID: ${{ inputs.run-id }}
CURRENT_RUN_ID: ${{ github.run_id }}
REPOSITORY: ${{ github.repository }}
SHA: ${{ github.sha }}
run: |
mapfile -t required < <(
printf '%s\n' "$REQUIRED_ARTIFACTS" \
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
| grep -v '^$'
)

# Build the list of run IDs to check.
if [ -n "$CHECK_RUN_ID" ]; then
run_ids="$CHECK_RUN_ID"
else
run_ids=$(curl -fsSL \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPOSITORY}/actions/workflows/${WORKFLOW_FILE}/runs?head_sha=${SHA}&per_page=20" \
| jq -r --argjson cur "$CURRENT_RUN_ID" \
'.workflow_runs[] | select(.id != $cur) | .id') || true
fi

found_run_id=""
for run_id in $run_ids; do
names=$(curl -fsSL \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=200" \
| jq -r '.artifacts[] | select(.expired == false) | .name') || continue

all_found=true
for artifact in "${required[@]}"; do
if ! printf '%s\n' "$names" | grep -qx "$artifact"; then
all_found=false
break
fi
done

if $all_found; then
echo "Found all required artifacts in run $run_id"
found_run_id="$run_id"
break
fi
done

if [ -z "$found_run_id" ]; then
echo "No run found with all required artifacts"
fi
echo "run_id=${found_run_id}" >> "$GITHUB_OUTPUT"
25 changes: 25 additions & 0 deletions .github/workflows/build-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ name: Build and Publish Docker Image

on:
workflow_call:
inputs:
artifacts_run_id:
description: "Run ID to download build artifacts from (empty = current run)"
type: string
required: false
default: ""

permissions:
contents: read
packages: write
actions: read

env:
REGISTRY: ghcr.io
Expand Down Expand Up @@ -55,18 +62,24 @@ jobs:
with:
name: fda-${{ matrix.rust_target }}
path: build
run-id: ${{ inputs.artifacts_run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Download pipeline-manager
uses: actions/download-artifact@v4
with:
name: pipeline-manager-${{ matrix.rust_target }}
path: build
run-id: ${{ inputs.artifacts_run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Download Compiler Binaries
uses: actions/download-artifact@v4
with:
name: feldera-sql-compiler
path: build
run-id: ${{ inputs.artifacts_run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

# Remove if https://github.com/actions/upload-artifact/issues/38 ever gets fixed
- name: Make binaries executable
Expand Down Expand Up @@ -188,3 +201,15 @@ jobs:
"${{ vars.FELDERA_IMAGE_NAME }}" \
"${{ steps.meta.outputs.version }}" \
"amd64,arm64"

# Upload after the image is verified so check-prior-build knows the
# sha-{sha} tag actually exists in GHCR (not just that digests were pushed).
- name: Signal docker image ready
run: echo "${{ steps.meta.outputs.version }}" > /tmp/docker-image-ready.txt

- name: Upload docker-image-ready artifact
uses: actions/upload-artifact@v4
with:
name: docker-image-ready
path: /tmp/docker-image-ready.txt
retention-days: 7
115 changes: 108 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,78 @@ on:
jobs:
# NOTE: For every job added here, add a matching cancel-if-<name>-failed
# sentinel job in the cancel-if-* section below.

# Checks whether all build artifacts already exist in a prior run for this
# commit. If so, downstream build jobs are skipped and tests download
# artifacts from that prior run instead of the current one.
# NOTE: No cancel sentinel is needed for this job — it is designed to always
# succeed. Individual steps use continue-on-error and the final 'result'
# step runs unconditionally, so the job never leaves the workflow in a
# failed state.
check-prior-build:
name: Check for Prior Build Artifacts
runs-on: ubuntu-latest-amd64
permissions:
actions: read
# Outputs are set by the final 'result' step so this job always succeeds
# and never blocks downstream jobs even if individual checks fail.
outputs:
artifacts_run_id: ${{ steps.result.outputs.artifacts_run_id }}
skip_docker: ${{ steps.result.outputs.skip_docker }}
steps:
- uses: actions/checkout@v4

# Finds the first prior run for this commit that has all Rust/Java build
# artifacts. If found, its run ID is used by downstream jobs to download
# artifacts instead of rebuilding.
- name: Find prior run with all Rust/Java build artifacts
id: find-builds
continue-on-error: true
uses: ./.github/actions/find-artifacts-run
with:
required-artifacts: |
fda-x86_64-unknown-linux-gnu
fda-aarch64-unknown-linux-gnu
pipeline-manager-x86_64-unknown-linux-gnu
pipeline-manager-aarch64-unknown-linux-gnu
feldera-test-binaries-x86_64-unknown-linux-gnu
feldera-test-binaries-aarch64-unknown-linux-gnu
feldera-sql-compiler
token: ${{ github.token }}

# Checks whether the run found above also has the Docker image artifact.
# This artifact is uploaded by merge-manifests only after the image is
# verified in GHCR, so its presence guarantees the sha-{sha} tag exists.
- name: Check if Docker image artifact also present
id: find-docker
if: steps.find-builds.outputs.run-id != ''
continue-on-error: true
uses: ./.github/actions/find-artifacts-run
with:
required-artifacts: docker-image-ready
token: ${{ github.token }}
run-id: ${{ steps.find-builds.outputs.run-id }}

# Consolidates outputs so both are always set even if a find step failed.
# Defaults: artifacts_run_id='' (run builds), skip_docker=false (run Docker build).
- name: Consolidate outputs
id: result
if: always()
run: |
echo "artifacts_run_id=${{ steps.find-builds.outputs.run-id }}" >> "$GITHUB_OUTPUT"
echo "skip_docker=${{ steps.find-docker.outputs.run-id != '' && 'true' || 'false' }}" >> "$GITHUB_OUTPUT"

invoke-build-rust:
name: Build Rust
needs: [check-prior-build]
if: needs.check-prior-build.outputs.artifacts_run_id == ''
uses: ./.github/workflows/build-rust.yml
secrets: inherit

invoke-build-java:
name: Build Java
needs: [check-prior-build]
if: needs.check-prior-build.outputs.artifacts_run_id == ''
uses: ./.github/workflows/build-java.yml
secrets: inherit

Expand All @@ -24,49 +89,84 @@ jobs:

invoke-tests-unit:
name: Unit Tests
needs: [invoke-build-rust, invoke-build-java]
needs: [check-prior-build, invoke-build-rust, invoke-build-java]
if: |
always() &&
(needs.invoke-build-rust.result == 'success' || needs.invoke-build-rust.result == 'skipped') &&
(needs.invoke-build-java.result == 'success' || needs.invoke-build-java.result == 'skipped')
uses: ./.github/workflows/test-unit.yml
with:
artifacts_run_id: ${{ needs.check-prior-build.outputs.artifacts_run_id }}
secrets: inherit

invoke-tests-adapter:
name: Adapter Tests
needs: [invoke-build-rust]
needs: [check-prior-build, invoke-build-rust]
if: |
always() &&
(needs.invoke-build-rust.result == 'success' || needs.invoke-build-rust.result == 'skipped')
uses: ./.github/workflows/test-adapters.yml
with:
artifacts_run_id: ${{ needs.check-prior-build.outputs.artifacts_run_id }}
secrets: inherit

invoke-build-docker:
name: Build Docker
needs: [invoke-build-rust, invoke-build-java]
needs: [check-prior-build, invoke-build-rust, invoke-build-java]
if: |
always() &&
needs.check-prior-build.outputs.skip_docker != 'true' &&
(needs.invoke-build-rust.result == 'success' || needs.invoke-build-rust.result == 'skipped') &&
(needs.invoke-build-java.result == 'success' || needs.invoke-build-java.result == 'skipped')
uses: ./.github/workflows/build-docker.yml
with:
artifacts_run_id: ${{ needs.check-prior-build.outputs.artifacts_run_id }}
secrets: inherit

invoke-generate-sbom:
name: Generate SBOMs
needs: [invoke-build-docker]
if: |
always() &&
(needs.invoke-build-docker.result == 'success' || needs.invoke-build-docker.result == 'skipped')
uses: ./.github/workflows/generate-sbom.yml
secrets: inherit

invoke-tests-integration-platform:
name: Integration Tests
needs: [invoke-build-docker]
needs: [check-prior-build, invoke-build-docker]
if: |
always() &&
(needs.invoke-build-docker.result == 'success' || needs.invoke-build-docker.result == 'skipped')
uses: ./.github/workflows/test-integration-platform.yml
with:
artifacts_run_id: ${{ needs.check-prior-build.outputs.artifacts_run_id }}
secrets: inherit

invoke-tests-integration-runtime:
name: Integration Tests
needs: [invoke-build-java]
needs: [check-prior-build, invoke-build-java]
if: |
always() &&
(needs.invoke-build-java.result == 'success' || needs.invoke-build-java.result == 'skipped')
uses: ./.github/workflows/test-integration-runtime.yml
secrets: inherit

invoke-tests-java:
name: Java Tests
needs: [invoke-build-java]
needs: [check-prior-build, invoke-build-java]
if: |
always() &&
(needs.invoke-build-java.result == 'success' || needs.invoke-build-java.result == 'skipped')
uses: ./.github/workflows/test-java.yml
secrets: inherit

invoke-publish-crates-dry-run:
name: Publish Crates (Dry Run)
needs: [invoke-build-rust]
needs: [check-prior-build, invoke-build-rust]
if: |
always() &&
(needs.invoke-build-rust.result == 'success' || needs.invoke-build-rust.result == 'skipped')
uses: ./.github/workflows/publish-crates.yml
with:
environment: ci
Expand Down Expand Up @@ -234,6 +334,7 @@ jobs:
if: always()
runs-on: ubuntu-latest-amd64
needs:
- check-prior-build
- invoke-build-rust
- invoke-build-java
- invoke-build-docs
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/test-adapters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ name: Adapter Tests

on:
workflow_call:
inputs:
artifacts_run_id:
description: "Run ID to download build artifacts from (empty = current run)"
type: string
required: false
default: ""
workflow_dispatch:
inputs:
run_id:
description: "ID of the workflow run that uploaded the artifact"
required: true

permissions:
actions: read
contents: read

jobs:
adapter-tests:
if: ${{ !contains(vars.CI_SKIP_JOBS, 'adapter-tests') }}
Expand Down Expand Up @@ -60,7 +70,7 @@ jobs:
with:
name: feldera-test-binaries-${{ matrix.target }}
path: build
run-id: ${{ github.event.inputs.run_id || github.run_id }}
run-id: ${{ inputs.artifacts_run_id || github.event.inputs.run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

# Remove if https://github.com/actions/upload-artifact/issues/38 ever gets fixed
Expand Down Expand Up @@ -139,7 +149,7 @@ jobs:
with:
name: feldera-test-binaries-x86_64-unknown-linux-gnu
path: build
run-id: ${{ github.event.inputs.run_id || github.run_id }}
run-id: ${{ inputs.artifacts_run_id || github.event.inputs.run_id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

# Remove if https://github.com/actions/upload-artifact/issues/38 ever gets fixed
Expand Down
Loading
Loading