CI Orchestrator Design Document¶
Status: Concept/Design Phase Author: Claude Code AI Assistant Date: 2025-01-16 Purpose: Design specification for a CI/CD testing orchestration layer
Table of Contents¶
- Executive Summary
- GitHub Operations Analysis
- Python Testing Framework Design
- Abstraction Layer Architecture
- GitHub CLI Extension Design
- Common Patterns Analysis
- Integration Ecosystem
- Implementation Roadmap
Executive Summary¶
This document outlines the design for a CI/CD Testing Orchestration Layer that abstracts CI operations across multiple providers (GitHub Actions, GitLab CI, Jenkins, etc.) and provides a unified interface for testing, migrating, and comparing workflows.
Key Features¶
- Provider-agnostic: Works with any CI/CD platform through pluggable providers
- Extensible: Plugin system for custom test scenarios and metrics
- CLI-friendly: Natural extension of existing tools like
ghCLI - Programmatic: Full Python API for automation and integration
- Configuration-driven: YAML-based test scenario definitions
Primary Use Cases¶
- Workflow Testing: Validate CI workflows across multiple configurations
- Migration Assistance: Help migrate from custom workflows to shared actions
- Performance Comparison: Compare workflow implementations and optimizations
- Continuous Validation: Monitor workflow health and performance over time
GitHub Operations Analysis¶
Most Frequent Operations (>50% of usage)¶
Based on analysis of common CI/CD testing workflows, these are the most frequently used GitHub operations:
1. Workflow Management¶
gh workflow run {workflow.yml} # Trigger workflow execution
gh workflow list # List available workflows
gh run list --limit N # Check recent runs
gh run view {run_id} # View run details
gh run watch # Monitor running workflow
gh run view --web # Open run in browser
2. Repository Operations¶
gh repo create {org}/{name} # Create test repositories
gh repo clone {repo} # Clone for testing
gh repo view --web # Quick repository access
gh repo list {org} --limit N # List organization repos
3. Git Version Control¶
git status # Check working directory state
git add -A # Stage all changes
git commit -m "message" # Commit changes
git push origin {branch} # Push to remote
git checkout -b {branch} # Create test branches
git tag {version} # Tag releases
Common Operations (25-40% of usage)¶
4. Pull Request Workflow¶
gh pr create --title --body # Create test PRs
gh pr list # List active PRs
gh pr view # Review PR details
gh pr checks # Check CI status
5. Release Management¶
gh release create {tag} # Create releases
gh release view {tag} # View release details
gh release list # List releases
Occasional Operations (10-25% of usage)¶
6. Advanced Git Operations¶
gh api # Custom API calls
gh secret set # Configure repository secrets
gh repo fork # Test with forks
Python Testing Framework Design¶
Core Architecture¶
from github import Github # PyGithub
import subprocess
import time
from typing import Dict, List, Optional, Protocol
from dataclasses import dataclass
from enum import Enum
class WorkflowStatus(Enum):
PENDING = "pending"
RUNNING = "running"
SUCCESS = "success"
FAILURE = "failure"
CANCELLED = "cancelled"
@dataclass
class TestResult:
"""Standardized test result format"""
status: WorkflowStatus
duration: float
coverage: Optional[float]
violations: int
artifacts: List[str]
logs: str
metadata: Dict[str, Any]
@dataclass
class WorkflowRun:
"""Workflow run information"""
id: str
status: WorkflowStatus
url: str
created_at: datetime
completed_at: Optional[datetime]
jobs: List[Dict[str, Any]]
class GitHubTestOrchestrator:
"""Main orchestrator for GitHub CI/CD testing"""
def __init__(self, token: str, org: str = "provide-io"):
self.gh = Github(token)
self.org = org
def create_test_repo(self, name: str, **kwargs) -> Repository:
"""Create a test repository with specified configuration"""
org = self.gh.get_organization(self.org)
repo = org.create_repo(
name=name,
private=kwargs.get('private', False),
description=kwargs.get('description', 'CI Testing Repository'),
auto_init=True
)
return repo
def setup_test_branch(self, repo: str, branch: str) -> bool:
"""Create and checkout test branch in repository"""
repo_obj = self.gh.get_repo(repo)
main_sha = repo_obj.get_branch('main').commit.sha
repo_obj.create_git_ref(ref=f'refs/heads/{branch}', sha=main_sha)
return True
def trigger_workflow(self, repo: str, workflow: str,
ref: str = "main", inputs: Dict = None) -> WorkflowRun:
"""Trigger workflow and return run object"""
repo_obj = self.gh.get_repo(repo)
workflow_obj = repo_obj.get_workflow(workflow)
# Trigger workflow
workflow_obj.create_dispatch(ref=ref, inputs=inputs or {})
# Get the latest run
time.sleep(2) # Allow time for run to appear
runs = workflow_obj.get_runs()
latest_run = list(runs)[0]
return WorkflowRun(
id=str(latest_run.id),
status=WorkflowStatus(latest_run.status or "pending"),
url=latest_run.html_url,
created_at=latest_run.created_at,
completed_at=latest_run.updated_at if latest_run.conclusion else None,
jobs=[]
)
def wait_for_workflow(self, run: WorkflowRun, timeout: int = 300) -> str:
"""Wait for workflow completion with timeout"""
start_time = time.time()
repo_obj = self.gh.get_repo(f"{self.org}/{run.id.split('/')[0]}")
while time.time() - start_time < timeout:
workflow_run = repo_obj.get_workflow_run(int(run.id))
if workflow_run.status == "completed":
return workflow_run.conclusion
time.sleep(30) # Check every 30 seconds
raise TimeoutError(f"Workflow {run.id} did not complete within {timeout} seconds")
def analyze_workflow_results(self, run: WorkflowRun) -> TestResult:
"""Extract and analyze workflow results"""
repo_obj = self.gh.get_repo(f"{self.org}/{run.id.split('/')[0]}")
workflow_run = repo_obj.get_workflow_run(int(run.id))
# Calculate duration
duration = 0.0
if workflow_run.created_at and workflow_run.updated_at:
duration = (workflow_run.updated_at - workflow_run.created_at).total_seconds()
# Extract artifacts
artifacts = [artifact.name for artifact in workflow_run.get_artifacts()]
# Get logs
logs = ""
for job in workflow_run.jobs():
job_logs = job.logs()
logs += f"=== {job.name} ===\n{job_logs}\n\n"
return TestResult(
status=WorkflowStatus(workflow_run.conclusion or "pending"),
duration=duration,
coverage=None, # Would extract from artifacts/logs
violations=0, # Would parse from quality check outputs
artifacts=artifacts,
logs=logs,
metadata={
'run_id': run.id,
'url': run.url,
'jobs_count': workflow_run.jobs_count,
'run_number': workflow_run.run_number
}
)
class CIToolingTester:
"""Specialized tester for ci-tooling repository"""
def __init__(self, orchestrator: GitHubTestOrchestrator):
self.orchestrator = orchestrator
def test_action(self, action_name: str, inputs: Dict) -> TestResult:
"""Test individual GitHub Action"""
# Create test repository
test_repo = self.orchestrator.create_test_repo(f"test-{action_name}")
# Add test workflow that uses the action
workflow_content = self._generate_action_test_workflow(action_name, inputs)
test_repo.create_file(
".github/workflows/test.yml",
f"Test {action_name}",
workflow_content
)
# Trigger workflow
run = self.orchestrator.trigger_workflow(test_repo.full_name, "test.yml")
# Wait for completion
self.orchestrator.wait_for_workflow(run)
# Analyze results
return self.orchestrator.analyze_workflow_results(run)
def test_reusable_workflow(self, workflow: str, config: Dict) -> TestResult:
"""Test reusable workflow with specified configuration"""
# Similar to test_action but for reusable workflows
pass
def test_migration(self, source_repo: str) -> Dict[str, TestResult]:
"""Test workflow migration from old to new format"""
# 1. Analyze existing workflows
# 2. Generate migrated versions
# 3. Test both versions
# 4. Compare results
pass
def compare_workflows(self, old_run: WorkflowRun,
new_run: WorkflowRun) -> Dict[str, Any]:
"""Compare two workflow runs for performance and functionality"""
old_result = self.orchestrator.analyze_workflow_results(old_run)
new_result = self.orchestrator.analyze_workflow_results(new_run)
return {
'duration_change': new_result.duration - old_result.duration,
'duration_percent': ((new_result.duration - old_result.duration) / old_result.duration) * 100,
'status_comparison': {
'old': old_result.status,
'new': new_result.status,
'improved': new_result.status == WorkflowStatus.SUCCESS and old_result.status != WorkflowStatus.SUCCESS
},
'artifacts_comparison': {
'old_count': len(old_result.artifacts),
'new_count': len(new_result.artifacts),
'added': set(new_result.artifacts) - set(old_result.artifacts),
'removed': set(old_result.artifacts) - set(new_result.artifacts)
}
}
def _generate_action_test_workflow(self, action: str, inputs: Dict) -> str:
"""Generate test workflow YAML for an action"""
inputs_yaml = '\n'.join(f" {k}: {v}" for k, v in inputs.items())
return f"""name: Test {action}
on: [workflow_dispatch]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: provide-io/ci-tooling/actions/{action}@v0
with:
{inputs_yaml}
"""
Example Test Automation Script¶
#!/usr/bin/env python3
"""
Comprehensive CI testing automation script
"""
import json
import yaml
from pathlib import Path
def test_ci_tooling_comprehensive():
"""Run comprehensive test suite for ci-tooling"""
# Initialize orchestrator
orchestrator = GitHubTestOrchestrator(
token=os.environ["GITHUB_TOKEN"],
org="provide-io"
)
tester = CIToolingTester(orchestrator)
# Test configuration
test_config = {
'actions': [
{
'name': 'setup-python-env',
'inputs': {
'python-version': '3.11',
'uv-version': '0.7.8'
},
'matrix': [
{'python-version': '3.11'},
{'python-version': '3.12'},
{'python-version': '3.13'}
]
},
{
'name': 'python-quality',
'inputs': {
'source-paths': 'src/',
'fail-on-error': 'false'
}
},
{
'name': 'python-test',
'inputs': {
'coverage-threshold': '80'
}
}
],
'workflows': [
{
'name': 'python-ci.yml',
'config': {
'python-version': '3.11',
'matrix-testing': True,
'run-security': True
}
}
]
}
results = {}
# Test individual actions
for action_config in test_config['actions']:
action_name = action_config['name']
print(f"Testing action: {action_name}")
# Test base configuration
result = tester.test_action(action_name, action_config['inputs'])
results[f"action_{action_name}"] = result
# Test matrix configurations if specified
if 'matrix' in action_config:
for i, matrix_inputs in enumerate(action_config['matrix']):
combined_inputs = {**action_config['inputs'], **matrix_inputs}
matrix_result = tester.test_action(action_name, combined_inputs)
results[f"action_{action_name}_matrix_{i}"] = matrix_result
# Test reusable workflows
for workflow_config in test_config['workflows']:
workflow_name = workflow_config['name']
print(f"Testing workflow: {workflow_name}")
result = tester.test_reusable_workflow(workflow_name, workflow_config['config'])
results[f"workflow_{workflow_name.replace('.yml', '')}"] = result
# Generate comprehensive report
report = generate_test_report(results)
# Save results
with open('ci-test-results.json', 'w') as f:
json.dump(results, f, indent=2, default=str)
with open('ci-test-report.md', 'w') as f:
f.write(report)
return results
def generate_test_report(results: Dict[str, TestResult]) -> str:
"""Generate markdown test report"""
report = ["# CI Tooling Test Report", ""]
report.append(f"**Date**: {datetime.now().isoformat()}")
report.append(f"**Total Tests**: {len(results)}")
# Summary statistics
successful = sum(1 for r in results.values() if r.status == WorkflowStatus.SUCCESS)
failed = len(results) - successful
report.extend([
"",
"## Summary",
f"- ✅ **Successful**: {successful}",
f"- ❌ **Failed**: {failed}",
f"- 📊 **Success Rate**: {(successful/len(results))*100:.1f}%",
""
])
# Detailed results
report.extend(["## Detailed Results", ""])
report.append("| Test | Status | Duration | Artifacts |")
report.append("|------|--------|----------|-----------|")
for test_name, result in results.items():
status_emoji = "✅" if result.status == WorkflowStatus.SUCCESS else "❌"
report.append(
f"| {test_name} | {status_emoji} {result.status.value} | "
f"{result.duration:.1f}s | {len(result.artifacts)} |"
)
return "\n".join(report)
if __name__ == "__main__":
results = test_ci_tooling_comprehensive()
print(f"Testing complete. {len(results)} tests executed.")
Abstraction Layer Architecture¶
Core Design Principles¶
- Provider Agnostic: Support multiple CI/CD platforms
- Extensible: Plugin system for custom functionality
- Type Safe: Full typing support with protocols
- Testable: Each layer independently testable
Provider Pattern Implementation¶
from abc import ABC, abstractmethod
from typing import Protocol, Dict, Any, List, Optional
from dataclasses import dataclass
from enum import Enum
class WorkflowStatus(Enum):
PENDING = "pending"
RUNNING = "running"
SUCCESS = "success"
FAILURE = "failure"
CANCELLED = "cancelled"
@dataclass
class WorkflowResult:
"""Universal workflow result format across all providers"""
status: WorkflowStatus
duration: float
jobs: Dict[str, Dict[str, Any]]
artifacts: List[str]
logs: str
metadata: Dict[str, Any]
class CIProvider(Protocol):
"""Protocol that any CI provider must implement"""
def create_repository(self, name: str, **kwargs) -> str:
"""Create a repository and return its identifier"""
...
def trigger_workflow(self, repo: str, workflow: str,
inputs: Dict[str, Any]) -> str:
"""Trigger a workflow and return run ID"""
...
def get_workflow_status(self, run_id: str) -> WorkflowStatus:
"""Get current status of a workflow run"""
...
def get_workflow_result(self, run_id: str) -> WorkflowResult:
"""Get complete results of a workflow run"""
...
def create_pull_request(self, repo: str, title: str,
body: str, **kwargs) -> str:
"""Create a PR and return its ID"""
...
class CITestOrchestrator(ABC):
"""Abstract orchestrator that works with any provider"""
def __init__(self, provider: CIProvider):
self.provider = provider
@abstractmethod
def test_scenario(self, scenario: Dict[str, Any]) -> WorkflowResult:
"""Run a specific test scenario"""
pass
@abstractmethod
def compare_workflows(self, old: str, new: str) -> Dict[str, Any]:
"""Compare two workflow implementations"""
pass
GitHub Provider Implementation¶
import subprocess
import json
from typing import Dict, Any
class GitHubCLIProvider:
"""GitHub provider using gh CLI - implements CIProvider protocol"""
def __init__(self, org: str = "provide-io"):
self.org = org
self._validate_cli()
def _validate_cli(self):
"""Ensure gh CLI is available and authenticated"""
result = subprocess.run(["gh", "auth", "status"],
capture_output=True)
if result.returncode != 0:
raise RuntimeError("gh CLI not authenticated")
def _run_gh(self, *args) -> Dict[str, Any]:
"""Run gh command and return JSON output"""
cmd = ["gh"] + list(args) + ["--json"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"gh command failed: {result.stderr}")
return json.loads(result.stdout) if result.stdout else {}
def create_repository(self, name: str, **kwargs) -> str:
"""Create GitHub repository"""
cmd = [
"gh", "repo", "create",
f"{self.org}/{name}",
"--public" if kwargs.get("public", True) else "--private"
]
if description := kwargs.get("description"):
cmd.extend(["--description", description])
subprocess.run(cmd, check=True)
return f"{self.org}/{name}"
def trigger_workflow(self, repo: str, workflow: str,
inputs: Dict[str, Any]) -> str:
"""Trigger GitHub Actions workflow"""
# Build input args
input_args = []
for key, value in inputs.items():
input_args.extend(["-f", f"{key}={value}"])
# Trigger workflow
cmd = [
"gh", "workflow", "run", workflow,
"--repo", repo
] + input_args
subprocess.run(cmd, check=True)
# Get the run ID
runs_data = self._run_gh("run", "list", "--repo", repo, "--limit", "1")
return str(runs_data[0]["databaseId"])
def get_workflow_status(self, run_id: str) -> WorkflowStatus:
"""Get workflow run status"""
result = self._run_gh("run", "view", run_id)
status = result.get("status", "pending")
# Map GitHub status to our enum
status_map = {
"queued": WorkflowStatus.PENDING,
"in_progress": WorkflowStatus.RUNNING,
"completed": WorkflowStatus.SUCCESS if result.get("conclusion") == "success" else WorkflowStatus.FAILURE,
"cancelled": WorkflowStatus.CANCELLED
}
return status_map.get(status, WorkflowStatus.PENDING)
def get_workflow_result(self, run_id: str) -> WorkflowResult:
"""Get complete workflow results"""
run_data = self._run_gh("run", "view", run_id)
# Calculate duration
duration = 0.0
if run_data.get("createdAt") and run_data.get("updatedAt"):
from datetime import datetime
created = datetime.fromisoformat(run_data["createdAt"].replace('Z', '+00:00'))
updated = datetime.fromisoformat(run_data["updatedAt"].replace('Z', '+00:00'))
duration = (updated - created).total_seconds()
# Get jobs information
jobs = {}
for job in run_data.get("jobs", []):
jobs[job["name"]] = {
"status": job.get("conclusion", "unknown"),
"duration": job.get("duration", 0),
"steps": len(job.get("steps", []))
}
return WorkflowResult(
status=self.get_workflow_status(run_id),
duration=duration,
jobs=jobs,
artifacts=[], # Would need additional API call
logs="", # Would need additional API call
metadata={
"run_number": run_data.get("runNumber"),
"workflow_name": run_data.get("workflowName"),
"event": run_data.get("event"),
"url": run_data.get("url")
}
)
def create_pull_request(self, repo: str, title: str,
body: str, **kwargs) -> str:
"""Create GitHub pull request"""
cmd = [
"gh", "pr", "create",
"--repo", repo,
"--title", title,
"--body", body
]
if base := kwargs.get("base"):
cmd.extend(["--base", base])
if head := kwargs.get("head"):
cmd.extend(["--head", head])
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
# Extract PR number from URL
pr_url = result.stdout.strip()
pr_number = pr_url.split('/')[-1]
return pr_number
Plugin System¶
import pkg_resources
from typing import Dict, Type, Any
class ProviderRegistry:
"""Registry for CI providers with plugin discovery"""
_providers: Dict[str, Type[CIProvider]] = {}
@classmethod
def register(cls, name: str, provider: Type[CIProvider]):
"""Register a new provider"""
cls._providers[name] = provider
@classmethod
def get(cls, name: str) -> Type[CIProvider]:
"""Get a provider by name"""
if name not in cls._providers:
cls._load_plugin(name)
return cls._providers[name]
@classmethod
def list_providers(cls) -> List[str]:
"""List all available providers"""
cls._discover_plugins()
return list(cls._providers.keys())
@classmethod
def _load_plugin(cls, name: str):
"""Dynamically load provider plugin"""
for entry_point in pkg_resources.iter_entry_points('ci_providers'):
if entry_point.name == name:
provider_class = entry_point.load()
cls.register(name, provider_class)
return
raise ValueError(f"Provider {name} not found")
@classmethod
def _discover_plugins(cls):
"""Discover and register all available providers"""
for entry_point in pkg_resources.iter_entry_points('ci_providers'):
if entry_point.name not in cls._providers:
try:
provider_class = entry_point.load()
cls.register(entry_point.name, provider_class)
except Exception as e:
print(f"Failed to load provider {entry_point.name}: {e}")
# Auto-register built-in providers
ProviderRegistry.register("github", GitHubCLIProvider)
# Example plugin registration in setup.py:
"""
setup(
name="ci-orchestrator-gitlab",
entry_points={
'ci_providers': [
'gitlab = ci_orchestrator_gitlab:GitLabProvider',
],
},
)
"""
class TestPlugin(ABC):
"""Base class for test plugins"""
@abstractmethod
def test(self, scenario: Dict, provider: CIProvider) -> Dict:
"""Run plugin-specific tests"""
pass
class PerformanceTestPlugin(TestPlugin):
"""Plugin for performance testing"""
def test(self, scenario: Dict, provider: CIProvider) -> Dict:
"""Run performance-specific tests"""
# Measure performance metrics
start_time = time.time()
# Run the test scenario
run_id = provider.trigger_workflow(
scenario['repo'],
scenario['workflow'],
scenario.get('inputs', {})
)
# Wait for completion and measure
result = provider.get_workflow_result(run_id)
return {
'performance': {
'total_duration': result.duration,
'queue_time': start_time,
'jobs_count': len(result.jobs),
'artifacts_count': len(result.artifacts),
'efficiency_score': self._calculate_efficiency(result)
}
}
def _calculate_efficiency(self, result: WorkflowResult) -> float:
"""Calculate workflow efficiency score"""
# Example efficiency calculation
if result.duration == 0:
return 0.0
base_score = 100.0
# Penalize for long duration (>10 minutes)
if result.duration > 600:
base_score -= (result.duration - 600) / 60 * 5
# Bonus for successful completion
if result.status == WorkflowStatus.SUCCESS:
base_score += 10
return max(0.0, min(100.0, base_score))
class SecurityTestPlugin(TestPlugin):
"""Plugin for security-focused testing"""
def test(self, scenario: Dict, provider: CIProvider) -> Dict:
"""Run security-specific tests"""
# Check for security scanning workflows
# Verify security action usage
# Validate secret handling
return {
'security': {
'has_security_scan': True,
'secrets_properly_masked': True,
'vulnerability_scan_passed': True,
'security_score': 85.5
}
}
GitHub CLI Extension Design¶
Extension Structure¶
# gh-extension-manifest.yml
name: ci-orchestrator
description: CI/CD testing orchestration for GitHub Actions
version: 1.0.0
author: provide.io llc
homepage: https://github.com/provide-io/ci-orchestrator
commands:
- name: test
description: Test workflows with various configurations
usage: gh ci test [workflow] [flags]
flags:
- name: matrix
description: JSON matrix configuration
type: string
- name: timeout
description: Timeout in seconds
type: int
default: 300
- name: provider
description: CI provider to use
type: string
default: github
- name: compare
description: Compare two workflow implementations
usage: gh ci compare [old-workflow] [new-workflow]
flags:
- name: metrics
description: Metrics to compare (duration,success_rate,coverage)
type: string
default: duration,success_rate
- name: migrate
description: Migrate workflows to shared actions
usage: gh ci migrate [workflow-file] [flags]
flags:
- name: strategy
description: Migration strategy
type: string
choices: [reusable, actions, hybrid]
default: reusable
- name: output
description: Output file for migrated workflow
type: string
- name: validate
description: Validate workflow configuration
usage: gh ci validate [config-file]
- name: report
description: Generate comprehensive test report
usage: gh ci report [test-results-file]
dependencies:
- gh: ">=2.0.0"
- python: ">=3.11"
- packages:
- ci-orchestrator>=1.0.0
- click>=8.0.0
- rich>=12.0.0
CLI Implementation¶
#!/usr/bin/env python3
"""
GitHub CLI extension for CI orchestration
"""
import click
import json
import yaml
import sys
from pathlib import Path
from rich.console import Console
from rich.table import Table
from rich.progress import Progress
from ci_orchestrator import CITestOrchestrator, ProviderRegistry
console = Console()
@click.group()
@click.option('--provider', default='github', help='CI provider to use')
@click.option('--org', default='provide-io', help='Organization name')
@click.option('--config', type=click.Path(), help='Configuration file')
@click.pass_context
def cli(ctx, provider, org, config):
"""CI/CD testing orchestration for GitHub Actions"""
ctx.ensure_object(dict)
# Initialize provider
try:
provider_class = ProviderRegistry.get(provider)
ctx.obj['provider'] = provider_class(org=org)
ctx.obj['orchestrator'] = CITestOrchestrator(ctx.obj['provider'])
except Exception as e:
console.print(f"[red]Failed to initialize provider {provider}: {e}[/red]")
sys.exit(1)
# Load configuration if provided
if config and Path(config).exists():
with open(config) as f:
ctx.obj['config'] = yaml.safe_load(f)
else:
ctx.obj['config'] = {}
@cli.command()
@click.argument('workflow')
@click.option('--matrix', type=str, help='JSON matrix configuration')
@click.option('--timeout', type=int, default=300, help='Timeout in seconds')
@click.option('--repo', help='Repository name (org/repo)')
@click.pass_context
def test(ctx, workflow, matrix, timeout, repo):
"""Test workflow with various configurations"""
orchestrator = ctx.obj['orchestrator']
# Parse matrix if provided
matrix_config = {}
if matrix:
try:
matrix_config = json.loads(matrix)
except json.JSONDecodeError as e:
console.print(f"[red]Invalid matrix JSON: {e}[/red]")
sys.exit(1)
# Determine repository
if not repo:
# Try to detect from current directory
repo = detect_current_repo()
if not repo:
console.print("[red]No repository specified and none detected[/red]")
sys.exit(1)
console.print(f"[blue]Testing workflow: {workflow} in {repo}[/blue]")
# Create test scenarios
scenarios = []
if matrix_config:
# Generate scenarios from matrix
scenarios = generate_matrix_scenarios(workflow, matrix_config)
else:
# Single scenario
scenarios = [{'workflow': workflow, 'repo': repo}]
# Run tests with progress bar
results = {}
with Progress() as progress:
task = progress.add_task("Running tests...", total=len(scenarios))
for i, scenario in enumerate(scenarios):
scenario_name = f"test_{i}"
if matrix_config:
scenario_name = generate_scenario_name(scenario)
try:
result = orchestrator.test_scenario(scenario)
results[scenario_name] = result
status = "✅" if result.status.value == "success" else "❌"
console.print(f"{status} {scenario_name}: {result.status.value} ({result.duration:.1f}s)")
except Exception as e:
console.print(f"❌ {scenario_name}: Failed - {e}")
results[scenario_name] = {"error": str(e)}
progress.advance(task)
# Display summary
display_test_summary(results)
# Save results
output_file = f"ci-test-results-{workflow.replace('/', '-')}.json"
with open(output_file, 'w') as f:
json.dump(results, f, indent=2, default=str)
console.print(f"[green]Results saved to {output_file}[/green]")
@cli.command()
@click.argument('old_workflow')
@click.argument('new_workflow')
@click.option('--metrics', default='duration,success_rate', help='Metrics to compare')
@click.option('--runs', type=int, default=3, help='Number of test runs')
@click.pass_context
def compare(ctx, old_workflow, new_workflow, metrics, runs):
"""Compare two workflow implementations"""
orchestrator = ctx.obj['orchestrator']
console.print(f"[blue]Comparing workflows:[/blue]")
console.print(f" Old: {old_workflow}")
console.print(f" New: {new_workflow}")
console.print(f" Runs: {runs}")
# Run multiple tests for statistical significance
old_results = []
new_results = []
with Progress() as progress:
task = progress.add_task("Running comparison tests...", total=runs * 2)
for run in range(runs):
# Test old workflow
old_result = orchestrator.test_scenario({'workflow': old_workflow})
old_results.append(old_result)
progress.advance(task)
# Test new workflow
new_result = orchestrator.test_scenario({'workflow': new_workflow})
new_results.append(new_result)
progress.advance(task)
# Calculate comparison metrics
comparison = calculate_comparison_metrics(old_results, new_results, metrics.split(','))
# Display comparison table
display_comparison_table(comparison)
# Save comparison report
with open('workflow-comparison.json', 'w') as f:
json.dump(comparison, f, indent=2, default=str)
console.print("[green]Comparison complete. Report saved to workflow-comparison.json[/green]")
@cli.command()
@click.argument('workflow_file')
@click.option('--strategy', type=click.Choice(['reusable', 'actions', 'hybrid']),
default='reusable', help='Migration strategy')
@click.option('--output', help='Output file for migrated workflow')
@click.pass_context
def migrate(ctx, workflow_file, strategy, output):
"""Migrate workflow to shared actions"""
from ci_orchestrator.migration import WorkflowMigrator
if not Path(workflow_file).exists():
console.print(f"[red]Workflow file not found: {workflow_file}[/red]")
sys.exit(1)
console.print(f"[blue]Migrating {workflow_file} using {strategy} strategy[/blue]")
migrator = WorkflowMigrator()
try:
# Generate migrated workflow
migrated_content = migrator.migrate_workflow(workflow_file, strategy)
# Determine output file
if not output:
output = f"{workflow_file}.migrated.yml"
# Write migrated workflow
with open(output, 'w') as f:
f.write(migrated_content)
console.print(f"[green]✅ Migration complete. Saved to {output}[/green]")
# Show diff preview
console.print("\n[yellow]Preview of changes:[/yellow]")
show_workflow_diff(workflow_file, output)
except Exception as e:
console.print(f"[red]Migration failed: {e}[/red]")
sys.exit(1)
@cli.command()
@click.argument('config_file')
@click.pass_context
def validate(ctx, config_file):
"""Validate workflow configuration"""
if not Path(config_file).exists():
console.print(f"[red]Configuration file not found: {config_file}[/red]")
sys.exit(1)
try:
with open(config_file) as f:
config = yaml.safe_load(f)
# Validate configuration structure
errors = validate_config_structure(config)
if errors:
console.print("[red]Configuration validation failed:[/red]")
for error in errors:
console.print(f" ❌ {error}")
sys.exit(1)
else:
console.print("[green]✅ Configuration is valid[/green]")
except yaml.YAMLError as e:
console.print(f"[red]Invalid YAML: {e}[/red]")
sys.exit(1)
@cli.command()
@click.argument('results_file')
@click.option('--format', type=click.Choice(['html', 'markdown', 'json']),
default='markdown', help='Report format')
@click.option('--output', help='Output file for report')
def report(results_file, format, output):
"""Generate comprehensive test report"""
if not Path(results_file).exists():
console.print(f"[red]Results file not found: {results_file}[/red]")
sys.exit(1)
try:
with open(results_file) as f:
results = json.load(f)
# Generate report based on format
if format == 'markdown':
report = generate_markdown_report(results)
ext = '.md'
elif format == 'html':
report = generate_html_report(results)
ext = '.html'
else:
report = json.dumps(results, indent=2)
ext = '.json'
# Determine output file
if not output:
output = f"ci-test-report{ext}"
# Write report
with open(output, 'w') as f:
f.write(report)
console.print(f"[green]Report generated: {output}[/green]")
except Exception as e:
console.print(f"[red]Report generation failed: {e}[/red]")
sys.exit(1)
# Helper functions
def detect_current_repo() -> str:
"""Detect repository from current directory"""
import subprocess
try:
result = subprocess.run(['git', 'remote', 'get-url', 'origin'],
capture_output=True, text=True, check=True)
url = result.stdout.strip()
# Extract org/repo from URL
if 'github.com' in url:
if url.startswith('git@'):
# SSH format: [email protected]:org/repo.git
repo_part = url.split(':')[1].replace('.git', '')
else:
# HTTPS format: https://github.com/org/repo.git
repo_part = url.split('github.com/')[1].replace('.git', '')
return repo_part
except subprocess.CalledProcessError:
pass
return None
def generate_matrix_scenarios(workflow: str, matrix: Dict) -> List[Dict]:
"""Generate test scenarios from matrix configuration"""
import itertools
# Get all combinations
keys = list(matrix.keys())
values = [matrix[key] if isinstance(matrix[key], list) else [matrix[key]]
for key in keys]
scenarios = []
for combination in itertools.product(*values):
scenario = {
'workflow': workflow,
'inputs': dict(zip(keys, combination))
}
scenarios.append(scenario)
return scenarios
def generate_scenario_name(scenario: Dict) -> str:
"""Generate readable name for scenario"""
inputs = scenario.get('inputs', {})
if not inputs:
return 'default'
parts = [f"{k}={v}" for k, v in inputs.items()]
return '_'.join(parts)
def display_test_summary(results: Dict):
"""Display test results summary table"""
table = Table(title="Test Results Summary")
table.add_column("Test", style="cyan")
table.add_column("Status", style="green")
table.add_column("Duration", style="yellow")
table.add_column("Details", style="blue")
for test_name, result in results.items():
if isinstance(result, dict) and 'error' in result:
table.add_row(test_name, "❌ Error", "-", result['error'])
else:
status = "✅ Success" if result.status.value == "success" else "❌ Failed"
table.add_row(
test_name,
status,
f"{result.duration:.1f}s",
f"{len(result.jobs)} jobs, {len(result.artifacts)} artifacts"
)
console.print(table)
def calculate_comparison_metrics(old_results: List, new_results: List, metrics: List[str]) -> Dict:
"""Calculate comparison metrics between workflow runs"""
comparison = {}
# Duration comparison
if 'duration' in metrics:
old_durations = [r.duration for r in old_results]
new_durations = [r.duration for r in new_results]
comparison['duration'] = {
'old_avg': sum(old_durations) / len(old_durations),
'new_avg': sum(new_durations) / len(new_durations),
'improvement': (sum(old_durations) - sum(new_durations)) / sum(old_durations) * 100
}
# Success rate comparison
if 'success_rate' in metrics:
old_successes = sum(1 for r in old_results if r.status.value == "success")
new_successes = sum(1 for r in new_results if r.status.value == "success")
comparison['success_rate'] = {
'old_rate': old_successes / len(old_results) * 100,
'new_rate': new_successes / len(new_results) * 100,
'improvement': (new_successes - old_successes) / len(old_results) * 100
}
return comparison
def display_comparison_table(comparison: Dict):
"""Display workflow comparison table"""
table = Table(title="Workflow Comparison")
table.add_column("Metric", style="cyan")
table.add_column("Old", style="yellow")
table.add_column("New", style="green")
table.add_column("Change", style="blue")
for metric, data in comparison.items():
if metric == 'duration':
old_val = f"{data['old_avg']:.1f}s"
new_val = f"{data['new_avg']:.1f}s"
change = f"{data['improvement']:+.1f}%"
elif metric == 'success_rate':
old_val = f"{data['old_rate']:.1f}%"
new_val = f"{data['new_rate']:.1f}%"
change = f"{data['improvement']:+.1f}%"
else:
old_val = str(data.get('old', '-'))
new_val = str(data.get('new', '-'))
change = str(data.get('change', '-'))
table.add_row(metric.title(), old_val, new_val, change)
console.print(table)
if __name__ == '__main__':
cli()
Usage Examples¶
# Install the extension
gh extension install provide-io/gh-ci-orchestrator
# Test a workflow with matrix configuration
gh ci test .github/workflows/ci.yml \
--matrix '{"python-version": ["3.11", "3.12", "3.13"], "os": ["ubuntu-latest", "macos-latest"]}'
# Compare two workflow implementations
gh ci compare .github/workflows/old-ci.yml .github/workflows/new-ci.yml \
--metrics duration,success_rate,coverage --runs 5
# Migrate existing workflow to shared actions
gh ci migrate .github/workflows/ci.yml --strategy reusable --output ci-new.yml
# Validate workflow configuration
gh ci validate .github/ci-testing.yml
# Generate comprehensive test report
gh ci report ci-test-results.json --format html --output test-report.html
# Use with configuration file
gh ci test --config .github/ci-testing.yml
# Test specific repository
gh ci test .github/workflows/ci.yml --repo provide-io/my-project
Common Patterns Analysis¶
This design follows well-established industry patterns that are considered best practices in modern software engineering:
1. Provider/Strategy Pattern ✅ Very Common¶
Examples in Popular Tools:
# Django - Database backends
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql', # Swappable
}
}
# Terraform - Provider plugins
provider "aws" { } # Or azure, google, etc.
provider "azure" { }
provider "google" { }
# Kubernetes - Cloud providers
--cloud-provider=aws # Or gce, azure, openstack
2. Plugin Architecture ✅ Extremely Common¶
Used by most extensible tools:
- GitHub CLI:
gh extension install/create - VSCode: Extension marketplace via Extension API
- Jenkins: Massive plugin ecosystem
- Pytest: Plugin system via setuptools entry points
- Ansible: Custom modules and plugins
- Vim/Neovim: Plugin managers (vim-plug, packer)
3. Protocol/Interface Pattern ✅ Industry Standard¶
# Python's Protocol (PEP 544) - standard practice
from typing import Protocol
# Examples in standard library:
from collections.abc import Sequence, Mapping
# Popular frameworks:
# - SQLAlchemy: Dialect protocol for databases
# - Flask: WSGI protocol
# - AsyncIO: Protocol classes for networking
4. CLI Extension Pattern ✅ Growing Trend¶
# Modern CLIs with extension support:
gh extension install dlvhdr/gh-dash # GitHub CLI
aws configure add-model # AWS CLI
az extension add --name azure-devops # Azure CLI
kubectl plugin list # Kubernetes
git-flow # Git extensions
5. Real-World Examples Similar to Our Design¶
Pulumi - Multi-Cloud Infrastructure¶
# Exactly like our provider pattern
import pulumi_aws as aws
import pulumi_azure as azure
import pulumi_gcp as gcp
# Provider abstraction - same interface, different implementations
provider = aws.Provider("prod", region="us-west-2")
# or
provider = azure.Provider("prod", location="West US")
Apache Libcloud - Cloud Abstraction¶
from libcloud.compute.types import Provider
from libcloud.compute.providers import get_driver
# Same pattern - abstract interface, multiple providers
cls = get_driver(Provider.AMAZON_EC2) # or AZURE_ARM, GCE
driver = cls('access_key', 'secret_key')
OpenTelemetry - Observability¶
# Provider pattern for different backends
from opentelemetry import trace
from opentelemetry.exporter.datadog import DatadogExporter
from opentelemetry.exporter.jaeger import JaegerExporter
# Swap providers without changing core logic
exporter = DatadogExporter() # or JaegerExporter()
6. Industry Standards Using This Pattern¶
- JDBC/ODBC: Database connectivity abstraction
- PSR Standards (PHP): Interfaces for logging, caching, HTTP
- WSGI/ASGI (Python): Web server interfaces
- OpenAPI/Swagger: API specification abstraction
- Container Runtime Interface (CRI): Kubernetes container abstraction
- Cloud Native Computing Foundation (CNCF): Many projects use provider patterns
7. CI/CD Space Specifically¶
Our approach aligns with existing successful tools:
- Tekton: Provider-agnostic CI/CD pipelines
- Argo Workflows: Abstract workflow definitions
- Jenkins X: GitOps abstraction over providers
- Spinnaker: Multi-cloud deployment abstraction
- CircleCI Orbs: Reusable configuration packages
- GitLab CI Components: Reusable pipeline components
Why This Pattern is Ubiquitous¶
- Separation of Concerns: Clear interface vs implementation boundaries
- Flexibility: Swap providers without changing core logic
- Testability: Easy to mock providers for testing
- Extensibility: Add new providers without modifying existing code
- Vendor Independence: Avoid platform lock-in
- Maintainability: Changes to one provider don't affect others
Assessment¶
Our design is: - ✅ Following established patterns used by major tools - ✅ Industry best practice recommended by software architecture experts - ✅ Similar to successful tools like Pulumi, Terraform, OpenTelemetry - ✅ Properly abstracted with clear separation of concerns - ✅ Extension-friendly following modern CLI patterns
This is not just common—it's the recommended approach for building extensible, maintainable tools that need to work across multiple providers or platforms. The pattern has proven successful across decades of software development.
Integration Ecosystem¶
Overview¶
The CI orchestrator abstraction layer can be integrated into multiple environments and tools, creating a comprehensive ecosystem for CI/CD testing and optimization.
Integration Architecture Diagram¶
┌─────────────────────────────┐
│ CI Orchestrator Core │
│ (Provider Abstraction) │
└─────────────┬───────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
CLI Layer API Layer Service Layer
│ │ │
┌───────┴────────┐ ┌────────┴────────┐ ┌─────────┴────────┐
│ gh extension │ │ Python Package │ │ GitHub App │
│ npm package │ │ REST API │ │ K8s Operator │
│ brew formula │ │ GraphQL API │ │ Docker Image │
└────────────────┘ └─────────────────┘ └──────────────────┘
│ │ │
└──────────────────────────┼──────────────────────────┘
│
┌────────┴─────────┐
│ Integrations │
├──────────────────┤
│ • VS Code │
│ • IntelliJ │
│ • Slack/Discord │
│ • Terraform │
│ • Pulumi │
│ • Copilot │
│ • Dependabot │
└──────────────────┘
1. Direct GitHub CLI Extension (Immediate Path)¶
# Installation
gh extension install provide-io/gh-ci-orchestrator
# New commands added to gh CLI
gh ci test --matrix python=[3.11,3.12,3.13]
gh ci migrate --from .github/workflows/old.yml
gh ci compare --baseline main --candidate feature-branch
gh ci validate --config .github/ci-testing.yml
gh ci report --format html
Benefits: - Natural extension of existing developer workflow - Leverages existing gh CLI authentication - Consistent with other GitHub tooling
2. GitHub Actions Marketplace (Action Wrapper)¶
# .github/workflows/ci-orchestration.yml
name: CI with Orchestration
on: [push, pull_request]
jobs:
orchestrated-test:
runs-on: ubuntu-latest
steps:
- uses: provide-io/ci-orchestrator-action@v1
with:
provider: github
test-suite: comprehensive
compare-with: ${{ github.base_ref }}
matrix: |
python-version: [3.11, 3.12, 3.13]
os: [ubuntu-latest, macos-latest]
auto-migrate:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: provide-io/ci-orchestrator-action@v1
with:
mode: migrate
strategy: reusable
auto-pr: true
3. VS Code Extension (IDE Integration)¶
// Extension adds CI testing capabilities to VS Code
export function activate(context: vscode.ExtensionContext) {
// Register commands
const testWorkflow = vscode.commands.registerCommand(
'ci-orchestrator.testWorkflow',
async () => {
const workflowFile = await vscode.window.showOpenDialog({
filters: { 'YAML': ['yml', 'yaml'] }
});
if (workflowFile) {
const panel = vscode.window.createWebviewPanel(
'ci-test-results',
'CI Test Results',
vscode.ViewColumn.Two,
{ enableScripts: true }
);
// Run test and display results
const results = await runWorkflowTest(workflowFile[0].fsPath);
panel.webview.html = generateResultsHTML(results);
}
}
);
// Add tree view for workflow monitoring
const provider = new WorkflowTestProvider();
vscode.window.createTreeView('ci-orchestrator.workflowTests', {
treeDataProvider: provider
});
context.subscriptions.push(testWorkflow);
}
// Tree view showing test results
class WorkflowTestProvider implements vscode.TreeDataProvider<TestResult> {
// Implementation for showing test results in sidebar
}
Features: - Right-click context menu on workflow files - Integrated test results viewer - Real-time workflow monitoring - Auto-suggestions for improvements
4. GitHub Apps Platform (SaaS Integration)¶
# GitHub App providing automated CI orchestration
from github_app import App
from ci_orchestrator import CIOrchestrator
app = App(app_id=123456, private_key=key)
@app.on("workflow_run.completed")
def on_workflow_complete(event):
"""Automatically analyze completed workflows"""
orchestrator = CIOrchestrator(provider=GitHubProvider(event.token))
# Compare with baseline performance
comparison = orchestrator.compare_with_baseline(
run_id=event.workflow_run.id,
baseline_branch="main"
)
# Post results as PR comment if significant difference
if comparison.has_significant_change():
event.create_comment(comparison.to_markdown())
@app.on("pull_request.opened")
def on_pr_opened(event):
"""Auto-suggest workflow improvements"""
orchestrator = CIOrchestrator(provider=GitHubProvider(event.token))
# Analyze PR for workflow changes
suggestions = orchestrator.analyze_workflow_changes(event.pull_request)
if suggestions:
event.create_review_comment(
body=suggestions.to_markdown(),
path=".github/workflows/ci.yml"
)
@app.on("push")
def on_push(event):
"""Trigger comprehensive testing on main branch"""
if event.ref == "refs/heads/main":
orchestrator = CIOrchestrator(provider=GitHubProvider(event.token))
# Run nightly comprehensive test suite
orchestrator.schedule_comprehensive_test(event.repository.full_name)
5. Python Package (Library Integration)¶
# pip install ci-orchestrator
from ci_orchestrator import CIOrchestrator, GitHubProvider
# Use in existing Python automation tools
def validate_ci_pipeline():
"""Integrate into existing Python tools/scripts"""
orch = CIOrchestrator(GitHubProvider(token=os.getenv("GH_TOKEN")))
# Test current CI configuration
results = orch.test_matrix({
"workflow": ".github/workflows/ci.yml",
"matrix": {
"os": ["ubuntu-latest", "windows-latest"],
"python": ["3.11", "3.12"]
}
})
# Fail deployment if CI tests don't pass
assert all(r.status == "success" for r in results)
return results
# Integration with existing tools
class CIPipelineValidator:
"""Integrate into CI/CD deployment pipelines"""
def __init__(self):
self.orchestrator = CIOrchestrator(GitHubProvider())
def pre_deployment_check(self, repo: str) -> bool:
"""Run before deploying to production"""
results = self.orchestrator.run_smoke_tests(repo)
return all(r.status == "success" for r in results)
def post_deployment_validation(self, repo: str) -> Dict:
"""Validate CI still works after deployment"""
return self.orchestrator.validate_workflows(repo)
6. Container/Kubernetes Operator (Cloud Native)¶
# Kubernetes CRD for CI orchestration
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: workflowtests.ci.provide.io
spec:
group: ci.provide.io
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
provider:
type: string
enum: [github, gitlab, jenkins]
repository:
type: string
schedule:
type: string
tests:
type: array
items:
type: object
properties:
name:
type: string
workflow:
type: string
matrix:
type: object
---
# Example WorkflowTest resource
apiVersion: ci.provide.io/v1
kind: WorkflowTest
metadata:
name: nightly-comprehensive-test
namespace: ci-testing
spec:
provider: github
repository: provide-io/my-app
schedule: "0 2 * * *" # 2 AM daily
tests:
- name: matrix-test
workflow: ci.yml
matrix:
python: [3.11, 3.12, 3.13]
os: [ubuntu-latest, macos-latest]
- name: performance-baseline
workflow: benchmark.yml
compare: true
baseline_branch: main
notifications:
slack:
webhook: https://hooks.slack.com/...
channel: "#ci-alerts"
validation:
min_coverage: 80
max_duration: 300
required_checks: [security, quality]
# Kubernetes Operator implementation
import kopf
from kubernetes import client, config
from ci_orchestrator import CIOrchestrator
@kopf.on.create('ci.provide.io', 'v1', 'workflowtests')
def create_workflow_test(body, **kwargs):
"""Handle WorkflowTest resource creation"""
spec = body['spec']
# Initialize orchestrator with appropriate provider
provider_class = get_provider_class(spec['provider'])
orchestrator = CIOrchestrator(provider_class())
# Schedule test execution
schedule_test_execution(orchestrator, spec)
def schedule_test_execution(orchestrator, spec):
"""Schedule recurring test execution"""
# Implementation for scheduling tests based on cron expression
pass
7. Terraform Provider (Infrastructure as Code)¶
# Terraform provider for CI orchestration
terraform {
required_providers {
ci_orchestrator = {
source = "provide-io/ci-orchestrator"
version = "~> 1.0"
}
}
}
provider "ci_orchestrator" {
github_token = var.github_token
organization = "provide-io"
}
# Define CI test suite as infrastructure
resource "ci_orchestrator_test_suite" "main" {
repository = "provide-io/my-app"
scenario {
name = "comprehensive"
workflow = ".github/workflows/ci.yml"
matrix {
os = ["ubuntu-latest", "macos-latest"]
python = ["3.11", "3.12"]
}
}
scenario {
name = "security"
workflow = ".github/workflows/security.yml"
schedule = "0 6 * * *" # Daily at 6 AM
}
validation {
min_coverage = 80
max_duration = 300
required_checks = ["security", "quality"]
performance_baseline {
compare_with = "main"
max_regression = 20 # Max 20% performance regression
}
}
notifications {
slack_webhook = var.slack_webhook
email_alerts = ["[email protected]"]
}
}
# Monitor CI health across multiple repositories
resource "ci_orchestrator_monitor" "org_wide" {
repositories = data.github_repositories.provide_io.names
health_check {
frequency = "daily"
metrics = ["success_rate", "duration", "coverage"]
}
alerts {
success_rate_threshold = 95
duration_threshold = 600 # 10 minutes
coverage_threshold = 80
}
}
# Output CI health dashboard URL
output "ci_dashboard_url" {
value = ci_orchestrator_monitor.org_wide.dashboard_url
}
8. Developer Tool Integrations¶
Pre-commit Hooks¶
# .pre-commit-config.yaml
repos:
- repo: https://github.com/provide-io/ci-orchestrator
rev: v1.0.0
hooks:
- id: validate-workflow
name: Validate GitHub workflows
entry: ci-orchestrator validate
language: python
files: '^\.github/workflows/.*\.ya?ml$'
- id: check-migration-ready
name: Check if workflows can be migrated
entry: ci-orchestrator check-migration
language: python
files: '^\.github/workflows/.*\.ya?ml$'
Make/Task Integration¶
# Makefile
.PHONY: test-ci migrate-ci validate-ci
test-ci:
@echo "Running CI workflow tests..."
ci-orchestrator test --config .github/ci-testing.yml
migrate-ci:
@echo "Migrating workflows to shared actions..."
ci-orchestrator migrate --all-workflows --strategy reusable
validate-ci:
@echo "Validating CI configuration..."
ci-orchestrator validate .github/ci-testing.yml
ci-report:
@echo "Generating CI test report..."
ci-orchestrator report --format html --output ci-report.html
9. ChatOps Integration (Slack/Discord/Teams)¶
# Slack bot integration
from slack_bolt import App
from ci_orchestrator import CIOrchestrator
app = App(token=os.environ["SLACK_BOT_TOKEN"])
@app.command("/ci-test")
def handle_ci_test(ack, command, client):
"""Handle /ci-test command"""
ack()
# Parse command: /ci-test repo:my-app matrix:full
args = parse_command_args(command['text'])
orchestrator = CIOrchestrator(GitHubProvider())
# Start test asynchronously
client.chat_postMessage(
channel=command['channel_id'],
text=f"🚀 Starting CI test for {args.get('repo', 'current')} repository..."
)
try:
result = orchestrator.test_workflow(
repo=args.get('repo'),
matrix=args.get('matrix', 'default')
)
# Post results
client.chat_postMessage(
channel=command['channel_id'],
blocks=format_test_results_for_slack(result)
)
except Exception as e:
client.chat_postMessage(
channel=command['channel_id'],
text=f"❌ CI test failed: {str(e)}"
)
@app.command("/ci-compare")
def handle_ci_compare(ack, command, client):
"""Compare workflow performance"""
ack()
# Implementation for workflow comparison
# /ci-compare old:.github/workflows/old.yml new:.github/workflows/new.yml
pass
@app.command("/ci-migrate")
def handle_ci_migrate(ack, command, client):
"""Migrate workflows to shared actions"""
ack()
# Implementation for automated migration
# /ci-migrate repo:my-app strategy:reusable
pass
# Discord bot integration
import discord
from discord.ext import commands
bot = commands.Bot(command_prefix='!ci-')
@bot.command(name='test')
async def ci_test(ctx, *, args):
"""!ci-test repo matrix=full"""
orchestrator = CIOrchestrator(GitHubProvider())
embed = discord.Embed(title="CI Test Started", color=0x00ff00)
message = await ctx.send(embed=embed)
try:
result = orchestrator.test_workflow(repo=args)
embed = discord.Embed(
title="CI Test Complete",
color=0x00ff00 if result.success else 0xff0000
)
embed.add_field(name="Duration", value=f"{result.duration:.1f}s")
embed.add_field(name="Status", value=result.status)
await message.edit(embed=embed)
except Exception as e:
embed = discord.Embed(title="CI Test Failed", color=0xff0000)
embed.add_field(name="Error", value=str(e))
await message.edit(embed=embed)
10. GitHub Copilot Extension (AI Integration)¶
// GitHub Copilot Chat extension
export interface CICopilotExtension {
participants: ChatParticipant[];
}
const ciOrchestrator: ChatParticipant = {
id: 'ci-orchestrator',
name: 'CI Helper',
description: 'AI assistant for CI/CD workflow optimization',
commands: [
{
name: 'test',
description: 'Test workflow configurations'
},
{
name: 'optimize',
description: 'Suggest workflow optimizations'
},
{
name: 'migrate',
description: 'Help migrate to shared actions'
},
{
name: 'analyze',
description: 'Analyze workflow performance'
}
],
async handler(request: ChatRequest): Promise<ChatResponse> {
const { command, prompt } = request;
switch (command) {
case 'test':
return await handleTestCommand(prompt);
case 'optimize':
return await handleOptimizeCommand(prompt);
case 'migrate':
return await handleMigrateCommand(prompt);
case 'analyze':
return await handleAnalyzeCommand(prompt);
default:
return { content: 'Unknown command. Available: test, optimize, migrate, analyze' };
}
}
};
async function handleTestCommand(prompt: string): Promise<ChatResponse> {
// Parse natural language request
// "Test my CI workflow with Python 3.11 and 3.12"
const config = parseTestRequest(prompt);
// Generate test configuration
const testConfig = await generateTestConfig(config);
return {
content: `I'll help you test your CI workflow. Here's the configuration I generated:
\`\`\`yaml
${testConfig}
\`\`\`
Run this test with:
\`\`\`bash
gh ci test --config test-config.yml
\`\`\``,
suggestions: [
'Run the test',
'Modify configuration',
'Add more test scenarios'
]
};
}
async function handleOptimizeCommand(prompt: string): Promise<ChatResponse> {
// Analyze current workflow and suggest improvements
const workflowFile = await getCurrentWorkflowFile();
const analysis = await analyzeWorkflow(workflowFile);
const suggestions = analysis.optimizations.map(opt =>
`- **${opt.category}**: ${opt.description} (saves ~${opt.estimated_time}s)`
).join('\n');
return {
content: `I found several optimization opportunities:
${suggestions}
Would you like me to help implement any of these optimizations?`,
followUp: analysis.optimizations.map(opt => ({
title: `Implement ${opt.category} optimization`,
command: `implement-optimization ${opt.id}`
}))
};
}
Copilot Integration Features: - Natural language CI testing: "Test my workflow with Python 3.11 and 3.12" - Optimization suggestions: "How can I make my CI faster?" - Migration assistance: "Help me migrate to shared actions" - Performance analysis: "Why is my CI slow?"
Primary Integration Path (Recommended)¶
Based on developer workflow and adoption patterns, the most logical progression would be:
- Phase 1: Python package (
pip install ci-orchestrator) - Core functionality for automation scripts
-
Foundation for other integrations
-
Phase 2: GitHub CLI extension (
gh extension install) - Natural fit for developer workflow
-
Leverages existing authentication
-
Phase 3: GitHub Action (Marketplace)
- Enables automated testing in CI pipelines
-
Broader reach to GitHub users
-
Phase 4: GitHub App (automated checking)
- Automated PR analysis and suggestions
-
Organization-wide CI health monitoring
-
Phase 5: Enterprise integrations
- Kubernetes Operator for large-scale deployment
- Terraform Provider for infrastructure teams
- IDE extensions for developer productivity
This abstraction layer would essentially become middleware for CI/CD testing, sitting between: - Users/Tools (top layer): IDEs, CLIs, ChatOps, AI assistants - CI Providers (bottom layer): GitHub Actions, GitLab CI, Jenkins, etc.
The pattern is similar to successful abstraction layers like: - Terraform: Between users and cloud providers - Helm: Between users and Kubernetes - Docker Compose: Between users and container runtimes - OpenTelemetry: Between applications and observability backends
Implementation Roadmap¶
Phase 1: Foundation (Weeks 1-4)¶
Week 1: Core Abstraction Layer¶
- Provider Protocol Definition
- Define
CIProviderprotocol interface - Implement abstract
CITestOrchestratorbase class -
Create type definitions and data classes
-
GitHub Provider Implementation
- Implement
GitHubCLIProviderclass - Add authentication and validation
-
Create workflow triggering and monitoring
-
Basic Testing Framework
- Implement core test orchestration logic
- Add result collection and analysis
- Create basic reporting functionality
Week 2: Python Package Structure¶
- Package Organization
- Set up proper Python package structure
- Add setuptools configuration with entry points
-
Implement plugin discovery system
-
Core Actions Testing
- Create test scenarios for ci-tooling actions
- Implement matrix testing capabilities
-
Add performance measurement
-
Configuration System
- YAML-based configuration files
- Environment variable support
- Validation and schema definition
Week 3: CLI Interface¶
- Click-based CLI
- Implement main CLI commands (test, compare, migrate)
- Add rich console output and progress bars
-
Create comprehensive help system
-
Migration Tools
- Workflow analysis and parsing
- Migration strategy implementation
- Diff generation and preview
Week 4: Documentation & Testing¶
- Documentation
- API documentation with examples
- Usage guides and tutorials
-
Architecture documentation
-
Test Suite
- Unit tests for all components
- Integration tests with real GitHub repos
- CI/CD for the orchestrator itself
Phase 2: GitHub Integration (Weeks 5-8)¶
Week 5: GitHub CLI Extension¶
- Extension Development
- Create gh extension manifest
- Implement extension commands
-
Add authentication integration
-
Advanced Features
- Workflow comparison and analysis
- Performance benchmarking
- Automated suggestions
Week 6: GitHub Action¶
- Action Development
- Create composite action for orchestration
- Add input/output definitions
-
Implement workflow integration
-
Marketplace Preparation
- Action documentation and examples
- Icon and branding
- Security and best practices review
Week 7: GitHub App¶
- App Foundation
- GitHub App registration and setup
- Webhook handling infrastructure
-
Authentication and permissions
-
Automated Features
- PR analysis and commenting
- Workflow run monitoring
- Performance regression detection
Week 8: Integration Testing¶
- Real-world Testing
- Test with multiple provide-io repositories
- Performance validation and optimization
- User feedback collection and iteration
Phase 3: Advanced Features (Weeks 9-12)¶
Week 9: Plugin System¶
- Plugin Architecture
- Finalize plugin interface
- Create example plugins
-
Plugin discovery and loading
-
Built-in Plugins
- Performance testing plugin
- Security analysis plugin
- Coverage tracking plugin
Week 10: IDE Integration¶
- VS Code Extension
- Basic workflow testing integration
- Results visualization
-
Context menu integration
-
Language Server Protocol
- YAML workflow file validation
- Auto-completion for shared actions
- Inline diagnostics and suggestions
Week 11: Container & K8s¶
- Docker Image
- Containerized orchestrator
- Multi-architecture builds
-
Docker Hub publication
-
Kubernetes Operator
- CRD definitions
- Controller implementation
- Helm chart creation
Week 12: Infrastructure as Code¶
- Terraform Provider
- Provider framework setup
- Resource definitions
-
State management
-
Pulumi Support
- SDK generation
- Example configurations
- Integration testing
Phase 4: Enterprise & Scale (Weeks 13-16)¶
Week 13: SaaS Platform¶
- Web Dashboard
- React-based frontend
- Real-time workflow monitoring
-
Historical analysis and trends
-
API Gateway
- REST API for orchestration
- Authentication and rate limiting
- Multi-tenant support
Week 14: ChatOps Integration¶
- Slack Integration
- Bot commands for CI testing
- Interactive workflow management
-
Alert and notification system
-
Discord/Teams Support
- Multi-platform chat integration
- Consistent command interface
- Rich message formatting
Week 15: AI/ML Features¶
- GitHub Copilot Extension
- Natural language CI queries
- Optimization suggestions
-
Automated migration assistance
-
Predictive Analytics
- Failure prediction models
- Performance trend analysis
- Capacity planning insights
Week 16: Launch & Distribution¶
- Package Distribution
- PyPI publication
- npm package for Node.js tools
- Homebrew formula
-
Conda package
-
Documentation Site
- Comprehensive documentation portal
- Interactive tutorials
- Community resources
Success Metrics¶
Phase 1 (Foundation)¶
- Successfully test all provide-io/ci-tooling actions
- Migrate at least 3 provide-io repositories
- Achieve <30s test execution time for basic scenarios
- 90%+ test success rate
Phase 2 (GitHub Integration)¶
- GitHub CLI extension published
- GitHub Action in marketplace with >100 downloads
- GitHub App installed on provide-io organization
- Automated PR analysis functioning
Phase 3 (Advanced Features)¶
- 3+ community plugins developed
- VS Code extension with >1000 installs
- Kubernetes operator deployed in production
- Terraform provider published
Phase 4 (Enterprise)¶
- SaaS platform serving external customers
- GitHub Copilot extension approved
- Multi-provider support (GitLab, Jenkins)
- Open source community contributions
Risk Mitigation¶
Technical Risks¶
- API Rate Limits: Implement intelligent rate limiting and caching
- Authentication Complexity: Use existing CLI tools where possible
- Provider Differences: Maintain strict abstraction boundaries
- Performance: Parallel execution and optimization
Business Risks¶
- Adoption: Start with internal use at provide-io
- Competition: Focus on unique abstraction value
- Maintenance: Build strong test coverage and documentation
- Scope Creep: Maintain focus on core use cases
Resource Requirements¶
Development Team¶
- Lead Developer: Full-time for entire project
- Backend Developer: Focus on provider implementations
- Frontend Developer: Week 13+ for web dashboard
- DevOps Engineer: Phase 3+ for K8s and infrastructure
Infrastructure¶
- Development Environment: GitHub, PyPI, Docker Hub accounts
- Testing Infrastructure: Multiple GitHub repositories for testing
- CI/CD: GitHub Actions for project itself
- Documentation: GitHub Pages or dedicated hosting
Validation Checkpoints¶
End of Phase 1¶
- Demo: Live migration of pyvider-cty workflow
- Performance: Complete test suite runs in <5 minutes
- Quality: >95% test coverage, no critical security issues
End of Phase 2¶
- Demo: GitHub CLI extension workflow testing
- Adoption: 5+ provide-io repositories using shared actions
- Feedback: Positive developer experience feedback
End of Phase 3¶
- Demo: Complete IDE integration workflow
- Scale: Supporting 50+ workflows across 20+ repositories
- Community: External contributions and plugin development
End of Phase 4¶
- Demo: End-to-end enterprise workflow
- Business: Revenue-generating SaaS platform
- Impact: Measurable CI/CD efficiency improvements
This roadmap provides a structured approach to building the CI orchestration layer from core functionality through enterprise features, with clear milestones and success criteria at each phase.