name: CI
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
workflow_dispatch: # Allow manual trigger
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: "3.13"
UV_CACHE_DIR: /tmp/.uv-cache
jobs:
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Setup Python
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: uv sync --dev
- name: Install ruff
run: uv add --dev ruff
- name: Check code formatting with Ruff
run: uv run ruff format --check .
- name: Lint with Ruff
run: uv run ruff check . --output-format=github
continue-on-error: true # Allow to fail for now
- name: Type check with mypy
run: uv run mypy chmcp
continue-on-error: true # Allow to fail for now
test:
name: Test Suite
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
python-version: ["3.13", "3.14"] # Test multiple Python versions if needed
fail-fast: false
services:
clickhouse:
image: clickhouse/clickhouse-server:24.11-alpine # Use latest stable
ports:
- 9000:9000
- 8123:8123
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Setup Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install Project with all dependencies
run: uv sync --all-extras --dev
- name: Wait for ClickHouse to be ready
run: |
echo "Waiting for ClickHouse to be ready..."
for i in {1..30}; do
if curl -s http://localhost:8123/ping > /dev/null; then
echo "✅ ClickHouse is ready!"
break
fi
echo "⏳ Waiting for ClickHouse... ($i/30)"
sleep 2
done
- name: Test ClickHouse connection
run: |
curl -v http://localhost:8123/
echo "SELECT 1" | curl -s 'http://localhost:8123/' --data-binary @-
- name: Run unit tests
env:
CLICKHOUSE_HOST: "localhost"
CLICKHOUSE_PORT: "8123"
CLICKHOUSE_USER: "default"
CLICKHOUSE_PASSWORD: ""
CLICKHOUSE_SECURE: "false"
CLICKHOUSE_VERIFY: "false"
CLICKHOUSE_CONNECT_TIMEOUT: "10"
CLICKHOUSE_SEND_RECEIVE_TIMEOUT: "30"
run: |
uv run pytest tests/ -v \
--cov=chmcp \
--cov-report=term-missing \
--cov-report=xml \
--junit-xml=test-results.xml
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.python-version }}
path: |
test-results.xml
coverage.xml
continue-on-error: true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: matrix.python-version == '3.13' # Only upload once
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
continue-on-error: true
security:
name: Security Scan
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Setup Python
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: uv sync --dev
- name: Run security checks with bandit
run: uv run bandit -r chmcp/ -f json -o bandit-report.json
continue-on-error: true
- name: Upload security scan results
uses: actions/upload-artifact@v4
if: always()
with:
name: security-scan-results
path: bandit-report.json
docker-build:
name: Docker Build & Test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: chmcp
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=test
- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: .
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
- name: Test Docker image import
run: |
docker run --rm chmcp:test python -c "
import chmcp
print('✅ MCP ClickHouse Cloud & On-Prem package imports successfully!')
print(f'Version: {chmcp.__version__}')
print(f'Author: {chmcp.__author__}')
"
- name: Test Docker image health check
run: |
echo "Testing Docker container startup..."
# Test basic container functionality
docker run --rm chmcp:test python -c "
import chmcp
print('✅ Container and package work correctly')
"
# Test with timeout to simulate actual usage
timeout 10s docker run --rm \
-e CLICKHOUSE_HOST=dummy \
-e CLICKHOUSE_USER=test \
chmcp:test \
python -c 'from chmcp.main import main; print("✅ Main module loads successfully")' \
|| echo "Expected timeout or controlled exit"
- name: Test Docker image with environment variables
run: |
docker run --rm \
-e CLICKHOUSE_HOST=test-host \
-e CLICKHOUSE_USER=test-user \
-e CLICKHOUSE_PASSWORD=test-pass \
chmcp:test python -c "
import os
from chmcp.mcp_env import get_config
try:
config = get_config()
print('✅ Configuration loads successfully with environment variables')
print(f'Host: {config.host}')
print(f'User: {config.username}')
except Exception as e:
print(f'❌ Configuration failed: {e}')
exit(1)
"
package-test:
name: Package Installation Test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Setup Python
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Build package
run: uv build
- name: Test package installation in fresh environment
run: |
# Create a fresh virtual environment
uv venv test-env
source test-env/bin/activate
# Install the built package
uv pip install dist/*.whl
# Test import
python -c "import chmcp; print('✅ Package installs and imports correctly')"
# Test CLI
python -m chmcp.main --help || echo "CLI help displayed"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: python-package
path: dist/
# Integration test with real ClickHouse Cloud (only on main branch)
integration:
name: Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 15
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [lint, test]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Setup Python
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Install Project
run: uv sync --all-extras --dev
- name: Run integration tests
env:
CLICKHOUSE_HOST: "sql-clickhouse.clickhouse.com"
CLICKHOUSE_PORT: "8443"
CLICKHOUSE_USER: "demo"
CLICKHOUSE_PASSWORD: ""
CLICKHOUSE_SECURE: "true"
CLICKHOUSE_VERIFY: "true"
run: |
uv run pytest tests/ -m integration -v
continue-on-error: true # Integration tests may fail due to external dependencies
summary:
name: CI Summary
runs-on: ubuntu-latest
needs: [lint, test, security, docker-build, package-test]
if: always()
steps:
- name: Check all jobs status
run: |
echo "## CI Results Summary" >> $GITHUB_STEP_SUMMARY
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-----|---------|" >> $GITHUB_STEP_SUMMARY
echo "| Lint | ${{ needs.lint.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Test | ${{ needs.test.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Security | ${{ needs.security.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Docker Build | ${{ needs.docker-build.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Package Test | ${{ needs.package-test.result }} |" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.lint.result }}" == "failure" || "${{ needs.test.result }}" == "failure" || "${{ needs.docker-build.result }}" == "failure" || "${{ needs.package-test.result }}" == "failure" ]]; then
echo "❌ Some critical jobs failed"
exit 1
else
echo "✅ All critical jobs passed"
fi