Skip to main content

CI/CD Integration Guide

This guide covers how to integrate CVT into your CI/CD pipelines for automated contract testing, consumer registration, and deployment safety checks.

Overview

A typical CVT-enabled pipeline includes:

  1. Contract tests - Validate interactions against OpenAPI schemas
  2. Consumer registration - Register your service's API dependencies after tests pass
  3. Deployment safety checks - Run can-i-deploy before deploying schema changes
Schema-Specific PR Checks

For PR checks that detect breaking changes when your OpenAPI spec file changes, see the Breaking Changes CI/CD section.


CVT works best when each team has a clear role. The producer owns the schema, the consumer validates against it, and CVT is the shared infrastructure that connects them.

┌──────────────────────────────┐
│ Producer CI/CD │
│ │
│ 1. Build & test API │
│ 2. Register schema with CVT │◄── Producer owns the OpenAPI spec
│ 3. can-i-deploy check │
│ 4. Deploy │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│ CVT Server (shared) │
│ │
│ • Registered schemas │◄── Central source of truth
│ • Consumer registrations │
│ • Validation engine │
└──────────────┬───────────────┘


┌──────────────────────────────┐
│ Consumer CI/CD │
│ │
│ 1. Run contract tests │◄── Validates against producer's schema
│ 2. Register as consumer │
│ 3. Deploy │
└──────────────────────────────┘
StepWhoWhatWhen
Register schemaProducerPublishes OpenAPI spec to CVTOn merge to main
Validate interactionsConsumerTests requests/responses against schemaOn every CI run
Register consumerConsumerRecords which endpoints are usedAfter tests pass on main
can-i-deployProducerChecks if schema changes break consumersBefore deploying
Schema Registration Ownership

The producer should register their schema in CVT — not the consumer. If consumers register schemas independently, you risk stale or inconsistent contracts. In local development, consumers may register schemas for convenience, but in CI/CD the producer's pipeline should be the single source of truth.


GitHub Actions

Consumer Contract Tests

name: Contract Tests

on: [push, pull_request]

jobs:
contract-tests:
runs-on: ubuntu-latest
services:
cvt:
image: ghcr.io/sahina/cvt:latest
ports:
- 9550:9550

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: npm ci

- name: Run contract tests
run: npm test

- name: Register consumer
if: github.ref == 'refs/heads/main'
run: |
npm run register-consumer -- \
--id my-service \
--version ${{ github.sha }} \
--schema petstore \
--env staging
Docker Image

The published CVT server image is available at ghcr.io/sahina/cvt:latest. You can also:

  1. Build your own image: docker build -t cvt .
  2. Use make up to run CVT locally
  3. Host CVT as a shared service in your infrastructure

Producer Deployment Safety

name: Deploy API

on:
push:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run contract tests
run: npm test

- name: Check deployment safety
run: |
cvt can-i-deploy \
--schema ${{ env.SCHEMA_ID }} \
--version ${{ env.VERSION }} \
--env prod \
--server ${{ secrets.CVT_SERVER }}

- name: Deploy
if: success()
run: ./deploy.sh

Pre-Deploy Safety Check

deploy:
needs: [contract-tests]
steps:
- name: Check if upstream is safe
run: |
# This checks if any upstream dependencies have breaking changes
cvt can-i-deploy \
--schema petstore \
--version ${{ env.SCHEMA_VERSION }} \
--env prod \
--json > check.json

if [ "$(jq '.safe_to_deploy' check.json)" != "true" ]; then
echo "Upstream API has breaking changes!"
jq '.affected_consumers[] | select(.will_break == true) | .consumer_id' check.json
exit 1
fi

GitLab CI

Prerequisites

The can-i-deploy job requires the cvt CLI and jq to be available. Either use a base image with these tools pre-installed, or add installation steps to your before_script.

Consumer Contract Tests

stages:
- test
- deploy

variables:
CVT_SERVER: "localhost:9550"

contract-tests:
stage: test
services:
- name: ghcr.io/sahina/cvt:latest
alias: cvt
variables:
CVT_SERVER: "cvt:9550"
script:
- npm ci
- npm test
artifacts:
reports:
junit: test-results.xml

register-consumer:
stage: deploy
only:
- main
script:
- |
npm run register-consumer -- \
--id my-service \
--version $CI_COMMIT_SHA \
--schema petstore \
--env staging

can-i-deploy:
stage: deploy
only:
- main
script:
- |
cvt can-i-deploy \
--schema petstore \
--version $SCHEMA_VERSION \
--env prod \
--json > check.json

if [ "$(jq '.safe_to_deploy' check.json)" != "true" ]; then
echo "Breaking changes detected!"
jq '.affected_consumers[] | select(.will_break == true)' check.json
exit 1
fi

Producer Deployment Safety

stages:
- test
- safety-check
- deploy

contract-tests:
stage: test
script:
- npm test

deployment-safety:
stage: safety-check
script:
- cvt can-i-deploy --schema $SCHEMA_ID --version $VERSION --env prod --json
allow_failure: false

deploy:
stage: deploy
script:
- ./deploy.sh
only:
- main

Jenkins

Jenkins plugins

This example uses readJSON from the Pipeline Utility Steps plugin.

Consumer Contract Tests

pipeline {
agent any

environment {
CVT_SERVER = 'cvt.internal:9550'
}

stages {
stage('Start CVT') {
steps {
sh 'docker-compose up -d cvt-server'
sh 'cvt wait --server localhost:9550 --timeout 60'
}
}

stage('Contract Tests') {
steps {
sh 'npm ci'
sh 'npm test'
}
post {
always {
junit 'test-results.xml'
}
}
}

stage('Register Consumer') {
when {
branch 'main'
}
steps {
sh """
npm run register-consumer -- \\
--id my-service \\
--version ${env.GIT_COMMIT} \\
--schema petstore \\
--env staging
"""
}
}

stage('Can I Deploy') {
when {
branch 'main'
}
steps {
script {
def result = sh(
script: """
cvt can-i-deploy \\
--schema petstore \\
--version ${env.SCHEMA_VERSION} \\
--env prod \\
--json
""",
returnStdout: true
).trim()

def json = readJSON text: result
if (!json.safe_to_deploy) {
error "Breaking changes detected! Affected consumers: ${json.affected_consumers}"
}
}
}
}
}

post {
always {
sh 'docker-compose down'
}
}
}

Next Steps