This is an automated email from the ASF dual-hosted git repository. hgruszecki pushed a commit to branch refactor-post-merge-publish in repository https://gitbox.apache.org/repos/asf/iggy.git
commit 9de452c10b75d27fbd9b074fb9c6749f3ffdfaaf Author: Hubert Gruszecki <[email protected]> AuthorDate: Mon Dec 15 12:58:48 2025 +0100 feat(ci): replace Docker buildx with native runner builds No more QEMU emulation for Rust images. Changes: - Add _build_docker_images.yml workflow using native ARM/x86 runners - Add prebuilt stages to mcp, connectors, bench-dashboard Dockerfiles - Extend _build_rust_artifacts.yml with iggy-mcp and bench-dashboard - Update post-merge.yml and publish.yml to use new workflow - Deprecate docker-buildx action (kept only for web-ui) --- .github/actions/utils/docker-buildx/action.yml | 16 +- .github/workflows/_build_docker_images.yml | 409 +++++++++++++++++++++++++ .github/workflows/_build_rust_artifacts.yml | 98 +++++- .github/workflows/post-merge.yml | 80 ++--- .github/workflows/publish.yml | 127 +++++++- core/ai/mcp/Dockerfile | 21 +- core/bench/dashboard/server/Dockerfile | 62 +++- core/connectors/runtime/Dockerfile | 21 +- 8 files changed, 749 insertions(+), 85 deletions(-) diff --git a/.github/actions/utils/docker-buildx/action.yml b/.github/actions/utils/docker-buildx/action.yml index 276cb6e0b..ba34b5f39 100644 --- a/.github/actions/utils/docker-buildx/action.yml +++ b/.github/actions/utils/docker-buildx/action.yml @@ -15,8 +15,22 @@ # specific language governing permissions and limitations # under the License. +# ================================================================================== +# DEPRECATION NOTICE +# ================================================================================== +# This action uses Docker buildx with QEMU emulation for multi-arch builds. +# For Rust components (server, mcp, connectors, bench-dashboard), use the new +# _build_docker_images.yml workflow instead, which uses native runners for each +# architecture, resulting in ~4x faster builds. +# +# This action is ONLY used for: +# - web-ui (Node.js) - QEMU overhead is minimal for Node.js builds +# +# Do NOT use this action for new Rust Docker images. +# ================================================================================== + name: docker-buildx -description: Multi-arch Docker build and push +description: Multi-arch Docker build and push (DEPRECATED for Rust images - use _build_docker_images.yml instead) inputs: task: description: "Task to run (build/publish/docker)" diff --git a/.github/workflows/_build_docker_images.yml b/.github/workflows/_build_docker_images.yml new file mode 100644 index 000000000..3846a5748 --- /dev/null +++ b/.github/workflows/_build_docker_images.yml @@ -0,0 +1,409 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: _build_docker_images +on: + workflow_call: + inputs: + version: + type: string + required: false + default: "edge" + description: "Version tag for Docker images" + dry_run: + type: boolean + required: false + default: false + description: "If true, build images but don't push" + components: + type: string + required: false + default: "rust-server,rust-mcp,rust-connectors,rust-bench-dashboard" + description: "Comma-separated list of components to build" + +env: + IGGY_CI_BUILD: true + +jobs: + build-images: + name: ${{ matrix.component }} (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + arch: [amd64, arm64] + # Note: web-ui is NOT included here - it's a Node.js project that doesn't need + # pre-built Rust artifacts. It continues to use docker-buildx action directly. + component: [rust-server, rust-mcp, rust-connectors, rust-bench-dashboard] + include: + - arch: amd64 + runner: ubuntu-latest + artifact_target: x86_64-unknown-linux-gnu + - arch: arm64 + runner: ubuntu-24.04-arm + artifact_target: aarch64-unknown-linux-gnu + steps: + - name: Check if component should be built + id: check + run: | + components="${{ inputs.components }}" + component="${{ matrix.component }}" + # Use word boundary matching to avoid partial matches + if echo "$components" | grep -wq "$component"; then + echo "should_build=true" >> "$GITHUB_OUTPUT" + else + echo "should_build=false" >> "$GITHUB_OUTPUT" + echo "Skipping $component (not in component list)" + fi + + - name: Checkout code + if: steps.check.outputs.should_build == 'true' + uses: actions/checkout@v4 + + - name: Download binaries artifact + if: steps.check.outputs.should_build == 'true' && matrix.component != 'rust-bench-dashboard' + uses: actions/download-artifact@v4 + with: + name: binaries-${{ matrix.artifact_target }} + path: artifacts + + - name: Download bench-dashboard artifact + if: steps.check.outputs.should_build == 'true' && matrix.component == 'rust-bench-dashboard' + uses: actions/download-artifact@v4 + with: + name: bench-dashboard-${{ matrix.artifact_target }} + path: artifacts + + - name: Extract artifacts + if: steps.check.outputs.should_build == 'true' + run: | + cd artifacts + # Extract all tarballs (handles single or multiple files safely) + for tarball in *.tar.gz; do + [ -f "$tarball" ] || continue + echo "Extracting $tarball..." + tar xzf "$tarball" + rm -f "$tarball" + done + echo "Extracted artifacts:" + ls -la + + - name: Set up Docker + if: steps.check.outputs.should_build == 'true' + run: | + # Ensure docker is available + docker version + + - name: Login to Docker Hub + if: steps.check.outputs.should_build == 'true' && inputs.dry_run == false + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Resolve image config + if: steps.check.outputs.should_build == 'true' + id: config + run: | + component="${{ matrix.component }}" + case "$component" in + rust-server) + echo "image=apache/iggy" >> "$GITHUB_OUTPUT" + echo "dockerfile=core/server/Dockerfile" >> "$GITHUB_OUTPUT" + echo "binary=iggy-server" >> "$GITHUB_OUTPUT" + echo "prebuilt_arg=PREBUILT_IGGY_SERVER" >> "$GITHUB_OUTPUT" + echo "extra_args=--build-arg PREBUILT_IGGY_CLI=artifacts/iggy" >> "$GITHUB_OUTPUT" + ;; + rust-mcp) + echo "image=apache/iggy-mcp" >> "$GITHUB_OUTPUT" + echo "dockerfile=core/ai/mcp/Dockerfile" >> "$GITHUB_OUTPUT" + echo "binary=iggy-mcp" >> "$GITHUB_OUTPUT" + echo "prebuilt_arg=PREBUILT_IGGY_MCP" >> "$GITHUB_OUTPUT" + echo "extra_args=" >> "$GITHUB_OUTPUT" + ;; + rust-connectors) + echo "image=apache/iggy-connect" >> "$GITHUB_OUTPUT" + echo "dockerfile=core/connectors/runtime/Dockerfile" >> "$GITHUB_OUTPUT" + echo "binary=iggy-connectors" >> "$GITHUB_OUTPUT" + echo "prebuilt_arg=PREBUILT_IGGY_CONNECTORS" >> "$GITHUB_OUTPUT" + echo "extra_args=" >> "$GITHUB_OUTPUT" + ;; + rust-bench-dashboard) + echo "image=apache/iggy-bench-dashboard" >> "$GITHUB_OUTPUT" + echo "dockerfile=core/bench/dashboard/server/Dockerfile" >> "$GITHUB_OUTPUT" + echo "binary=iggy-bench-dashboard-server" >> "$GITHUB_OUTPUT" + echo "prebuilt_arg=PREBUILT_IGGY_BENCH_DASHBOARD_SERVER" >> "$GITHUB_OUTPUT" + echo "extra_args=--build-arg PREBUILT_FRONTEND_DIST=artifacts/dist" >> "$GITHUB_OUTPUT" + ;; + *) + echo "Unknown component: $component" + exit 1 + ;; + esac + + - name: Resolve version + if: steps.check.outputs.should_build == 'true' + id: version + run: | + input_version="${{ inputs.version }}" + component="${{ matrix.component }}" + + # If version is "auto", extract actual version from component + if [ "$input_version" = "auto" ]; then + chmod +x scripts/extract-version.sh 2>/dev/null || true + VERSION=$(scripts/extract-version.sh "$component" 2>/dev/null || echo "") + if [ -z "$VERSION" ]; then + echo "❌ Failed to extract version for $component" + exit 1 + fi + echo "📦 Extracted version for $component: $VERSION" + else + # Use provided version (e.g., "edge", "0.8.0") + VERSION="$input_version" + echo "📦 Using provided version: $VERSION" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Build Docker image + if: steps.check.outputs.should_build == 'true' + run: | + IMAGE="${{ steps.config.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + ARCH="${{ matrix.arch }}" + DOCKERFILE="${{ steps.config.outputs.dockerfile }}" + BINARY="${{ steps.config.outputs.binary }}" + PREBUILT_ARG="${{ steps.config.outputs.prebuilt_arg }}" + EXTRA_ARGS="${{ steps.config.outputs.extra_args }}" + + TAG="${IMAGE}:${VERSION}-${ARCH}" + + echo "Building $TAG" + echo " Dockerfile: $DOCKERFILE" + echo " Binary: artifacts/$BINARY" + echo " Prebuilt arg: $PREBUILT_ARG" + + docker build \ + --file "$DOCKERFILE" \ + --target runtime-prebuilt \ + --tag "$TAG" \ + --build-arg "${PREBUILT_ARG}=artifacts/${BINARY}" \ + $EXTRA_ARGS \ + . + + echo "Built image: $TAG" + docker images "$IMAGE" + + - name: Push Docker image + if: steps.check.outputs.should_build == 'true' && inputs.dry_run == false + run: | + IMAGE="${{ steps.config.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + ARCH="${{ matrix.arch }}" + TAG="${IMAGE}:${VERSION}-${ARCH}" + + echo "Pushing $TAG" + docker push "$TAG" + + - name: Save image digest + if: steps.check.outputs.should_build == 'true' + id: digest + run: | + IMAGE="${{ steps.config.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + ARCH="${{ matrix.arch }}" + TAG="${IMAGE}:${VERSION}-${ARCH}" + + if [ "${{ inputs.dry_run }}" = "false" ]; then + DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$TAG" 2>/dev/null || echo "") + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" + else + echo "digest=dry-run" >> "$GITHUB_OUTPUT" + fi + + create-manifests: + name: Create manifests + needs: build-images + if: inputs.dry_run == false + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + component: [rust-server, rust-mcp, rust-connectors, rust-bench-dashboard] + steps: + - name: Check if component should be built + id: check + run: | + components="${{ inputs.components }}" + component="${{ matrix.component }}" + # Use word boundary matching to avoid partial matches + if echo "$components" | grep -wq "$component"; then + echo "should_build=true" >> "$GITHUB_OUTPUT" + else + echo "should_build=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout code + if: steps.check.outputs.should_build == 'true' + uses: actions/checkout@v4 + + - name: Login to Docker Hub + if: steps.check.outputs.should_build == 'true' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Resolve image name + if: steps.check.outputs.should_build == 'true' + id: config + run: | + component="${{ matrix.component }}" + case "$component" in + rust-server) echo "image=apache/iggy" >> "$GITHUB_OUTPUT" ;; + rust-mcp) echo "image=apache/iggy-mcp" >> "$GITHUB_OUTPUT" ;; + rust-connectors) echo "image=apache/iggy-connect" >> "$GITHUB_OUTPUT" ;; + rust-bench-dashboard) echo "image=apache/iggy-bench-dashboard" >> "$GITHUB_OUTPUT" ;; + esac + + - name: Resolve version + if: steps.check.outputs.should_build == 'true' + id: version + run: | + input_version="${{ inputs.version }}" + component="${{ matrix.component }}" + + # If version is "auto", extract actual version from component + if [ "$input_version" = "auto" ]; then + chmod +x scripts/extract-version.sh 2>/dev/null || true + VERSION=$(scripts/extract-version.sh "$component" 2>/dev/null || echo "") + if [ -z "$VERSION" ]; then + echo "❌ Failed to extract version for $component" + exit 1 + fi + echo "📦 Extracted version for $component: $VERSION" + else + VERSION="$input_version" + echo "📦 Using provided version: $VERSION" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Create and push manifest + if: steps.check.outputs.should_build == 'true' + run: | + IMAGE="${{ steps.config.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + + echo "Creating manifest for $IMAGE:$VERSION" + + # Create manifest from platform-specific images + docker manifest create "${IMAGE}:${VERSION}" \ + "${IMAGE}:${VERSION}-amd64" \ + "${IMAGE}:${VERSION}-arm64" + + # Push the manifest + docker manifest push "${IMAGE}:${VERSION}" + + echo "Pushed manifest: ${IMAGE}:${VERSION}" + + - name: Create and push latest tag + if: steps.check.outputs.should_build == 'true' + run: | + IMAGE="${{ steps.config.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + + # Only create 'latest' tag for stable releases (not edge, rc, alpha, beta) + if [ "$VERSION" = "edge" ]; then + echo "Skipping latest tag for edge version" + exit 0 + fi + + if echo "$VERSION" | grep -qE '(rc|alpha|beta)'; then + echo "Skipping latest tag for pre-release version: $VERSION" + exit 0 + fi + + echo "Creating latest manifest for $IMAGE (version: $VERSION)" + + # Create manifest for latest + docker manifest create "${IMAGE}:latest" \ + "${IMAGE}:${VERSION}-amd64" \ + "${IMAGE}:${VERSION}-arm64" + + # Push the latest manifest + docker manifest push "${IMAGE}:latest" + + echo "Pushed manifest: ${IMAGE}:latest" + + summary: + name: Summary + needs: [build-images, create-manifests] + if: always() + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Generate summary + run: | + { + echo "## Docker Images Build Summary" + echo "" + echo "| Component | Version | Status |" + echo "|-----------|---------|--------|" + + components="${{ inputs.components }}" + input_version="${{ inputs.version }}" + dry_run="${{ inputs.dry_run }}" + + for component in rust-server rust-mcp rust-connectors rust-bench-dashboard; do + # Use word boundary matching to avoid partial matches + if echo "$components" | grep -wq "$component"; then + case "$component" in + rust-server) image="apache/iggy" ;; + rust-mcp) image="apache/iggy-mcp" ;; + rust-connectors) image="apache/iggy-connect" ;; + rust-bench-dashboard) image="apache/iggy-bench-dashboard" ;; + esac + + # Resolve version for display + if [ "$input_version" = "auto" ]; then + chmod +x scripts/extract-version.sh 2>/dev/null || true + version=$(scripts/extract-version.sh "$component" 2>/dev/null || echo "unknown") + else + version="$input_version" + fi + + if [ "$dry_run" = "true" ]; then + echo "| $component | $version | Built (dry-run) |" + else + echo "| $component | $version | Published to \`$image:$version\` |" + fi + else + echo "| $component | - | Skipped |" + fi + done + + echo "" + if [ "$dry_run" = "true" ]; then + echo "**Mode:** Dry run (images not pushed)" + else + echo "**Mode:** Production (images pushed to Docker Hub)" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/_build_rust_artifacts.yml b/.github/workflows/_build_rust_artifacts.yml index f5b3c5006..8ecfe440a 100644 --- a/.github/workflows/_build_rust_artifacts.yml +++ b/.github/workflows/_build_rust_artifacts.yml @@ -41,7 +41,7 @@ on: binaries: type: string required: false - default: "iggy-server,iggy,iggy-bench,iggy-connectors" + default: "iggy-server,iggy,iggy-bench,iggy-connectors,iggy-mcp" description: "Comma-separated list of binaries to build" connector_plugins: type: string @@ -245,9 +245,102 @@ jobs: retention-days: 7 if-no-files-found: error + build-bench-dashboard: + name: Bench dashboard ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + runner: ubuntu-latest + - target: aarch64-unknown-linux-gnu + runner: ubuntu-24.04-arm + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.commit || github.sha }} + + - name: Apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save .github scripts + /tmp/copy-latest-from-master.sh apply + + - name: Setup Rust with cache + uses: ./.github/actions/utils/setup-rust-with-cache + with: + cache-targets: false + + - name: Add Rust targets + run: | + rustup target add ${{ matrix.target }} + rustup target add wasm32-unknown-unknown + + - name: Install trunk + run: | + # Install trunk for WASM frontend build + TRUNK_VERSION="0.21.15" + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) TRUNK_ARCH="x86_64-unknown-linux-gnu" ;; + aarch64) TRUNK_ARCH="aarch64-unknown-linux-gnu" ;; + *) + echo "Unsupported architecture: $ARCH" + exit 1 + ;; + esac + curl -L "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-${TRUNK_ARCH}.tar.gz" | tar xz + sudo mv trunk /usr/local/bin/ + trunk --version + + - name: Build frontend (WASM) + run: | + cd core/bench/dashboard/frontend + trunk build --release + + - name: Build backend binary + run: | + cargo build --locked --release --target ${{ matrix.target }} --bin iggy-bench-dashboard-server + + - name: Package bench-dashboard + id: pkg + run: | + target="${{ matrix.target }}" + version="${{ inputs.version }}" + outdir="dist/${target}" + mkdir -p "${outdir}/dist" + + # Copy backend binary + cp "target/${target}/release/iggy-bench-dashboard-server" "${outdir}/" + + # Copy frontend dist folder + cp -r core/bench/dashboard/frontend/dist/* "${outdir}/dist/" + + tarball="iggy-bench-dashboard-${target}-${version}.tar.gz" + tar czf "${tarball}" -C "${outdir}" . + echo "tarball=${tarball}" >> "$GITHUB_OUTPUT" + + - name: Upload artifact + if: inputs.upload_artifacts + uses: actions/upload-artifact@v4 + with: + name: bench-dashboard-${{ matrix.target }} + path: ${{ steps.pkg.outputs.tarball }} + retention-days: 7 + if-no-files-found: error + collect: name: Collect artifacts - needs: [build-binaries, build-connector-plugins] + needs: [build-binaries, build-connector-plugins, build-bench-dashboard] if: inputs.upload_artifacts runs-on: ubuntu-latest outputs: @@ -288,6 +381,7 @@ jobs: size=$(ls -lh "$tarball" | awk '{print $5}') if [[ "$filename" == iggy-connectors-* ]]; then type="plugins" + elif [[ "$filename" == iggy-bench-dashboard-* ]]; then type="dashboard" else type="binaries"; fi if [[ "$filename" == *"x86_64"* ]]; then target="x86_64" diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml index 84a2bc9ed..50300cdb0 100644 --- a/.github/workflows/post-merge.yml +++ b/.github/workflows/post-merge.yml @@ -34,67 +34,6 @@ env: IGGY_CI_BUILD: true jobs: - plan: - name: Plan dockerhub components - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.mk.outputs.matrix }} - steps: - - uses: actions/checkout@v4 - - - name: Load publish config (base64) - id: cfg - shell: bash - run: | - if ! command -v yq >/dev/null 2>&1; then - YQ_VERSION="v4.47.1" - YQ_CHECKSUM="0fb28c6680193c41b364193d0c0fc4a03177aecde51cfc04d506b1517158c2fb" - curl -sSL -o /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 - echo "${YQ_CHECKSUM} /usr/local/bin/yq" | sha256sum -c - || exit 1 - chmod +x /usr/local/bin/yq - fi - echo "components_b64=$(yq -o=json -I=0 '.components' .github/config/publish.yml | base64 -w0)" >> "$GITHUB_OUTPUT" - - - name: Build matrix - id: mk - uses: actions/github-script@v7 - with: - script: | - const b64 = `${{ steps.cfg.outputs.components_b64 }}` || ''; - if (!b64) { - core.setOutput('matrix', JSON.stringify({ include: [{ component: 'noop' }] })); - return; - } - const comps = JSON.parse(Buffer.from(b64, 'base64').toString('utf8')); - const include = Object.entries(comps) - .filter(([_, v]) => v && v.registry === 'dockerhub') - .map(([k]) => ({ component: k })); - const uniq = Array.from(new Map(include.map(i => [i.component, i])).values()); - core.setOutput('matrix', JSON.stringify(uniq.length ? { include: uniq } : { include: [{ component: 'noop' }] })); - - docker-edge: - name: ${{ matrix.component }} - needs: plan - if: ${{ fromJson(needs.plan.outputs.matrix).include[0].component != 'noop' }} - runs-on: ubuntu-latest - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.plan.outputs.matrix) }} - env: - DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - uses: ./.github/actions/utils/docker-buildx - with: - task: publish - libc: musl - component: ${{ matrix.component }} - version: edge - dry_run: ${{ github.event.repository.fork }} # forks: always dry-run - build-artifacts: name: Build artifacts uses: ./.github/workflows/_build_rust_artifacts.yml @@ -102,6 +41,17 @@ jobs: version: edge upload_artifacts: true + docker-images: + name: Docker images + needs: build-artifacts + if: needs.build-artifacts.result == 'success' + uses: ./.github/workflows/_build_docker_images.yml + with: + version: edge + dry_run: ${{ github.event.repository.fork }} + components: "rust-server,rust-mcp,rust-connectors,rust-bench-dashboard" + secrets: inherit + create-prerelease: name: Create edge pre-release runs-on: ubuntu-latest @@ -153,6 +103,10 @@ jobs: - `iggy` - The command-line interface - `iggy-bench` - The benchmarking tool - `iggy-connectors` - The connectors runtime + - `iggy-mcp` - Model Context Protocol server for LLM integration + + ## Bench Dashboard + - `iggy-bench-dashboard-server` - Benchmarking web dashboard (with frontend) ## Connector plugins included (.so) - `iggy_connector_elasticsearch_sink` @@ -176,6 +130,10 @@ jobs: - `iggy-connectors-x86_64-unknown-linux-gnu-edge.tar.gz` - Linux x86_64 (glibc) - `iggy-connectors-aarch64-unknown-linux-gnu-edge.tar.gz` - Linux ARM64 (glibc) + ### Bench Dashboard + - `iggy-bench-dashboard-x86_64-unknown-linux-gnu-edge.tar.gz` - Linux x86_64 (glibc) + - `iggy-bench-dashboard-aarch64-unknown-linux-gnu-edge.tar.gz` - Linux ARM64 (glibc) + ## Build info - Server version: ${{ steps.meta.outputs.server_version }} - Commit: ${{ github.sha }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 77470d3c5..29856b00b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -136,10 +136,13 @@ jobs: outputs: targets: ${{ steps.mk.outputs.targets }} non_rust_targets: ${{ steps.mk.outputs.non_rust_targets }} + non_docker_targets: ${{ steps.mk.outputs.non_docker_targets }} count: ${{ steps.mk.outputs.count }} go_sdk_version: ${{ steps.mk.outputs.go_sdk_version }} has_python: ${{ steps.mk.outputs.has_python }} has_rust_crates: ${{ steps.mk.outputs.has_rust_crates }} + rust_docker_components: ${{ steps.mk.outputs.rust_docker_components }} + has_web_ui: ${{ steps.mk.outputs.has_web_ui }} steps: - name: Download latest copy script from master if: inputs.use_latest_ci @@ -263,9 +266,13 @@ jobs: } } + // Filter non-Docker targets (for SDK publishing, not handled by _build_docker_images.yml) + const nonDockerTargets = nonRustTargets.filter(t => t.type !== 'docker'); + console.log(`Publishing ${targets.length} components:`); targets.forEach(t => console.log(` - ${t.name} (${t.type}) -> ${t.registry || 'N/A'}`)); console.log(` (${nonRustTargets.length} non-Rust, ${targets.length - nonRustTargets.length} Rust crates)`); + console.log(` (${nonDockerTargets.length} non-Docker SDKs for matrix job)`); // Output all targets for reference and tag creation core.setOutput('targets', JSON.stringify(targets.length ? { include: targets } : { include: [{ key: 'noop', type: 'noop' }] })); @@ -273,10 +280,24 @@ jobs: // Output only non-Rust targets for the parallel publish job core.setOutput('non_rust_targets', JSON.stringify(nonRustTargets.length ? { include: nonRustTargets } : { include: [{ key: 'noop', type: 'noop' }] })); + // Output only non-Docker, non-Rust targets (SDKs only) + core.setOutput('non_docker_targets', JSON.stringify(nonDockerTargets.length ? { include: nonDockerTargets } : { include: [{ key: 'noop', type: 'noop' }] })); + core.setOutput('count', String(targets.length)); core.setOutput('go_sdk_version', goVersion); core.setOutput('has_rust_crates', String(hasRustCrates)); + // Separate Rust Docker components from web-ui + // web-ui uses docker-buildx action directly (Node.js, doesn't need pre-built artifacts) + const rustDockerComponents = targets + .filter(t => t.type === 'docker' && t.key !== 'web-ui') + .map(t => t.key) + .join(','); + const hasWebUi = targets.some(t => t.key === 'web-ui'); + + core.setOutput('rust_docker_components', rustDockerComponents); + core.setOutput('has_web_ui', String(hasWebUi)); + // Check if Python SDK is in targets and extract version const pythonTarget = targets.find(t => t.key === 'sdk-python'); if (pythonTarget) { @@ -455,6 +476,84 @@ jobs: use_latest_ci: ${{ inputs.use_latest_ci }} commit: ${{ needs.validate.outputs.commit }} + build-docker-artifacts: + name: Build Docker artifacts + needs: [validate, plan, check-tags] + if: | + needs.validate.outputs.has_targets == 'true' && + inputs.publish_dockerhub != '' + uses: ./.github/workflows/_build_rust_artifacts.yml + with: + version: "publish" + upload_artifacts: true + use_latest_ci: ${{ inputs.use_latest_ci }} + commit: ${{ needs.validate.outputs.commit }} + + publish-docker-images: + name: Publish Rust Docker images + needs: [validate, plan, check-tags, build-docker-artifacts] + if: | + needs.validate.outputs.has_targets == 'true' && + needs.plan.outputs.rust_docker_components != '' && + needs.build-docker-artifacts.result == 'success' + uses: ./.github/workflows/_build_docker_images.yml + with: + version: "auto" + dry_run: ${{ inputs.dry_run }} + components: ${{ needs.plan.outputs.rust_docker_components }} + secrets: inherit + + # web-ui uses docker-buildx directly (Node.js, doesn't benefit from pre-built artifacts) + publish-web-ui: + name: Publish web-ui Docker image + needs: [validate, plan, check-tags] + if: | + needs.validate.outputs.has_targets == 'true' && + needs.plan.outputs.has_web_ui == 'true' + runs-on: ubuntu-latest + steps: + - name: Download latest copy script from master + if: inputs.use_latest_ci + run: | + curl -sSL "https://raw.githubusercontent.com/${{ github.repository }}/master/scripts/copy-latest-from-master.sh" \ + -o /tmp/copy-latest-from-master.sh + chmod +x /tmp/copy-latest-from-master.sh + echo "✅ Downloaded latest copy script from master" + + - name: Checkout at commit + uses: actions/checkout@v4 + with: + ref: ${{ needs.validate.outputs.commit }} + + - name: Save and apply latest CI from master + if: inputs.use_latest_ci + run: | + /tmp/copy-latest-from-master.sh save \ + .github \ + scripts \ + web/Dockerfile + + /tmp/copy-latest-from-master.sh apply + + - name: Extract web-ui version + id: version + run: | + chmod +x scripts/extract-version.sh + VERSION=$(scripts/extract-version.sh web-ui) + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "📦 web-ui version: $VERSION" + + - name: Build and push web-ui image + uses: ./.github/actions/utils/docker-buildx + env: + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + with: + task: publish + component: web-ui + version: ${{ steps.version.outputs.version }} + dry_run: ${{ inputs.dry_run }} + # Sequential Rust crate publishing to handle dependencies properly publish-rust-crates: name: Publish Rust Crates @@ -610,17 +709,15 @@ jobs: if: | always() && needs.validate.outputs.has_targets == 'true' && - fromJson(needs.plan.outputs.non_rust_targets).include[0].key != 'noop' && + fromJson(needs.plan.outputs.non_docker_targets).include[0].key != 'noop' && (needs.build-python-wheels.result == 'success' || needs.build-python-wheels.result == 'skipped') && (needs.publish-rust-crates.result == 'success' || needs.publish-rust-crates.result == 'skipped') runs-on: ubuntu-latest strategy: fail-fast: false - matrix: ${{ fromJson(needs.plan.outputs.non_rust_targets) }} + matrix: ${{ fromJson(needs.plan.outputs.non_docker_targets) }} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NEXUS_USER: ${{ secrets.NEXUS_USER }} @@ -667,7 +764,7 @@ jobs: test -x scripts/extract-version.sh || chmod +x scripts/extract-version.sh - name: Setup Rust toolchain (if needed) - if: matrix.type == 'rust' || matrix.type == 'docker' || matrix.type == 'python' + if: matrix.type == 'rust' || matrix.type == 'python' uses: ./.github/actions/utils/setup-rust-with-cache with: cache-targets: false @@ -696,18 +793,6 @@ jobs: echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "✅ Resolved ${{ matrix.key }} -> version=$VERSION tag=${TAG:-<none>}" - # ───────────────────────────────────────── - # Docker Publishing - # ───────────────────────────────────────── - - name: Publish Docker image - if: matrix.type == 'docker' - uses: ./.github/actions/utils/docker-buildx - with: - task: publish - component: ${{ matrix.key }} - version: ${{ steps.ver.outputs.version }} - dry_run: ${{ inputs.dry_run }} - # ───────────────────────────────────────── # Python SDK Publishing # ───────────────────────────────────────── @@ -774,6 +859,8 @@ jobs: check-tags, build-python-wheels, publish-rust-crates, + publish-docker-images, + publish-web-ui, publish, ] if: | @@ -782,6 +869,8 @@ jobs: inputs.dry_run == false && inputs.skip_tag_creation == false && (needs.publish.result == 'success' || needs.publish.result == 'skipped') && + (needs.publish-docker-images.result == 'success' || needs.publish-docker-images.result == 'skipped') && + (needs.publish-web-ui.result == 'success' || needs.publish-web-ui.result == 'skipped') && (needs.publish-rust-crates.result == 'success' || needs.publish-rust-crates.result == 'skipped') && (needs.build-python-wheels.result == 'success' || needs.build-python-wheels.result == 'skipped') runs-on: ubuntu-latest @@ -873,6 +962,8 @@ jobs: check-tags, build-python-wheels, publish-rust-crates, + publish-docker-images, + publish-web-ui, publish, create-tags, ] @@ -1055,6 +1146,8 @@ jobs: plan, build-python-wheels, publish-rust-crates, + publish-docker-images, + publish-web-ui, publish, create-tags, summary, diff --git a/core/ai/mcp/Dockerfile b/core/ai/mcp/Dockerfile index f5fa7e977..6566d347d 100644 --- a/core/ai/mcp/Dockerfile +++ b/core/ai/mcp/Dockerfile @@ -85,7 +85,26 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry-${TARG fi # -# Final runtime - Debian trixie Slim +# Prebuilt path (FAST) - for CI with pre-compiled binaries +# +FROM debian:trixie-slim AS prebuilt +ARG PREBUILT_IGGY_MCP +WORKDIR /out +COPY ${PREBUILT_IGGY_MCP} /out/iggy-mcp +RUN chmod +x /out/iggy-mcp + +# +# Final runtime using prebuilt binary +# +FROM debian:trixie-slim AS runtime-prebuilt +WORKDIR /app + +COPY --from=prebuilt /out/iggy-mcp /usr/local/bin/iggy-mcp + +ENTRYPOINT ["iggy-mcp"] + +# +# Final runtime - Debian trixie Slim (from source build) # FROM debian:trixie-slim AS runtime WORKDIR /app diff --git a/core/bench/dashboard/server/Dockerfile b/core/bench/dashboard/server/Dockerfile index 29e103824..417e1ff48 100644 --- a/core/bench/dashboard/server/Dockerfile +++ b/core/bench/dashboard/server/Dockerfile @@ -53,8 +53,66 @@ RUN cd core/bench/dashboard/frontend && trunk build --release # Build the server with release profile RUN cargo build --release --bin iggy-bench-dashboard-server -# Runtime stage -FROM debian:trixie-slim +# +# Prebuilt path (FAST) - for CI with pre-compiled binaries and frontend +# +FROM debian:trixie-slim AS prebuilt +ARG PREBUILT_IGGY_BENCH_DASHBOARD_SERVER +ARG PREBUILT_FRONTEND_DIST +WORKDIR /out +COPY ${PREBUILT_IGGY_BENCH_DASHBOARD_SERVER} /out/iggy-bench-dashboard-server +COPY ${PREBUILT_FRONTEND_DIST} /out/frontend/dist +RUN chmod +x /out/iggy-bench-dashboard-server + +# +# Runtime stage using prebuilt binaries +# +FROM debian:trixie-slim AS runtime-prebuilt + +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + openssl \ + curl \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +# Copy prebuilt binary and frontend files +COPY --from=prebuilt /out/iggy-bench-dashboard-server /app/ +COPY --from=prebuilt /out/frontend/dist /app/frontend/dist + +# Create data directory and non-root user +RUN groupadd -r iggy && \ + useradd -r -g iggy -s /bin/false iggy && \ + mkdir -p /data/performance_results && \ + chown -R iggy:iggy /app /data && \ + chmod -R 755 /data/performance_results + +# Copy the entrypoint script +COPY core/bench/dashboard/server/docker-entrypoint.sh /app/ +RUN chmod +x /app/docker-entrypoint.sh && \ + chown iggy:iggy /app/docker-entrypoint.sh + +# Set default environment variables for configuration +ENV HOST=0.0.0.0 \ + PORT=80 \ + RESULTS_DIR=/data/performance_results + +# Set volume for results with proper permissions +VOLUME ["/data/performance_results"] + +# Switch to non-root user +USER iggy + +# Set the entrypoint script +ENTRYPOINT ["/app/docker-entrypoint.sh"] + +# +# Runtime stage (from source build) +# +FROM debian:trixie-slim AS runtime WORKDIR /app diff --git a/core/connectors/runtime/Dockerfile b/core/connectors/runtime/Dockerfile index 7e78fe1a5..c65159133 100644 --- a/core/connectors/runtime/Dockerfile +++ b/core/connectors/runtime/Dockerfile @@ -85,7 +85,26 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry-${TARG fi # -# Final runtime - Debian trixie Slim +# Prebuilt path (FAST) - for CI with pre-compiled binaries +# +FROM debian:trixie-slim AS prebuilt +ARG PREBUILT_IGGY_CONNECTORS +WORKDIR /out +COPY ${PREBUILT_IGGY_CONNECTORS} /out/iggy-connectors +RUN chmod +x /out/iggy-connectors + +# +# Final runtime using prebuilt binary +# +FROM debian:trixie-slim AS runtime-prebuilt +WORKDIR /app + +COPY --from=prebuilt /out/iggy-connectors /usr/local/bin/iggy-connectors + +ENTRYPOINT ["iggy-connectors"] + +# +# Final runtime - Debian trixie Slim (from source build) # FROM debian:trixie-slim AS runtime WORKDIR /app
