name: Unified CI/CD Pipeline
on:
push:
branches: ["*"] # Run on all branches
tags: ["release-pypi-*"] # Run on PyPI release tags
pull_request:
branches: ["main"] # Run on all PRs
env:
PYTHON_VERSION: '3.11'
jobs:
quick-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Install package
run: |
python -m pip install -e .
- name: Check basic imports and functionality
run: |
python -c "
from mcp_template import TemplateDiscovery, DockerDeploymentService, DeploymentManager
print('✅ All imports successful')
discovery = TemplateDiscovery()
templates = discovery.discover_templates()
print(f'✅ Found {len(templates)} templates')
manager = DeploymentManager(backend_type='mock')
print('✅ DeploymentManager initialized')
"
- name: Run basic unit tests (no external dependencies)
run: |
python -m pytest tests/test_unit -m "unit and not docker and not kubernetes" \
-x --tb=short --disable-warnings
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install linting dependencies
run: |
python -m pip install --upgrade pip
pip install black isort flake8 bandit
- name: Check code formatting
run: black --check mcp_template/ tests/
- name: Check import sorting
run: isort --check-only --profile black mcp_template/ tests/
- name: Run linting
run: flake8 mcp_template/ --exclude mcp_template/**/tests,tests
- name: Run security scan
run: bandit -r mcp_template/ --exit-zero
unit-tests:
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' || github.ref == 'refs/heads/main') }}
needs: quick-validation
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Install package
run: |
python -m pip install -e .
- name: Run all unit tests
run: |
python -m pytest tests/test_unit -m "unit" \
--tb=short
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: always()
with:
file: ./coverage.xml
fail_ci_if_error: false
docker-tests:
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' || github.ref == 'refs/heads/main') }}
needs: quick-validation
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Install package
run: |
python -m pip install -e .
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build demo Docker image
run: |
docker build -t dataeverything/mcp-demo:latest ./mcp_template/template/templates/demo
- name: Run Docker-specific tests
run: |
python -m pytest tests/ -m "docker" \
--tb=short \
--disable-warnings
kubernetes-tests:
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' || github.ref == 'refs/heads/main') }}
needs: quick-validation
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Install package
run: |
python -m pip install -e .
- name: Setup Kubernetes (kind)
uses: helm/kind-action@v1.10.0
with:
cluster_name: mcp-test-cluster
kubectl_version: v1.29.0
- name: Build demo Docker image
run: |
docker build -t dataeverything/mcp-demo:latest ./mcp_template/template/templates/demo
- name: Run Kubernetes-specific tests
run: |
python -m pytest tests/ -m "kubernetes" \
--tb=short \
--disable-warnings
template-tests:
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' || github.ref == 'refs/heads/main') }}
needs: quick-validation
strategy:
matrix:
template: [demo, filesystem, zendesk]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Test template ${{ matrix.template }}
run: |
if [ -d "mcp_template/template/templates/${{ matrix.template }}/tests" ]; then
python -m pytest mcp_template/template/templates/${{ matrix.template }}/tests/ -v --tb=short --cov-fail-under=0
else
echo "No tests found for template ${{ matrix.template }}"
fi
integration-tests:
runs-on: ubuntu-latest
if: ${{ !(github.event_name == 'pull_request' || github.ref == 'refs/heads/main') }}
needs: quick-validation
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup Kubernetes (kind)
uses: helm/kind-action@v1.10.0
with:
cluster_name: mcp-test-cluster
kubectl_version: v1.29.0
- name: Build demo Docker image
run: |
docker build -t dataeverything/mcp-demo:latest ./mcp_template/template/templates/demo
- name: Ensure Docker is logged out
run: docker logout || true
- name: Run full integration tests
run: |
python -m pytest tests/test_integration/ -m "integration" \
--tb=short \
--disable-warnings \
|| echo "⚠️ Some integration tests failed (non-blocking)"
full-test-suite:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
needs: quick-validation
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup Kubernetes (kind)
uses: helm/kind-action@v1.10.0
with:
cluster_name: mcp-test-cluster
kubectl_version: v1.29.0
- name: Build demo Docker image
run: |
docker build -t dataeverything/mcp-demo:latest ./mcp_template/template/templates/demo
- name: Ensure Docker is logged out
run: docker logout || true
- name: Run complete test suite
run: |
echo "🧪 Running complete test suite..."
python -m pytest tests/ \
--cov=mcp_template \
--cov-report=term-missing \
--cov-report=html \
--cov-report=xml \
--cov-fail-under=50 \
--tb=short \
--timeout=300
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: always()
with:
file: ./coverage.xml
fail_ci_if_error: false
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: htmlcov/
- name: Upload coverage XML
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-xml
path: coverage.xml
coverage-check:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
needs: [full-test-suite]
steps:
- name: Download coverage XML
uses: actions/download-artifact@v4
with:
name: coverage-xml
- name: Check coverage threshold
run: |
python - <<'PY'
import sys
import xml.etree.ElementTree as ET
try:
tree = ET.parse('coverage.xml')
except Exception as e:
print('ERROR: failed to read coverage.xml:', e)
sys.exit(1)
root = tree.getroot()
pct = None
lr = root.get('line-rate')
if lr:
try:
pct = float(lr) * 100.0
except Exception:
pct = None
if pct is None:
totals = root.find('totals')
if totals is not None:
lines = totals.find('lines')
if lines is not None and 'percent' in lines.attrib:
try:
pct = float(lines.attrib['percent'])
except Exception:
pct = None
if pct is None:
print('Could not determine total coverage from coverage.xml')
sys.exit(1)
print(f'Total coverage: {pct:.2f}%')
REQUIRED = 50.0
if pct < REQUIRED:
print(f'FAIL: Required test coverage of {REQUIRED}% not reached.')
sys.exit(1)
print('Coverage requirement satisfied')
PY
# Multi-Python version testing (only on main branch and release tags)
multi-python-tests:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || contains(github.ref, 'release-pypi-')
needs: [quick-validation]
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Run core tests on Python ${{ matrix.python-version }}
run: |
python -m pytest tests/test_unit -m "unit and not docker and not kubernetes" \
--tb=short \
--disable-warnings
# PyPI deployment (only on release tags)
deploy-pypi:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/release-pypi-')
needs: [full-test-suite, code-quality, multi-python-tests]
environment:
name: pypi
url: https://pypi.org/p/mcp-template
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine setuptools_scm
- name: Install package dependencies
run: |
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Show package version (from git tag)
run: |
python -c "from mcp_template import __version__; print(f'PyPI version from git tag: {__version__}')"
- name: Check version from setuptools_scm
run: |
VERSION=$(python -m setuptools_scm)
echo "Detected version: $VERSION"
if [[ "$VERSION" == "0.0.0" ]]; then
echo "❌ ERROR: setuptools_scm did not detect a version from tags"
exit 1
fi
- name: Build package
run: |
python -m build
- name: Check package
run: |
python -m twine check dist/*
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true