# CI/CD Integration ## Overview Release Orchestrator integrates with CI/CD systems to: - Receive build completion notifications - Trigger additional pipelines during deployment - Create releases from CI artifacts - Report deployment status back to CI systems ## Integration Patterns ### Pattern 1: CI Triggers Release ``` CI TRIGGERS RELEASE ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ CI/CD │ │ Container │ │ Release │ │ System │ │ Registry │ │ Orchestrator │ └─────┬──────┘ └─────┬──────┘ └─────────┬──────────┘ │ │ │ │ Build & Push │ │ │─────────────────►│ │ │ │ │ │ │ Webhook: image pushed │ │─────────────────────►│ │ │ │ │ │ │ Create/Update │ │ │ Version Map │ │ │ │ │ │ Auto-create │ │ │ Release (if configured) │ │ │ │ API: Create Release (optional) │ │────────────────────────────────────────►│ │ │ │ │ │ │ Start Promotion │ │ │ Workflow │ │ │ ``` ### Pattern 2: Orchestrator Triggers CI ``` ORCHESTRATOR TRIGGERS CI ┌────────────────────┐ ┌────────────┐ ┌────────────┐ │ Release │ │ CI/CD │ │ Target │ │ Orchestrator │ │ System │ │ Systems │ └─────────┬──────────┘ └─────┬──────┘ └─────┬──────┘ │ │ │ │ Pre-deploy: Trigger │ │ │ Integration Tests │ │ │─────────────────────►│ │ │ │ │ │ │ Run Tests │ │ │─────────────────►│ │ │ │ │ Wait for completion │ │ │◄─────────────────────│ │ │ │ │ │ If passed: Deploy │ │ │─────────────────────────────────────────► │ │ │ ``` ### Pattern 3: Bidirectional Integration ``` BIDIRECTIONAL INTEGRATION ┌────────────┐ ┌────────────────────┐ │ CI/CD │◄───────────────────────►│ Release │ │ System │ │ Orchestrator │ └─────┬──────┘ └─────────┬──────────┘ │ │ │══════════════════════════════════════════│ │ Events (both directions) │ │══════════════════════════════════════════│ │ │ │ CI Events: │ │ - Pipeline completed │ │ - Tests passed/failed │ │ - Artifacts ready │ │ │ │ Orchestrator Events: │ │ - Deployment started │ │ - Deployment completed │ │ - Rollback initiated │ │ │ ``` ## CI/CD System Configuration ### GitLab CI Integration ```yaml # .gitlab-ci.yml stages: - build - push - release variables: STELLA_API_URL: https://stella.example.com/api/v1 COMPONENT_NAME: myapp build: stage: build script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . push: stage: push script: - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG rules: - if: $CI_COMMIT_TAG release: stage: release image: curlimages/curl:latest script: - | # Get image digest DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG | cut -d@ -f2) # Create release in Stella curl -X POST "$STELLA_API_URL/releases" \ -H "Authorization: Bearer $STELLA_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"$COMPONENT_NAME-$CI_COMMIT_TAG\", \"components\": [{ \"componentId\": \"$STELLA_COMPONENT_ID\", \"digest\": \"$DIGEST\" }], \"sourceRef\": { \"type\": \"git\", \"repository\": \"$CI_PROJECT_URL\", \"commit\": \"$CI_COMMIT_SHA\", \"tag\": \"$CI_COMMIT_TAG\" } }" rules: - if: $CI_COMMIT_TAG ``` ### GitHub Actions Integration ```yaml # .github/workflows/release.yml name: Release to Stella on: push: tags: - 'v*' jobs: build-and-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: push: true tags: | ghcr.io/${{ github.repository }}:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.ref_name }} - name: Get image digest id: digest run: | DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/${{ github.repository }}:${{ github.ref_name }} | cut -d@ -f2) echo "digest=$DIGEST" >> $GITHUB_OUTPUT - name: Create Stella Release uses: stella-ops/create-release-action@v1 with: stella-url: ${{ vars.STELLA_API_URL }} stella-token: ${{ secrets.STELLA_TOKEN }} release-name: ${{ github.event.repository.name }}-${{ github.ref_name }} components: | - componentId: ${{ vars.STELLA_COMPONENT_ID }} digest: ${{ steps.digest.outputs.digest }} source-ref: | type: git repository: ${{ github.server_url }}/${{ github.repository }} commit: ${{ github.sha }} tag: ${{ github.ref_name }} ``` ### Jenkins Integration ```groovy // Jenkinsfile pipeline { agent any environment { STELLA_API_URL = 'https://stella.example.com/api/v1' STELLA_TOKEN = credentials('stella-api-token') REGISTRY = 'registry.example.com' IMAGE_NAME = 'myorg/myapp' } stages { stage('Build') { steps { script { docker.build("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_TAG}") } } } stage('Push') { steps { script { docker.withRegistry("https://${REGISTRY}", 'registry-creds') { docker.image("${REGISTRY}/${IMAGE_NAME}:${env.BUILD_TAG}").push() } } } } stage('Create Release') { when { tag pattern: "v\\d+\\.\\d+\\.\\d+", comparator: "REGEXP" } steps { script { def digest = sh( script: "docker inspect --format='{{index .RepoDigests 0}}' ${REGISTRY}/${IMAGE_NAME}:${env.TAG_NAME} | cut -d@ -f2", returnStdout: true ).trim() def response = httpRequest( url: "${STELLA_API_URL}/releases", httpMode: 'POST', contentType: 'APPLICATION_JSON', customHeaders: [[name: 'Authorization', value: "Bearer ${STELLA_TOKEN}"]], requestBody: """ { "name": "${IMAGE_NAME}-${env.TAG_NAME}", "components": [{ "componentId": "${env.STELLA_COMPONENT_ID}", "digest": "${digest}" }], "sourceRef": { "type": "git", "repository": "${env.GIT_URL}", "commit": "${env.GIT_COMMIT}", "tag": "${env.TAG_NAME}" } } """ ) echo "Release created: ${response.content}" } } } } post { success { // Notify Stella of successful build httpRequest( url: "${STELLA_API_URL}/webhooks/ci-status", httpMode: 'POST', contentType: 'APPLICATION_JSON', customHeaders: [[name: 'Authorization', value: "Bearer ${STELLA_TOKEN}"]], requestBody: """ { "buildId": "${env.BUILD_ID}", "status": "success", "commit": "${env.GIT_COMMIT}" } """ ) } } } ``` ## Workflow Step Integration ### Trigger CI Pipeline Step ```typescript // Step type: trigger-ci interface TriggerCIConfig { integrationId: UUID; // CI integration reference pipelineId: string; // Pipeline to trigger ref?: string; // Branch/tag reference variables?: Record; waitForCompletion: boolean; timeout?: number; } class TriggerCIStep implements IStepExecutor { async execute( inputs: StepInputs, config: TriggerCIConfig, context: ExecutionContext ): Promise { const connector = await this.getConnector(config.integrationId); // Trigger pipeline const run = await connector.triggerPipeline( config.pipelineId, { ref: config.ref || context.release?.sourceRef?.tag, variables: { ...config.variables, STELLA_RELEASE_ID: context.release?.id, STELLA_PROMOTION_ID: context.promotion?.id, STELLA_ENVIRONMENT: context.environment?.name } } ); if (!config.waitForCompletion) { return { pipelineRunId: run.id, status: run.status, webUrl: run.webUrl }; } // Wait for completion const finalStatus = await this.waitForPipeline( connector, run.id, config.timeout || 3600 ); if (finalStatus.status !== "success") { throw new StepError( `Pipeline failed with status: ${finalStatus.status}`, { pipelineRunId: run.id, status: finalStatus } ); } return { pipelineRunId: run.id, status: finalStatus.status, webUrl: run.webUrl }; } private async waitForPipeline( connector: ICICDConnector, runId: string, timeout: number ): Promise { const deadline = Date.now() + timeout * 1000; while (Date.now() < deadline) { const run = await connector.getPipelineRun(runId); if (run.status === "success" || run.status === "failed" || run.status === "cancelled") { return run; } await sleep(10000); // Poll every 10 seconds } throw new TimeoutError(`Pipeline did not complete within ${timeout} seconds`); } } ``` ### Wait for CI Step ```typescript // Step type: wait-ci interface WaitCIConfig { integrationId: UUID; runId?: string; // If known, or from input runIdInput?: string; // Input name containing run ID timeout: number; failOnError: boolean; } class WaitCIStep implements IStepExecutor { async execute( inputs: StepInputs, config: WaitCIConfig, context: ExecutionContext ): Promise { const runId = config.runId || inputs[config.runIdInput!]; if (!runId) { throw new StepError("Pipeline run ID not provided"); } const connector = await this.getConnector(config.integrationId); const finalStatus = await this.waitForPipeline( connector, runId, config.timeout ); const success = finalStatus.status === "success"; if (!success && config.failOnError) { throw new StepError( `Pipeline failed with status: ${finalStatus.status}`, { pipelineRunId: runId, status: finalStatus } ); } return { status: finalStatus.status, success, pipelineRun: finalStatus }; } } ``` ## Deployment Status Reporting ### GitHub Deployment Status ```typescript class GitHubStatusReporter { async reportDeploymentStart( integration: Integration, deployment: DeploymentContext ): Promise { const client = await this.getClient(integration); // Create deployment const { data: ghDeployment } = await client.repos.createDeployment({ owner: deployment.repository.owner, repo: deployment.repository.name, ref: deployment.sourceRef.commit, environment: deployment.environment.name, auto_merge: false, required_contexts: [], payload: { stellaReleaseId: deployment.release.id, stellaPromotionId: deployment.promotion.id } }); // Set status to in_progress await client.repos.createDeploymentStatus({ owner: deployment.repository.owner, repo: deployment.repository.name, deployment_id: ghDeployment.id, state: "in_progress", log_url: `${this.stellaUrl}/deployments/${deployment.jobId}`, description: "Deployment in progress" }); // Store deployment ID for later status update await this.storeMapping(deployment.jobId, ghDeployment.id); } async reportDeploymentComplete( integration: Integration, deployment: DeploymentContext, success: boolean ): Promise { const client = await this.getClient(integration); const ghDeploymentId = await this.getMapping(deployment.jobId); await client.repos.createDeploymentStatus({ owner: deployment.repository.owner, repo: deployment.repository.name, deployment_id: ghDeploymentId, state: success ? "success" : "failure", log_url: `${this.stellaUrl}/deployments/${deployment.jobId}`, environment_url: deployment.environment.url, description: success ? "Deployment completed successfully" : "Deployment failed" }); } } ``` ### GitLab Pipeline Status ```typescript class GitLabStatusReporter { async reportDeploymentStatus( integration: Integration, deployment: DeploymentContext, state: "running" | "success" | "failed" | "canceled" ): Promise { const client = await this.getClient(integration); await client.post( `/projects/${integration.config.projectId}/statuses/${deployment.sourceRef.commit}`, { state, ref: deployment.sourceRef.tag || deployment.sourceRef.branch, name: `stella/${deployment.environment.name}`, target_url: `${this.stellaUrl}/deployments/${deployment.jobId}`, description: this.getDescription(state, deployment) } ); } private getDescription(state: string, deployment: DeploymentContext): string { switch (state) { case "running": return `Deploying to ${deployment.environment.name}`; case "success": return `Deployed to ${deployment.environment.name}`; case "failed": return `Deployment to ${deployment.environment.name} failed`; case "canceled": return `Deployment to ${deployment.environment.name} cancelled`; default: return ""; } } } ``` ## API for CI Systems ### Create Release from CI ```http POST /api/v1/releases Authorization: Bearer Content-Type: application/json { "name": "myapp-v1.2.0", "components": [ { "componentId": "component-uuid", "digest": "sha256:abc123..." } ], "sourceRef": { "type": "git", "repository": "https://github.com/myorg/myapp", "commit": "abc123def456", "tag": "v1.2.0", "branch": "main" }, "metadata": { "buildId": "12345", "buildUrl": "https://ci.example.com/builds/12345", "triggeredBy": "ci-pipeline" } } ``` ### Report Build Status ```http POST /api/v1/ci-events/build-complete Authorization: Bearer Content-Type: application/json { "integrationId": "integration-uuid", "buildId": "12345", "status": "success", "commit": "abc123def456", "artifacts": [ { "name": "myapp", "digest": "sha256:abc123...", "repository": "registry.example.com/myorg/myapp" } ], "testResults": { "passed": 150, "failed": 0, "skipped": 5 } } ``` ## Service Account for CI ### Creating CI Service Account ```http POST /api/v1/service-accounts Authorization: Bearer Content-Type: application/json { "name": "ci-pipeline", "description": "Service account for CI/CD integration", "roles": ["release-creator"], "permissions": [ { "resource": "release", "action": "create" }, { "resource": "component", "action": "read" }, { "resource": "version-map", "action": "read" } ], "expiresIn": "365d" } ``` Response: ```json { "success": true, "data": { "id": "sa-uuid", "name": "ci-pipeline", "token": "stella_sa_xxxxxxxxxxxxx", "expiresAt": "2027-01-09T00:00:00Z" } } ``` ## References - [Integrations Overview](overview.md) - [Connectors](connectors.md) - [Webhooks](webhooks.md) - [Workflow Templates](../workflow/templates.md)