name: Docker Build # Build and test Docker images on pushes to main and pull requests. # Note: Release tags (robust-mcp-server-v*) trigger mcp-server-release.yaml # which handles Docker Hub releases. This workflow is for CI testing only. on: push: branches: [main, master] paths: - "Dockerfile" - ".dockerignore" - "src/**/*.py" - "pyproject.toml" - "uv.lock" - ".github/workflows/docker.yaml" pull_request: branches: [main, master] paths: - "Dockerfile" - ".dockerignore" - "src/**/*.py" - "pyproject.toml" - "uv.lock" - ".github/workflows/docker.yaml" workflow_dispatch: workflow_call: # Cancel in-progress runs for the same branch concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: REGISTRY: ghcr.io IMAGE_NAME: freecad-robust-mcp jobs: build: name: Build Docker Image runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix=sha- - name: Get version for setuptools-scm id: version run: | # Get version from git describe (matches setuptools-scm behavior) # For tagged releases: v1.0.0 -> 1.0.0 # For dev builds: v1.0.0-5-g1234567 -> 1.0.0.dev5+g1234567 if git describe --tags --exact-match 2>/dev/null; then TAG=$(git describe --tags --exact-match) VERSION="${TAG#v}" else # Get the latest tag or use 0.0.0 if none exists LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") BASE_VERSION="${LATEST_TAG#v}" # Count commits since tag COMMITS=$(git rev-list "${LATEST_TAG}..HEAD" --count 2>/dev/null || echo "0") SHORT_SHA=$(git rev-parse --short HEAD) VERSION="${BASE_VERSION}.dev${COMMITS}+g${SHORT_SHA}" fi echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" echo "Detected version: $VERSION" - name: Build and push Docker image uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | VERSION=${{ steps.version.outputs.VERSION }} cache-from: type=gha cache-to: type=gha,mode=max - name: Test Docker image run: | # Build for current platform only for testing (--no-cache ensures fresh build) docker build --no-cache --build-arg VERSION=${{ steps.version.outputs.VERSION }} -t ${{ env.IMAGE_NAME }}:test . # Test that the container starts and responds to MCP initialize echo '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \ timeout 30 docker run --rm -i \ -e FREECAD_MODE=xmlrpc \ ${{ env.IMAGE_NAME }}:test 2>&1 | \ grep -q '"result"' && echo "Container test passed" || echo "Container test completed" - name: Cache Trivy vulnerability database uses: actions/cache@v4 with: path: .trivy-cache # Cache key based on OS and workflow file hash; refreshes when workflow changes key: trivy-db-${{ runner.os }}-${{ hashFiles('.github/workflows/docker.yaml') }} restore-keys: | trivy-db-${{ runner.os }}- - name: Scan for HIGH/CRITICAL vulnerabilities (fail build) uses: aquasecurity/trivy-action@0.33.1 with: image-ref: ${{ env.IMAGE_NAME }}:test version: "v0.68.2" severity: "HIGH,CRITICAL" exit-code: "1" format: "table" cache-dir: .trivy-cache trivyignores: ".trivyignore" - name: Scan for MEDIUM/LOW vulnerabilities (warning only) uses: aquasecurity/trivy-action@0.33.1 with: image-ref: ${{ env.IMAGE_NAME }}:test version: "v0.68.2" severity: "MEDIUM,LOW" exit-code: "0" format: "table" cache-dir: .trivy-cache trivyignores: ".trivyignore" continue-on-error: true - name: Generate SARIF report for GitHub Security if: github.event_name != 'pull_request' uses: aquasecurity/trivy-action@0.33.1 with: image-ref: ${{ env.IMAGE_NAME }}:test version: "v0.68.2" format: "sarif" output: "trivy-results.sarif" cache-dir: .trivy-cache trivyignores: ".trivyignore" continue-on-error: true - name: Upload Trivy scan results if: github.event_name != 'pull_request' uses: github/codeql-action/upload-sarif@v4 with: sarif_file: "trivy-results.sarif" continue-on-error: true