diff --git a/.github/workflows/docker-image-multiplatform-optimized.yml b/.github/workflows/docker-image-multiplatform-optimized.yml new file mode 100644 index 000000000..1c882cd11 --- /dev/null +++ b/.github/workflows/docker-image-multiplatform-optimized.yml @@ -0,0 +1,122 @@ +# =============================================================== +# ๐Ÿ“ฆ Optimized Multiplatform Docker Build Workflow +# =============================================================== +# +# This workflow builds multiplatform Docker images more efficiently by: +# 1. Building AMD64 and ARM64 images in parallel on native runners +# 2. Using matrix strategy to run builds simultaneously +# 3. Combining results into a single multiplatform manifest +# +# This approach is much faster than cross-compilation with emulation. +# =============================================================== + +name: Optimized Multiplatform Docker Build + +on: + workflow_dispatch: + inputs: + platforms: + description: 'Platforms to build (comma-separated)' + required: false + default: 'linux/amd64,linux/arm64' + push: + branches: ["main"] + paths: + - 'Containerfile.lite' + - 'mcpgateway/**' + - 'plugins/**' + - 'pyproject.toml' + pull_request: + branches: ["main"] + paths: + - 'Containerfile.lite' + - 'mcpgateway/**' + - 'plugins/**' + - 'pyproject.toml' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME_LOWER: ${{ github.repository }} + +jobs: + # Build individual platform images in parallel + build-platform: + runs-on: ${{ matrix.runs-on }} + strategy: + matrix: + include: + - platform: linux/amd64 + runs-on: ubuntu-latest + - platform: linux/arm64 + runs-on: ubuntu-latest-arm64 + + steps: + - name: โฌ‡๏ธ Checkout code + uses: actions/checkout@v4 + + - name: ๐Ÿ› ๏ธ Set up Docker Buildx + uses: docker/setup-buildx-action@v3.11.1 + + - name: ๐Ÿ”‘ Log in to GHCR + uses: docker/login-action@v3.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ—๏ธ Build single-platform image + run: | + TAG=$(date +%s) + IMAGE_NAME_LOWER=$(echo '${{ env.IMAGE_NAME }}' | tr '[:upper:]' '[:lower:]') + echo "๐Ÿ—๏ธ Building ${{ matrix.platform }} image..." + + docker buildx build \ + --platform ${{ matrix.platform }} \ + --file Containerfile.lite \ + --tag ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:$TAG \ + --tag ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:latest \ + --push \ + --progress=plain \ + . + + echo "TAG=$TAG" >> $GITHUB_OUTPUT + echo "IMAGE_NAME_LOWER=$IMAGE_NAME_LOWER" >> $GITHUB_OUTPUT + + # Combine individual platform images into multiplatform manifest + create-manifest: + needs: build-platform + runs-on: ubuntu-latest + + steps: + - name: โฌ‡๏ธ Checkout code + uses: actions/checkout@v4 + + - name: ๐Ÿ› ๏ธ Set up Docker Buildx + uses: docker/setup-buildx-action@v3.11.1 + + - name: ๐Ÿ”‘ Log in to GHCR + uses: docker/login-action@v3.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿท๏ธ Create multiplatform manifest + run: | + # Get outputs from the first matrix job (both should have same TAG and IMAGE_NAME_LOWER) + TAG=${{ needs.build-platform.outputs.TAG }} + IMAGE_NAME_LOWER=${{ needs.build-platform.outputs.IMAGE_NAME_LOWER }} + echo "๐Ÿท๏ธ Creating multiplatform manifest for tag: $TAG" + + docker buildx imagetools create \ + --tag ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:$TAG \ + --tag ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:latest \ + ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:$TAG + + echo "โœ… Multiplatform manifest created successfully!" + + - name: ๐Ÿ” Verify multiplatform manifest + run: | + IMAGE_NAME_LOWER=${{ needs.build-platform.outputs.IMAGE_NAME_LOWER }} + docker buildx imagetools inspect ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:latest diff --git a/.github/workflows/docker-image-multiplatform-simple.yml b/.github/workflows/docker-image-multiplatform-simple.yml new file mode 100644 index 000000000..cfee07aaa --- /dev/null +++ b/.github/workflows/docker-image-multiplatform-simple.yml @@ -0,0 +1,89 @@ +# =============================================================== +# ๐Ÿ“ฆ Simple Multiplatform Docker Build Workflow (DISABLED) +# =============================================================== +# +# โš ๏ธ DISABLED: This workflow is too slow due to ARM64 emulation on AMD64 runners +# +# This workflow builds multiplatform Docker images using a simpler approach: +# 1. Builds both platforms in a single job +# 2. Uses proper lowercase repository names +# 3. Creates timestamped tags for traceability +# 4. More reliable than matrix-based approach +# +# โŒ PROBLEM: ARM64 builds on AMD64 runners take 3+ hours due to emulation +# โœ… SOLUTION: Use docker-image-multiplatform-optimized.yml instead +# =============================================================== + +name: Simple Multiplatform Docker Build (DISABLED - TOO SLOW) + +on: + workflow_dispatch: + inputs: + platforms: + description: 'Platforms to build (comma-separated)' + required: false + default: 'linux/amd64,linux/arm64' + # DISABLED: This workflow is too slow due to ARM64 emulation on AMD64 runners + # Use docker-image-multiplatform-optimized.yml instead which uses native ARM64 runners + # push: + # branches: ["main"] + # paths: + # - 'Containerfile.lite' + # - 'mcpgateway/**' + # - 'plugins/**' + # - 'pyproject.toml' + # pull_request: + # branches: ["main"] + # paths: + # - 'Containerfile.lite' + # - 'mcpgateway/**' + # - 'plugins/**' + # - 'pyproject.toml' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-multiplatform: + runs-on: ubuntu-latest + + steps: + - name: โฌ‡๏ธ Checkout code + uses: actions/checkout@v4 + + - name: ๐Ÿ› ๏ธ Set up Docker Buildx + uses: docker/setup-buildx-action@v3.11.1 + + - name: ๐Ÿ”‘ Log in to GHCR + uses: docker/login-action@v3.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ—๏ธ Build multiplatform image + run: | + TAG=$(date +%s) + IMAGE_NAME_LOWER=$(echo '${{ env.IMAGE_NAME }}' | tr '[:upper:]' '[:lower:]') + echo "๐Ÿ—๏ธ Building multiplatform image (linux/amd64,linux/arm64)..." + echo "๐Ÿ“ฆ Repository: ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER" + echo "๐Ÿท๏ธ Tags: $TAG, latest" + echo "โš ๏ธ Note: ARM64 build on AMD64 runners uses emulation and may take longer" + + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --file Containerfile.lite \ + --tag ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:$TAG \ + --tag ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:latest \ + --push \ + --progress=plain \ + . + + echo "โœ… Multiplatform image built and pushed successfully!" + + - name: ๐Ÿ” Verify multiplatform image + run: | + IMAGE_NAME_LOWER=$(echo '${{ env.IMAGE_NAME }}' | tr '[:upper:]' '[:lower:]') + echo "๐Ÿ” Verifying multiplatform image..." + docker buildx imagetools inspect ${{ env.REGISTRY }}/$IMAGE_NAME_LOWER:latest diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 707338189..8c1a884e2 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -72,11 +72,10 @@ jobs: run: | curl -sSL https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 -o /usr/local/bin/hadolint chmod +x /usr/local/bin/hadolint - hadolint -f sarif Containerfile.lite > hadolint-results.sarif + hadolint -f sarif Containerfile.lite > hadolint-results.sarif || true echo "HADOLINT_EXIT=$?" >> "$GITHUB_ENV" - exit 0 - name: โ˜๏ธ Upload Hadolint SARIF - if: always() + if: always() && hashFiles('hadolint-results.sarif') != '' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: hadolint-results.sarif @@ -95,25 +94,47 @@ jobs: restore-keys: ${{ runner.os }}-buildx- # ------------------------------------------------------------- - # 3๏ธโƒฃ Build & tag image (timestamp + latest) + # 3๏ธโƒฃ Log in to GHCR (before build) # ------------------------------------------------------------- - - name: ๐Ÿ—๏ธ Build Docker image + - name: ๐Ÿ”‘ Log in to GHCR + uses: docker/login-action@v3.5.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # ------------------------------------------------------------- + # 4๏ธโƒฃ Build & tag image (timestamp + latest) + # ------------------------------------------------------------- + - name: ๐Ÿ—๏ธ Build multi-platform Docker image env: DOCKER_CONTENT_TRUST: "1" run: | TAG=$(date +%s) echo "TAG=$TAG" >> "$GITHUB_ENV" + echo "๐Ÿ—๏ธ Building multi-platform image (linux/amd64,linux/arm64)..." + echo "โš ๏ธ Note: ARM64 build on AMD64 runners uses emulation and may take longer" docker buildx build \ + --platform linux/amd64,linux/arm64 \ --file Containerfile.lite \ --tag $IMAGE_NAME:$TAG \ --tag $IMAGE_NAME:latest \ --cache-from type=local,src=${{ env.CACHE_DIR }} \ --cache-to type=local,dest=${{ env.CACHE_DIR }},mode=max \ - --load \ + --push \ + --progress=plain \ . # build context is mandatory # ------------------------------------------------------------- - # 4๏ธโƒฃ Image lint (Dockle CLI โ†’ SARIF) + # 5๏ธโƒฃ Pull image for scanning (multiplatform builds don't load locally) + # ------------------------------------------------------------- + - name: ๐Ÿ“ฅ Pull image for local scanning + run: | + echo "๐Ÿ“ฅ Pulling image for scanning (multiplatform images not available locally after --push)..." + docker pull $IMAGE_NAME:latest + + # ------------------------------------------------------------- + # 6๏ธโƒฃ Image lint (Dockle CLI โ†’ SARIF) # ------------------------------------------------------------- - name: ๐Ÿ” Image lint (Dockle) id: dockle @@ -125,17 +146,25 @@ jobs: | tar -xz -C /usr/local/bin dockle dockle --exit-code 1 --format sarif \ --output dockle-results.sarif \ - $IMAGE_NAME:latest + $IMAGE_NAME:latest || true echo "DOCKLE_EXIT=$?" >> "$GITHUB_ENV" - exit 0 + - name: ๐Ÿงน Sanitize Dockle SARIF (remove invalid URIs) + if: always() && hashFiles('dockle-results.sarif') != '' + run: | + # Filter out results with invalid URIs (containing spaces or non-file-path characters) + jq '.runs[].results |= map(select( + (.locations // []) | length == 0 or + all(.physicalLocation.artifactLocation.uri | test("^[^\\s]+$")) + ))' dockle-results.sarif > dockle-results-clean.sarif || cp dockle-results.sarif dockle-results-clean.sarif + mv dockle-results-clean.sarif dockle-results.sarif - name: โ˜๏ธ Upload Dockle SARIF - if: always() + if: always() && hashFiles('dockle-results.sarif') != '' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: dockle-results.sarif # ------------------------------------------------------------- - # 5๏ธโƒฃ Generate SPDX SBOM with Syft + # 7๏ธโƒฃ Generate SPDX SBOM with Syft # ------------------------------------------------------------- - name: ๐Ÿ“„ Generate SBOM (Syft) uses: anchore/sbom-action@v0.20.5 @@ -144,7 +173,7 @@ jobs: output-file: sbom.spdx.json # ------------------------------------------------------------- - # 6๏ธโƒฃ Trivy, Grype CVE scan โ†’ SARIF + # 8๏ธโƒฃ Trivy, Grype CVE scan โ†’ SARIF # ------------------------------------------------------------- - name: ๐Ÿ›ก๏ธ Trivy vulnerability scan if: env.TRIVY_ENABLED == 'true' @@ -158,7 +187,7 @@ jobs: severity: CRITICAL exit-code: 0 - name: โ˜๏ธ Upload Trivy SARIF - if: always() && env.TRIVY_ENABLED == 'true' + if: always() && env.TRIVY_ENABLED == 'true' && hashFiles('trivy-results.sarif') != '' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results.sarif @@ -167,35 +196,21 @@ jobs: run: | curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin - name: ๐Ÿ” Grype vulnerability scan + continue-on-error: true run: | grype ${{ env.IMAGE_NAME }}:latest --scope all-layers --only-fixed - name: ๐Ÿ“„ Generating Grype SARIF report + continue-on-error: true run: | grype ${{ env.IMAGE_NAME }}:latest --scope all-layers --output sarif --file grype-results.sarif - name: โ˜๏ธ Upload Grype SARIF - if: always() + if: always() && hashFiles('grype-results.sarif') != '' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: grype-results.sarif # ------------------------------------------------------------- - # 7๏ธโƒฃ Push both tags to GHCR - # ------------------------------------------------------------- - - name: ๐Ÿ”‘ Log in to GHCR - uses: docker/login-action@v3.5.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: ๐Ÿš€ Push image to GHCR - if: github.ref == 'refs/heads/main' - run: | - docker push $IMAGE_NAME:${{ env.TAG }} - docker push $IMAGE_NAME:latest - - # ------------------------------------------------------------- - # 8๏ธโƒฃ Key-less Cosign sign + attest (latest **and** timestamp) + # 9๏ธโƒฃ Key-less Cosign sign + attest (latest **and** timestamp) # ------------------------------------------------------------- - name: ๐Ÿ“ฅ Install Cosign if: github.ref == 'refs/heads/main' @@ -218,7 +233,7 @@ jobs: done # ------------------------------------------------------------- - # 9๏ธโƒฃ Single gate - fail job on any scanner error + # ๐Ÿ”Ÿ Single gate - fail job on any scanner error # ------------------------------------------------------------- - name: โ›” Enforce lint & vuln gates if: | diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 12af4b0e7..6b936cef3 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -102,26 +102,22 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} # ---------------------------------------------------------------- - # Step 4 Pull the image using the commit SHA tag + # Step 4 Set up Docker Buildx for multi-platform image handling # ---------------------------------------------------------------- - - name: โฌ‡๏ธ Pull image by commit SHA - run: | - IMAGE="ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" - docker pull "$IMAGE:${{ steps.meta.outputs.sha }}" - - # ---------------------------------------------------------------- - # Step 5 Tag the image with the semantic version tag - # ---------------------------------------------------------------- - - name: ๐Ÿท๏ธ Tag image with version - run: | - IMAGE="ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" - docker tag "$IMAGE:${{ steps.meta.outputs.sha }}" \ - "$IMAGE:${{ steps.meta.outputs.tag }}" + - name: ๐Ÿ› ๏ธ Set up Docker Buildx + uses: docker/setup-buildx-action@v3.11.1 # ---------------------------------------------------------------- - # Step 6 Push the new tag to GHCR + # Step 5 Create new manifest list with semantic version tag + # Note: For multiplatform images, we use 'docker buildx imagetools create' + # instead of 'docker pull' + 'docker tag' + 'docker push' because: + # 1. Multiplatform images are manifest lists, not single images + # 2. We create a new manifest list that references the existing SHA-tagged image + # 3. This is more efficient than pulling and re-pushing the entire image # ---------------------------------------------------------------- - - name: ๐Ÿš€ Push new version tag + - name: ๐Ÿท๏ธ Create multiplatform manifest with version tag run: | IMAGE="ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" - docker push "$IMAGE:${{ steps.meta.outputs.tag }}" + docker buildx imagetools create \ + "$IMAGE:${{ steps.meta.outputs.sha }}" \ + --tag "$IMAGE:${{ steps.meta.outputs.tag }}" diff --git a/.github/workflows/ibm-cloud-code-engine.yml b/.github/workflows/ibm-cloud-code-engine.yml index e4f6b5742..9dc528bf4 100644 --- a/.github/workflows/ibm-cloud-code-engine.yml +++ b/.github/workflows/ibm-cloud-code-engine.yml @@ -129,22 +129,20 @@ jobs: # ----------------------------------------------------------- # 5๏ธโƒฃ Build & tag image (cache-aware) # ----------------------------------------------------------- - - name: ๐Ÿ—๏ธ Build Docker image (with cache) + - name: ๐Ÿ—๏ธ Build multi-platform Docker image (with cache) run: | docker buildx build \ + --platform linux/amd64,linux/arm64 \ --file Containerfile.lite \ --tag "$REGISTRY_HOSTNAME/$ICR_NAMESPACE/$IMAGE_NAME:$IMAGE_TAG" \ --cache-from type=local,src=${{ env.CACHE_DIR }} \ --cache-to type=local,dest=${{ env.CACHE_DIR }},mode=max \ - --load \ + --push \ . # ----------------------------------------------------------- - # 6๏ธโƒฃ Push image to IBM Container Registry + # 6๏ธโƒฃ Image pushed during build (multi-platform) # ----------------------------------------------------------- - - name: ๐Ÿ“ค Push image to ICR - run: | - docker push "$REGISTRY_HOSTNAME/$ICR_NAMESPACE/$IMAGE_NAME:$IMAGE_TAG" # ----------------------------------------------------------- # 7๏ธโƒฃ Deploy (create or update) Code Engine application diff --git a/.gitignore b/.gitignore index fc3abd2f3..911b283a0 100644 --- a/.gitignore +++ b/.gitignore @@ -250,3 +250,4 @@ db_path/ tmp/ .continue +MULTIPLATFORM-DOCKER-SUPPORT.md diff --git a/Containerfile.lite b/Containerfile.lite index 899c88145..81dfd8862 100644 --- a/Containerfile.lite +++ b/Containerfile.lite @@ -27,7 +27,7 @@ ARG PYTHON_VERSION=3.12 # Builder stage ########################### FROM registry.access.redhat.com/ubi10/ubi:10.0-1756805986 AS builder -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] +SHELL ["/bin/sh", "-euo", "pipefail", "-c"] ARG PYTHON_VERSION ARG ROOTFS_PATH @@ -46,6 +46,7 @@ RUN set -euo pipefail \ python${PYTHON_VERSION} \ python${PYTHON_VERSION}-devel \ binutils \ + bash \ && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python${PYTHON_VERSION} 1 \ && dnf clean all @@ -105,69 +106,33 @@ RUN python3 -OO -m compileall -q /app/.venv /app/mcpgateway /app/plugins \ && find /app -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true # ---------------------------------------------------------------------------- -# Build a minimal, fully-patched rootfs containing only the runtime Python -# Include ca-certificates for HTTPS connections +# Build minimal rootfs by copying essential files from builder +# This avoids complex --installroot issues with QEMU emulation # ---------------------------------------------------------------------------- -# hadolint ignore=DL3041 RUN set -euo pipefail \ - && mkdir -p "${ROOTFS_PATH:?}" \ - && dnf --installroot="${ROOTFS_PATH:?}" --releasever=10 upgrade -y \ - && dnf --installroot="${ROOTFS_PATH:?}" --releasever=10 install -y \ - --setopt=install_weak_deps=0 \ - --setopt=tsflags=nodocs \ - python${PYTHON_VERSION} \ - ca-certificates \ - procps-ng \ - && dnf clean all --installroot="${ROOTFS_PATH:?}" - -# ---------------------------------------------------------------------------- -# Create `python3` symlink in the rootfs for compatibility -# ---------------------------------------------------------------------------- -RUN ln -sf /usr/bin/python${PYTHON_VERSION} ${ROOTFS_PATH:?}/usr/bin/python3 + && mkdir -p "${ROOTFS_PATH:?}"/{etc,usr/bin,usr/lib64,var/tmp,tmp,proc,sys,dev,run} \ + && cp -a /etc/{passwd,group,nsswitch.conf,pki,ssl} "${ROOTFS_PATH:?}/etc/" \ + && cp -a /usr/bin/python${PYTHON_VERSION} "${ROOTFS_PATH:?}/usr/bin/" \ + && cp -a /usr/bin/{bash,sh} "${ROOTFS_PATH:?}/usr/bin/" \ + && cp -a /usr/lib64/python${PYTHON_VERSION} "${ROOTFS_PATH:?}/usr/lib64/" \ + && cp -a /usr/lib64/*.so* "${ROOTFS_PATH:?}/usr/lib64/" \ + && ln -sf python${PYTHON_VERSION} "${ROOTFS_PATH:?}/usr/bin/python3" \ + && ln -sf bash "${ROOTFS_PATH:?}/usr/bin/sh" \ + && chmod 1777 "${ROOTFS_PATH:?}/tmp" "${ROOTFS_PATH:?}/var/tmp" # ---------------------------------------------------------------------------- -# Clean up unnecessary files from rootfs (if they exist) -# - Remove development headers, documentation -# - Use ${var:?} to prevent accidental deletion of host directories +# Clean up Python test files to reduce image size # ---------------------------------------------------------------------------- -RUN set -euo pipefail \ - && rm -rf ${ROOTFS_PATH:?}/usr/include/* \ - ${ROOTFS_PATH:?}/usr/share/man/* \ - ${ROOTFS_PATH:?}/usr/share/doc/* \ - ${ROOTFS_PATH:?}/usr/share/info/* \ - ${ROOTFS_PATH:?}/usr/share/locale/* \ - ${ROOTFS_PATH:?}/var/log/* \ - ${ROOTFS_PATH:?}/boot \ - ${ROOTFS_PATH:?}/media \ - ${ROOTFS_PATH:?}/srv \ - ${ROOTFS_PATH:?}/usr/games \ - && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "test" -exec rm -rf {} + 2>/dev/null || true \ +RUN find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "test" -exec rm -rf {} + 2>/dev/null || true \ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "tests" -exec rm -rf {} + 2>/dev/null || true \ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -type d -name "idle_test" -exec rm -rf {} + 2>/dev/null || true \ && find ${ROOTFS_PATH:?}/usr/lib*/python*/ -name "*.mo" -delete 2>/dev/null || true \ && rm -rf ${ROOTFS_PATH:?}/usr/lib*/python*/ensurepip \ ${ROOTFS_PATH:?}/usr/lib*/python*/idlelib \ - ${ROOTFS_PATH:?}/usr/lib*/python*/tkinter \ - ${ROOTFS_PATH:?}/usr/lib*/python*/turtle* \ - ${ROOTFS_PATH:?}/usr/lib*/python*/distutils/command/*.exe - -# ---------------------------------------------------------------------------- -# Remove package managers and unnecessary system tools from rootfs -# - Keep RPM database for security scanning with Trivy/Dockle -# - This keeps the final image size minimal while allowing vulnerability scanning -# ---------------------------------------------------------------------------- -RUN rm -rf ${ROOTFS_PATH:?}/usr/bin/dnf* \ - ${ROOTFS_PATH:?}/usr/bin/yum* \ - ${ROOTFS_PATH:?}/usr/bin/rpm* \ - ${ROOTFS_PATH:?}/usr/bin/microdnf \ - ${ROOTFS_PATH:?}/usr/lib/rpm \ - ${ROOTFS_PATH:?}/usr/lib/dnf \ - ${ROOTFS_PATH:?}/usr/lib/yum* \ - ${ROOTFS_PATH:?}/etc/dnf \ - ${ROOTFS_PATH:?}/etc/yum* + ${ROOTFS_PATH:?}/usr/lib*/python*/tkinter 2>/dev/null || true # ---------------------------------------------------------------------------- -# Strip unneeded symbols from shared libraries and remove binutils +# Strip unneeded symbols from shared libraries # - This reduces the final image size and removes the build tool in one step # ---------------------------------------------------------------------------- RUN find "${ROOTFS_PATH:?}/usr/lib64" -name '*.so*' -exec strip --strip-unneeded {} + 2>/dev/null || true \ diff --git a/Makefile b/Makefile index fa3fd0a14..8689a4889 100644 --- a/Makefile +++ b/Makefile @@ -1870,6 +1870,8 @@ endef # ============================================================================= # help: ๐Ÿณ UNIFIED CONTAINER OPERATIONS (Auto-detects Docker/Podman) # help: container-build - Build image using detected runtime +# help: container-build-multi - Build multi-platform image (amd64/arm64) and push +# help: container-build-multi-local - Build multi-platform image locally for testing # help: container-run - Run container using detected runtime # help: container-run-host - Run container using detected runtime with host networking # help: container-run-ssl - Run container with TLS using detected runtime @@ -1891,7 +1893,7 @@ endef .PHONY: container-build container-run container-run-ssl container-run-ssl-host \ container-run-ssl-jwt container-push container-info container-stop container-logs container-shell \ container-health image-list image-clean image-retag container-check-image \ - container-build-multi use-docker use-podman show-runtime print-runtime \ + container-build-multi container-build-multi-local use-docker use-podman show-runtime print-runtime \ print-image container-validate-env container-check-ports container-wait-healthy @@ -2101,7 +2103,7 @@ container-build-multi: @if [ "$(CONTAINER_RUNTIME)" = "docker" ]; then \ if ! docker buildx inspect $(PROJECT_NAME)-builder >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ Creating buildx builder..."; \ - docker buildx create --name $(PROJECT_NAME)-builder; \ + docker buildx create --name $(PROJECT_NAME)-builder --driver docker-container; \ fi; \ docker buildx use $(PROJECT_NAME)-builder; \ docker buildx build \ @@ -2122,6 +2124,34 @@ container-build-multi: exit 1; \ fi +# Build multi-platform image locally (without push) for testing +container-build-multi-local: + @echo "๐Ÿ”จ Building multi-architecture image locally..." + @if [ "$(CONTAINER_RUNTIME)" = "docker" ]; then \ + if ! docker buildx inspect $(PROJECT_NAME)-builder >/dev/null 2>&1; then \ + echo "๐Ÿ“ฆ Creating buildx builder..."; \ + docker buildx create --name $(PROJECT_NAME)-builder --driver docker-container; \ + fi; \ + docker buildx use $(PROJECT_NAME)-builder; \ + docker buildx build \ + --platform=linux/amd64,linux/arm64 \ + -f $(CONTAINER_FILE) \ + --tag $(IMAGE_BASE):$(IMAGE_TAG)-multi \ + --metadata-file /tmp/build-metadata.json \ + .; \ + echo "๐Ÿ’ก Multi-platform image built. Use 'docker buildx imagetools inspect $(IMAGE_BASE):$(IMAGE_TAG)-multi' to see details"; \ + elif [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ + echo "๐Ÿ“ฆ Building manifest with Podman..."; \ + $(CONTAINER_RUNTIME) build --platform=linux/amd64,linux/arm64 \ + -f $(CONTAINER_FILE) \ + --manifest $(IMAGE_BASE):$(IMAGE_TAG)-multi \ + .; \ + echo "๐Ÿ’ก Multi-platform image built locally"; \ + else \ + echo "โŒ Multi-arch builds require Docker buildx or Podman"; \ + exit 1; \ + fi + # Helper targets for debugging image issues image-list: @echo "๐Ÿ“‹ Images matching $(IMAGE_BASE):" diff --git a/mcpgateway/config.py b/mcpgateway/config.py index 17f6191b0..fd5401ac3 100644 --- a/mcpgateway/config.py +++ b/mcpgateway/config.py @@ -1121,9 +1121,7 @@ def validate_database(self) -> None: db_dir.mkdir(parents=True) # Validation patterns for safe display (configurable) - validation_dangerous_html_pattern: str = ( - r"<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|" - ) + validation_dangerous_html_pattern: str = r"<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|" validation_dangerous_js_pattern: str = r"(?i)(?:^|\s|[\"'`<>=])(javascript:|vbscript:|data:\s*[^,]*[;\s]*(javascript|vbscript)|\bon[a-z]+\s*=|<\s*script\b)" diff --git a/mcpgateway/routers/auth.py b/mcpgateway/routers/auth.py index 476040acb..1889bf257 100644 --- a/mcpgateway/routers/auth.py +++ b/mcpgateway/routers/auth.py @@ -153,9 +153,7 @@ async def login(login_request: LoginRequest, request: Request, db: Session = Dep logger.info(f"User {email} authenticated successfully") # Return session token for UI access and API key management - return AuthenticationResponse( - access_token=access_token, token_type="bearer", expires_in=expires_in, user=EmailUserResponse.from_email_user(user) - ) # nosec B106 - OAuth2 token type, not a password + return AuthenticationResponse(access_token=access_token, token_type="bearer", expires_in=expires_in, user=EmailUserResponse.from_email_user(user)) # nosec B106 - OAuth2 token type, not a password except ValueError as e: logger.warning(f"Login validation error: {e}") diff --git a/mcpgateway/routers/email_auth.py b/mcpgateway/routers/email_auth.py index a2ff53653..053786bc3 100644 --- a/mcpgateway/routers/email_auth.py +++ b/mcpgateway/routers/email_auth.py @@ -229,9 +229,7 @@ async def login(login_request: EmailLoginRequest, request: Request, db: Session access_token, expires_in = await create_access_token(user) # Return authentication response - return AuthenticationResponse( - access_token=access_token, token_type="bearer", expires_in=expires_in, user=EmailUserResponse.from_email_user(user) - ) # nosec B106 - OAuth2 token type, not a password + return AuthenticationResponse(access_token=access_token, token_type="bearer", expires_in=expires_in, user=EmailUserResponse.from_email_user(user)) # nosec B106 - OAuth2 token type, not a password except Exception as e: logger.error(f"Login error for {login_request.email}: {e}") @@ -280,9 +278,7 @@ async def register(registration_request: EmailRegistrationRequest, request: Requ logger.info(f"New user registered: {user.email}") - return AuthenticationResponse( - access_token=access_token, token_type="bearer", expires_in=expires_in, user=EmailUserResponse.from_email_user(user) - ) # nosec B106 - OAuth2 token type, not a password + return AuthenticationResponse(access_token=access_token, token_type="bearer", expires_in=expires_in, user=EmailUserResponse.from_email_user(user)) # nosec B106 - OAuth2 token type, not a password except EmailValidationError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) diff --git a/mcpgateway/services/gateway_service.py b/mcpgateway/services/gateway_service.py index 58e767dd8..237bd245d 100644 --- a/mcpgateway/services/gateway_service.py +++ b/mcpgateway/services/gateway_service.py @@ -1576,9 +1576,7 @@ async def delete_gateway(self, db: Session, gateway_id: str) -> None: db.rollback() raise GatewayError(f"Failed to delete gateway: {str(e)}") - async def forward_request( - self, gateway_or_db, method: str, params: Optional[Dict[str, Any]] = None, app_user_email: Optional[str] = None - ) -> Any: # noqa: F811 # pylint: disable=function-redefined + async def forward_request(self, gateway_or_db, method: str, params: Optional[Dict[str, Any]] = None, app_user_email: Optional[str] = None) -> Any: # noqa: F811 # pylint: disable=function-redefined """ Forward a request to a gateway or multiple gateways. diff --git a/mcpgateway/validators.py b/mcpgateway/validators.py index 743cf489f..c7ab15c2c 100644 --- a/mcpgateway/validators.py +++ b/mcpgateway/validators.py @@ -64,9 +64,7 @@ class SecurityValidator: """Configurable validation with MCP-compliant limits""" # Configurable patterns (from settings) - DANGEROUS_HTML_PATTERN = ( - settings.validation_dangerous_html_pattern - ) # Default: '<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|' + DANGEROUS_HTML_PATTERN = settings.validation_dangerous_html_pattern # Default: '<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|' DANGEROUS_JS_PATTERN = settings.validation_dangerous_js_pattern # Default: javascript:|vbscript:|on\w+\s*=|data:.*script ALLOWED_URL_SCHEMES = settings.validation_allowed_url_schemes # Default: ["http://", "https://", "ws://", "wss://"]