name: CI
# This workflow runs on every push and PR to protected branches and gitflow branches.
# It coordinates with parallel security workflows:
# - snyk.yml: Vulnerability scanning (runs independently)
# - sonarqube.yml: Code quality analysis (runs independently)
# - gitflow.yml: Branch naming and PR target validation
# All workflows must pass for merge protection (configure in branch settings)
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: "3.13"
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Lint with ruff
run: |
poetry run ruff check .
poetry run ruff format --check .
- name: Type check with mypy
run: |
poetry run mypy souschef
- name: Test with pytest
run: |
poetry run pytest --cov=souschef --cov-report=xml --cov-report=term-missing
- name: Upload coverage to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
if: matrix.python-version == '3.13'
with:
files: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
- name: Upload coverage for SonarCloud
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
if: matrix.python-version == '3.13'
with:
name: coverage-report
path: coverage.xml
retention-days: 1
security-tests:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Run security tests
run: |
poetry run pytest tests/unit/test_security.py -v --tb=short
mutation-test:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: test
permissions:
contents: read
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Run mutation testing on core modules
run: |
# Run mutation testing on core validation module with limited scope
MUTMUT=1 timeout 480 poetry run mutmut run --paths-to-mutate souschef/core/validation.py --test-command "poetry run pytest tests/unit/test_server.py::TestValidationFramework -x --tb=line" --runner pytest || true
# Show results
poetry run mutmut results | head -20
test-terraform-provider:
runs-on: ubuntu-latest
timeout-minutes: 15
needs: test
permissions:
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
with:
go-version: '1.24.x'
cache-dependency-path: terraform-provider/go.sum
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Install Python dependencies
run: poetry install --no-interaction --all-extras
- name: Set souschef path for Terraform tests
run: |
SOUSCHEF_PATH=$(realpath $(poetry run which souschef))
echo "TF_VAR_souschef_path=$SOUSCHEF_PATH" >> $GITHUB_ENV
echo "Souschef CLI path: $SOUSCHEF_PATH"
- name: Run Go unit tests
working-directory: terraform-provider
run: |
go test ./internal/provider -v -timeout 5m
- name: Run Terraform provider acceptance tests
env:
TF_ACC: "1"
run: |
cd terraform-provider
go test ./internal/provider -v -timeout 15m
test-cli:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: test
permissions:
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Test CLI functionality
run: |
# Test basic CLI commands
poetry run souschef --help
# Test with fixture files
poetry run souschef ls tests/integration/fixtures/sample_cookbook
poetry run souschef structure tests/integration/fixtures/sample_cookbook
poetry run souschef metadata tests/integration/fixtures/sample_cookbook/metadata.rb
# Test recipe parsing
if [ -f tests/integration/fixtures/sample_cookbook/recipes/default.rb ]; then
poetry run souschef recipe tests/integration/fixtures/sample_cookbook/recipes/default.rb
fi
# Test template parsing
TEMPLATE_FILE=$(find tests/integration/fixtures/sample_cookbook/templates/default -name '*.erb' -type f | head -n 1)
if [ -n "$TEMPLATE_FILE" ]; then
poetry run souschef template "$TEMPLATE_FILE"
fi
security:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0 # Need full history for TruffleHog
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Run security checks
run: |
# Check for known vulnerabilities in dependencies
poetry run pip-audit || echo "::warning::Vulnerabilities detected - review required"
- name: Check for secrets
uses: trufflesecurity/trufflehog@50aa4695becccb04c958215388cab34a75e0fd31 # main
with:
path: ./
base: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD~1' }}
head: ${{ github.event.pull_request.head.sha || github.sha }}
extra_args: --only-verified
ansible-lint:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: test
permissions:
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install ansible-lint
run: pip install ansible-lint
- name: Create test playbooks from examples
run: |
mkdir -p .ansible-lint-test
# Create a minimal playbook to validate ansible-lint is working
cat > .ansible-lint-test/test-playbook.yml << 'EOF'
---
- name: Test playbook for linting
hosts: localhost
gather_facts: false
tasks:
- name: Debug message
ansible.builtin.debug:
msg: "Ansible-lint validation working"
EOF
- name: Run ansible-lint on test playbook
run: |
ansible-lint .ansible-lint-test/
- name: Lint any generated playbooks in examples
run: |
# Find and lint any YAML files that look like Ansible playbooks
find examples -name '*.yml' -o -name '*.yaml' | while read -r file; do
if grep -q 'hosts:' "$file" 2>/dev/null; then
echo "Linting potential playbook: $file"
ansible-lint "$file" || true # Don't fail on example files
fi
done
continue-on-error: true
build:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [test, security-tests, test-terraform-provider, test-cli, security, ansible-lint]
permissions:
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
- name: Cache Poetry dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: .venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install dependencies
run: poetry install --no-interaction --all-extras
- name: Build package
run: poetry build
- name: Check package
run: |
poetry run twine check dist/*
- name: Upload build artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: dist-packages
path: dist/
retention-days: 7