OpenAPI Generator Workflows — Automating Code Generation with GitHub Actions and CI/CD
In this tutorial, you will learn about OpenAPI Generator Workflows. We cover key concepts, practical examples, and best practices to help you master this topic.
OpenAPI Generator workflows automate spec-to-code pipelines in CI/CD systems, ensuring generated SDKs and server stubs are always up-to-date with the latest API specification.
What You'll Learn
- GitHub Actions workflow for OpenAPI generation
- GitLab CI pipeline for multi-language SDKs
- Jenkins pipeline with spec change detection
- Artifact publishing to package registries
- Versioning generated code alongside specs
Why It Matters
Manual generation is error-prone and doesn't scale. Automated pipelines ensure every spec change produces tested, versioned SDKs. DodaTech's CI pipeline generates, tests, and publishes SDKs for 8 languages within 3 minutes of a spec merge, used by 200+ integration partners.
Real-World Use
A developer merges an OpenAPI spec change adding a new endpoint. The CI workflow detects the spec change, generates Python and TypeScript SDKs, runs contract tests, creates GitHub releases, and publishes to PyPI and npm — all automatically within minutes.
flowchart LR
A["Developer pushes spec change"] --> B["GitHub Actions Triggered"]
B --> C["Validate spec"]
C --> D{"Spec valid?"}
D -->|"No"| E["Fail pipeline, notify developer"]
D -->|"Yes"| F["Generate SDKs"]
F --> G["Python SDK"]
F --> H["TypeScript SDK"]
F --> I["Java SDK"]
G --> J["Run contract tests"]
H --> J
I --> J
J --> K{"All tests pass?"}
K -->|"No"| E
K -->|"Yes"| L["Publish to registries"]
L --> M["PyPI, npm, Maven Central"]
Code Examples
Example 1: GitHub Actions Workflow
# .github/workflows/openapi-generate.yml
name: Generate SDKs from OpenAPI Spec
on:
push:
paths:
- 'openapi/**/*.yaml'
- 'openapi/**/*.json'
workflow_dispatch:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate OpenAPI spec
run: |
npm install -g @redocly/cli
redocly lint openapi/spec.yaml
generate:
needs: validate
strategy:
matrix:
language: [python, typescript-axios, java, go, csharp-netcore]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: '17'
- name: Generate ${{ matrix.language }} SDK
run: |
mkdir -p generated/${{ matrix.language }}
openapi-generator-cli generate \
-i openapi/spec.yaml \
-g ${{ matrix.language }} \
-o generated/${{ matrix.language }} \
--additional-properties=packageName=dodatech-${{ matrix.language }}
- name: Upload ${{ matrix.language }} SDK
uses: actions/upload-artifact@v4
with:
name: sdk-${{ matrix.language }}
path: generated/${{ matrix.language }}
test:
needs: generate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download all SDKs
uses: actions/download-artifact@v4
with:
pattern: sdk-*
path: generated
- name: Run contract tests
run: |
python scripts/test-sdks.py \
--spec openapi/spec.yaml \
--sdk-dir generated
Example 2: GitLab CI Pipeline
# .gitlab-ci.yml
stages:
- validate
- generate
- test
- publish
variables:
SPEC_FILE: openapi/spec.yaml
GENERATED_DIR: generated
validate-spec:
stage: validate
image: node:20
script:
- npm install -g @redocly/cli
- redocly lint $SPEC_FILE
generate-python:
stage: generate
image: openapitools/openapi-generator-cli:v7.0
script:
- mkdir -p $GENERATED_DIR/python
- openapi-generator-cli generate
-i $SPEC_FILE
-g python
-o $GENERATED_DIR/python
--additional-properties=packageName=dodatech_client
artifacts:
paths:
- $GENERATED_DIR/python
generate-typescript:
stage: generate
image: openapitools/openapi-generator-cli:v7.0
script:
- mkdir -p $GENERATED_DIR/typescript
- openapi-generator-cli generate
-i $SPEC_FILE
-g typescript-axios
-o $GENERATED_DIR/typescript
artifacts:
paths:
- $GENERATED_DIR/typescript
test-sdks:
stage: test
image: python:3.11
script:
- cd $GENERATED_DIR/python
- pip install -e .
- python -m pytest -v test/
- cd ../../
publish-pypi:
stage: publish
image: python:3.11
script:
- cd $GENERATED_DIR/python
- python setup.py sdist bdist_wheel
- TWINE_PASSWORD=$PYPI_TOKEN twine upload --username __token__ dist/*
only:
- main
Example 3: Custom Python Workflow Script
import subprocess
import json
import sys
from pathlib import Path
from datetime import datetime
class OpenAPIWorkflow:
def __init__(self, spec_path, config_path):
self.spec_path = Path(spec_path)
with open(config_path) as f:
self.config = json.load(f)
def run(self):
"""Run the complete generation workflow."""
print(f"Starting workflow for {self.spec_path}")
start_time = datetime.now()
# Step 1: Validate spec
print("\n[1/5] Validating spec...")
self._validate_spec()
# Step 2: Generate SDKs
print("\n[2/5] Generating SDKs...")
for lang_config in self.config['generators']:
self._generate_sdk(lang_config)
# Step 3: Generate server stubs
print("\n[3/5] Generating server stubs...")
for server_config in self.config.get('servers', []):
self._generate_server(server_config)
# Step 4: Run tests
print("\n[4/5] Running tests...")
self._run_tests()
# Step 5: Package artifacts
print("\n[5/5] Packaging artifacts...")
self._package_artifacts()
elapsed = (datetime.now() - start_time).total_seconds()
print(f"\nWorkflow completed in {elapsed:.1f} seconds")
def _validate_spec(self):
result = subprocess.run([
'redocly', 'lint', str(self.spec_path)
], capture_output=True, text=True)
if result.returncode != 0:
print("Spec validation failed:")
print(result.stdout)
sys.exit(1)
print("Spec validation passed")
def _generate_sdk(self, lang_config):
lang = lang_config['language']
output = self.config['output_dir'] / lang
output.mkdir(parents=True, exist_ok=True)
cmd = [
'openapi-generator-cli', 'generate',
'-i', str(self.spec_path),
'-g', lang,
'-o', str(output)
]
for key, value in lang_config.get('properties', {}).items():
cmd.extend(['--additional-properties', f"{key}={value}"])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print(f" {lang}: generated successfully")
else:
print(f" {lang}: FAILED - {result.stderr[:200]}")
def _run_tests(self):
for lang_config in self.config['generators']:
lang = lang_config['language']
output = self.config['output_dir'] / lang
test_dir = output / 'test'
if test_dir.exists():
result = subprocess.run([
'pytest', str(test_dir), '-v', '--tb=short'
], capture_output=True, text=True)
if result.returncode == 0:
print(f" {lang}: tests passed")
else:
print(f" {lang}: tests FAILED")
def _package_artifacts(self):
artifact_dir = self.config['output_dir'] / 'artifacts'
artifact_dir.mkdir(exist_ok=True)
for lang_config in self.config['generators']:
lang = lang_config['language']
source = self.config['output_dir'] / lang
target = artifact_dir / f"sdk-{lang}.zip"
subprocess.run(['zip', '-r', str(target), str(source)])
# Usage
workflow = OpenAPIWorkflow(
'openapi/spec.yaml',
'openapi/workflow-config.json'
)
workflow.run()
Common Mistakes
1. Generating on Every Commit
Only generate when the spec changes. Use path filters and spec hashing to avoid unnecessary runs.
2. Not Testing Generated Code
Generation is useless if the output doesn't work. Always run contract tests after generation.
3. Ignoring Generator Version Pinning
Generator CLI versions change behavior. Pin the exact version in your workflow.
4. No Spec Validation Before Generation
Generating from an invalid spec wastes time and produces broken code.
5. Publishing Unchanged SDKs
Check if the generated code actually changed before publishing. Use git diff or checksum comparison.
Practice Questions
- What CI events should trigger Code Generation?
- How do you pin the generator version in CI?
- Why run spec validation before generation?
- How do you handle spec changes that break existing clients?
- What artifacts should a generation workflow produce?
Answers:
- Pushes/merges that change spec files. Use path filters to limit triggers.
- Use a specific Docker image tag:
openapitools/openapi-generator-cli:v7.0.0. - Invalid specs produce broken code. Failing early saves time and prevents downstream issues.
- Use semantic versioning. Breaking changes go into a new API version. The old spec continues generating the previous SDK version.
- Compiled packages (wheel, npm package, JAR), documentation, test reports, and a changelog.
Challenge: Build a GitHub Actions workflow that validates an OpenAPI spec, generates Python and TypeScript SDKs, runs contract tests, and publishes to PyPI and npm only when tests pass.
FAQ
What's Next
Integrate {{< ilink "OpenAPI" "OpenAPI Generator Testing" }} into your CI workflow, and explore {{< ilink "OpenAPI" "CI/CD Code Generation with OpenAPI Generator" }} for advanced pipeline patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro