CI/CD Integration
Pacta is designed for CI/CD pipelines. This guide shows how to integrate architecture checks into your workflow.
Exit Codes
| Code | Meaning |
|---|---|
0 |
Success, no violations (or no new violations with baseline) |
1 |
Violations found |
2 |
Engine error (config issue, parse error, etc.) |
GitHub Actions
Basic Check
Run architecture validation on every pull request:
name: Architecture Check
on:
pull_request:
branches: [main]
jobs:
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Pacta
run: pip install pacta
- name: Check Architecture
run: pacta scan . --model architecture.yml --rules rules.pacta.yml
With Baseline (Recommended)
For existing projects with legacy violations, use baselines to only fail on new violations:
name: Architecture Check
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Pacta
run: pip install pacta
# On main branch: update baseline
- name: Update Baseline
if: github.ref == 'refs/heads/main'
run: |
pacta scan . \
--model architecture.yml \
--rules rules.pacta.yml \
--save-ref baseline
# On PR: check against baseline
- name: Check Against Baseline
if: github.event_name == 'pull_request'
run: |
pacta scan . \
--model architecture.yml \
--rules rules.pacta.yml \
--baseline baseline
Persisting baselines
The baseline is stored in .pacta/snapshots/. Commit this directory to your repository, or use GitHub Actions cache/artifacts to persist it between runs.
JSON Output for PR Comments
Generate JSON output and post results as a PR comment:
name: Architecture Check
on:
pull_request:
branches: [main]
jobs:
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Pacta
run: pip install pacta
- name: Run Architecture Check
id: pacta
continue-on-error: true
run: |
pacta scan . \
--model architecture.yml \
--rules rules.pacta.yml \
--baseline baseline \
--format json > results.json
# Extract summary for PR comment
VIOLATIONS=$(jq '.summary.total_violations' results.json)
NEW=$(jq '.summary.new_violations // 0' results.json)
echo "violations=$VIOLATIONS" >> $GITHUB_OUTPUT
echo "new=$NEW" >> $GITHUB_OUTPUT
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const violations = ${{ steps.pacta.outputs.violations }};
const newViolations = ${{ steps.pacta.outputs.new }};
let body;
if (violations === 0) {
body = '### Architecture Check Passed\n\nNo architectural violations found.';
} else if (newViolations === 0) {
body = `### Architecture Check Passed\n\n${violations} existing violation(s), 0 new.`;
} else {
body = `### Architecture Check Failed\n\n**${newViolations} new violation(s)** introduced.\n\nRun \`pacta scan\` locally to see details.`;
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
- name: Fail on New Violations
if: steps.pacta.outputs.new > 0
run: exit 1
GitLab CI
Basic Check
architecture:
stage: test
image: python:3.11
script:
- pip install pacta
- pacta scan . --model architecture.yml --rules rules.pacta.yml
With Baseline
stages:
- test
variables:
PACTA_BASELINE: baseline
architecture:
stage: test
image: python:3.11
script:
- pip install pacta
- |
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
# Update baseline on main
pacta scan . --model architecture.yml --rules rules.pacta.yml --save-ref $PACTA_BASELINE
else
# Check against baseline on feature branches
pacta scan . --model architecture.yml --rules rules.pacta.yml --baseline $PACTA_BASELINE
fi
cache:
paths:
- .pacta/
Pre-commit Hook
Run Pacta as a pre-commit hook to catch violations before they're committed:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: pacta
name: Architecture Check
entry: pacta scan . --model architecture.yml --rules rules.pacta.yml -q
language: system
pass_filenames: false
types: [python]
Performance
Use -q (quiet mode) for faster output in hooks. Consider running against baseline for large codebases with legacy violations.
Makefile Integration
Add Pacta to your Makefile for easy local runs:
.PHONY: arch arch-snapshot arch-check arch-baseline arch-ci
# Full scan (snapshot + check in one step)
arch:
pacta scan . --model architecture.yml --rules rules.pacta.yml
# Two-step workflow
arch-snapshot:
pacta snapshot save . --model architecture.yml
arch-check:
pacta check . --rules rules.pacta.yml
# Update baseline
arch-baseline:
pacta snapshot save . --model architecture.yml --ref baseline
pacta check . --ref baseline --rules rules.pacta.yml
# Check against baseline (CI mode)
arch-ci:
pacta scan . --model architecture.yml --rules rules.pacta.yml --baseline baseline
JSON Schema
The --format json output follows this structure:
{
"run_info": {
"tool_version": "0.0.5",
"timestamp": "2025-01-22T12:00:00+00:00",
"commit": "abc1234",
"branch": "main"
},
"summary": {
"total_violations": 5,
"by_severity": {
"error": 3,
"warning": 2
},
"new_violations": 1,
"existing_violations": 4,
"fixed_violations": 0
},
"violations": [
{
"rule_id": "no_domain_to_infra",
"rule_name": "Domain cannot depend on Infrastructure",
"severity": "error",
"status": "new",
"location": {
"file": "src/domain/user.py",
"line": 3,
"column": 1
},
"message": "Domain layer must not import from Infrastructure",
"suggestion": "Use dependency injection...",
"explanation": "\"myapp.domain.UserService\" in domain layer imports \"myapp.infra.Database\" in infra layer"
}
]
}
Use jq to extract specific fields: