Adding Deploy data

Getting deployment data into Cortex is critically important for both engineering insights and organizational success. It enables the use of Eng Intelligence to assess DORA metrics and other KPIs to understand how quickly and efficiently your teams are shipping code. Deployment data also gives you the insight needed to create Scorecards and Initiatives that promote process improvement across teams.

When you add deploy data to Cortex, it is visible on entity pages, in Eng Intelligence Metrics Explorer and the DORA and Velocity Dashboards, in executive reports, and can be accessed via CQL.

Add deployment data to Cortex

To get deploys into Cortex, you must use the Add deployment for entity API endpoint.

Deploy data pipeline examples

In these examples, the repository secret or variable contains a valid Cortex API key, and the repository name matches the Cortex tag.

GitHub Action

In this example, a repository secret called CORTEX_TOKEN contains a valid Cortex API key.

name: Build and Deploy with Status Updates

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  CORTEX_API_URL: "https://api.getcortexapp.com/api/v1/catalog"
  PROJECT_NAME: "my-application"

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Validate Cortex token
      run: |
        if [ -z "${{ secrets.CORTEX_TOKEN }}" ]; then
          echo "ERROR: CORTEX_TOKEN secret not configured"
          exit 1
        fi
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
      
    - name: Run tests
      run: npm test
      
    - name: Build application
      run: npm run build
      
    - name: Deploy to staging
      run: |
        echo "Deploying to staging environment..."
        # Your deployment commands here
        # This might fail intentionally for demonstration

  # Guaranteed notification job that runs regardless of build-and-deploy outcome
  notify-result:
    runs-on: ubuntu-latest
    needs: build-and-deploy
    if: always() # This ensures the job runs regardless of build-and-deploy outcome
    
    steps:
    - name: Send deployment notification to Cortex
      run: |
        echo "Previous job result: ${{ needs.build-and-deploy.result }}"
        REPO_NAME=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]')
        echo "Using repo name: $REPO_NAME"
        
        # Check the status of the previous job
        if [ "${{ needs.build-and-deploy.result }}" == "success" ]; then
          TYPE="DEPLOY"
          STATUS="success"
          MESSAGE="All jobs completed successfully"
        elif [ "${{ needs.build-and-deploy.result }}" == "failure" ]; then
          TYPE="ROLLBACK"
          STATUS="failed"
          MESSAGE="Build and deploy job failed"
        elif [ "${{ needs.build-and-deploy.result }}" == "cancelled" ]; then
          TYPE="ROLLBACK"
          STATUS="cancelled"
          MESSAGE="Build and deploy job was cancelled"
        else
          TYPE="ROLLBACK"
          STATUS="skipped"
          MESSAGE="Build and deploy job was skipped"
        fi
        
        curl -L \
          --request POST \
          --max-time 30 \
          --retry 2 \
          --url "${{ env.CORTEX_API_URL }}/$REPO_NAME/deploys" \
          --header "Authorization: Bearer ${{ secrets.CORTEX_TOKEN }}" \
          --header "Content-Type: application/json" \
          --data "{
            \"customData\": {
              \"workflow\": \"${{ github.workflow }}\",
              \"run_id\": \"${{ github.run_id }}\",
              \"branch\": \"${{ github.ref_name }}\",
              \"final_status\": \"$STATUS\",
              \"message\": \"$MESSAGE\",
              \"actor\": \"${{ github.actor }}\",
              \"repository\": \"${{ github.repository }}\"
            },
            \"deployer\": {
              \"email\": \"${{ github.actor }}@users.noreply.github.com\",
              \"name\": \"${{ github.actor }}\"
            },
            \"environment\": \"staging\",
            \"sha\": \"${{ github.sha }}\",
            \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\",
            \"title\": \"Final deployment $STATUS - ${{ github.workflow }}\",
            \"type\": \"$TYPE\",
            \"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
          }"
GitLab pipeline

Make sure you have defined a CI/CD variable in GitLab that contains your Cortex API key, and ensure the repository you're running this in contains the package.json file.

If any stage in the pipeline fails, the entire pipeline fails and a ROLLBACK event is sent to Cortex.

stages:
  - build
  - test
  - deploy
  - notify

variables:
  CORTEX_API_URL: "https://api.getcortexapp.com/api/v1/catalog"

# Global settings
image: node:18

build_job:
  stage: build
  script:
    - echo "Building application..."
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test_job:
  stage: test
  script:
    - echo "Running tests..."
    - npm test
  dependencies:
    - build_job

deploy_job:
  stage: deploy
  script:
    - echo "Deploying to staging..."
    # Your deployment commands here
    - sleep 2
    - echo "Deployment completed"
  dependencies:
    - build_job
  environment:
    name: staging

# This job always runs and reports pipeline status to Cortex
notify_cortex:
  stage: notify
  image: curlimages/curl:latest
  before_script:
    # Check if previous stages succeeded by examining needs
    - |
      if [ "$BUILD_JOB_STATUS" = "success" ] && [ "$TEST_JOB_STATUS" = "success" ] && [ "$DEPLOY_JOB_STATUS" = "success" ]; then
        PIPELINE_STATUS="success"
        DEPLOY_TYPE="DEPLOY"
        MESSAGE="Pipeline completed successfully"
      else
        PIPELINE_STATUS="failed"
        DEPLOY_TYPE="ROLLBACK"
        MESSAGE="Pipeline failed - one or more stages failed"
      fi
      
      echo "Pipeline Status: $PIPELINE_STATUS"
      echo "Deploy Type: $DEPLOY_TYPE"
      echo "Message: $MESSAGE"
  script:
    - |
      # Convert repo name to lowercase
      REPO_NAME=$(echo "$CI_PROJECT_NAME" | tr '[:upper:]' '[:lower:]')
      echo "Repository: $REPO_NAME"
      
      # Get current timestamp
      TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
      
      # Send notification to Cortex
      curl -L \
        --request POST \
        --max-time 30 \
        --retry 2 \
        --url "$CORTEX_API_URL/$REPO_NAME/deploys" \
        --header "Authorization: Bearer $CORTEX_TOKEN" \
        --header "Content-Type: application/json" \
        --data "{
          \"customData\": {
            \"pipeline_id\": \"$CI_PIPELINE_ID\",
            \"job_id\": \"$CI_JOB_ID\",
            \"branch\": \"$CI_COMMIT_REF_NAME\",
            \"pipeline_status\": \"$PIPELINE_STATUS\",
            \"message\": \"$MESSAGE\",
            \"pipeline_url\": \"$CI_PIPELINE_URL\",
            \"project_path\": \"$CI_PROJECT_PATH\"
          },
          \"deployer\": {
            \"email\": \"$GITLAB_USER_EMAIL\",
            \"name\": \"$GITLAB_USER_NAME\"
          },
          \"environment\": \"staging\",
          \"sha\": \"$CI_COMMIT_SHA\",
          \"timestamp\": \"$TIMESTAMP\",
          \"title\": \"Pipeline $PIPELINE_STATUS - $CI_PROJECT_NAME\",
          \"type\": \"$DEPLOY_TYPE\",
          \"url\": \"$CI_PIPELINE_URL\"
        }"
      
      if [ $? -eq 0 ]; then
        echo "Successfully notified Cortex"
      else
        echo "Failed to notify Cortex, but continuing..."
      fi
  needs:
    - job: build_job
      artifacts: false
    - job: test_job  
      artifacts: false
    - job: deploy_job
      artifacts: false
  when: always
Azure DevOps

In this example, a variable called CORTEX_TOKEN contains a valid Cortex API key.

trigger:
  branches:
    include:
      - main
      - develop

pr:
  branches:
    include:
      - main

variables:
  CORTEX_API_URL: 'https://api.getcortexapp.com/api/v1/catalog'

pool:
  vmImage: 'ubuntu-latest'

stages:
- stage: Build
  displayName: 'Build Stage'
  jobs:
  - job: BuildJob
    displayName: 'Build Application'
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '18.x'
      displayName: 'Install Node.js'

    - script: |
        echo "Building application..."
        npm ci
        npm run build
      displayName: 'Build Application'

    - publish: dist
      artifact: BuildArtifacts
      displayName: 'Publish Build Artifacts'

- stage: Test
  displayName: 'Test Stage'
  dependsOn: Build
  jobs:
  - job: TestJob
    displayName: 'Run Tests'
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '18.x'
      displayName: 'Install Node.js'

    - script: |
        echo "Running tests..."
        npm ci
        npm test
      displayName: 'Run Tests'

- stage: Deploy
  displayName: 'Deploy Stage'
  dependsOn: Test
  jobs:
  - job: DeployJob
    displayName: 'Deploy to Staging'
    steps:
    - script: |
        echo "Deploying to staging..."
        sleep 2
        echo "Deployment completed"
      displayName: 'Deploy Application'

- stage: Notify
  displayName: 'Notify Cortex'
  dependsOn: 
    - Build
    - Test
    - Deploy
  condition: always()
  jobs:
  - job: NotifyJob
    displayName: 'Send Cortex Notification'
    steps:
    - checkout: none
    
    - bash: |
        echo "Build Stage Result: $(stageDependencies.Build.BuildJob.result)"
        echo "Test Stage Result: $(stageDependencies.Test.TestJob.result)"
        echo "Deploy Stage Result: $(stageDependencies.Deploy.DeployJob.result)"
        
        # Determine overall pipeline status
        BUILD_RESULT="$(stageDependencies.Build.BuildJob.result)"
        TEST_RESULT="$(stageDependencies.Test.TestJob.result)"
        DEPLOY_RESULT="$(stageDependencies.Deploy.DeployJob.result)"
        
        if [ "$BUILD_RESULT" = "Succeeded" ] && [ "$TEST_RESULT" = "Succeeded" ] && [ "$DEPLOY_RESULT" = "Succeeded" ]; then
          PIPELINE_STATUS="success"
          DEPLOY_TYPE="DEPLOY"
          MESSAGE="Pipeline completed successfully"
        else
          PIPELINE_STATUS="failed"
          DEPLOY_TYPE="ROLLBACK"
          MESSAGE="Pipeline failed - one or more stages failed (Build: $BUILD_RESULT, Test: $TEST_RESULT, Deploy: $DEPLOY_RESULT)"
        fi
        
        echo "Pipeline Status: $PIPELINE_STATUS"
        echo "Deploy Type: $DEPLOY_TYPE"
        echo "Message: $MESSAGE"
        
        # Convert repo name to lowercase (extract from full repository name)
        REPO_NAME=$(echo "$(Build.Repository.Name)" | cut -d'/' -f2 | tr '[:upper:]' '[:lower:]')
        echo "Repository: $REPO_NAME"
        
        # Get current timestamp
        TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
        
        # Get deployer information
        DEPLOYER_EMAIL="${BUILD_REQUESTEDFOREMAIL:[email protected]}"
        DEPLOYER_NAME="${BUILD_REQUESTEDFOR:-Azure DevOps}"
        
        # Send notification to Cortex
        curl -L \
          --request POST \
          --max-time 30 \
          --retry 2 \
          --url "$(CORTEX_API_URL)/$REPO_NAME/deploys" \
          --header "Authorization: Bearer $(CORTEX_TOKEN)" \
          --header "Content-Type: application/json" \
          --data "{
            \"customData\": {
              \"pipeline_id\": \"$(Build.BuildId)\",
              \"build_number\": \"$(Build.BuildNumber)\",
              \"branch\": \"$(Build.SourceBranchName)\",
              \"pipeline_status\": \"$PIPELINE_STATUS\",
              \"message\": \"$MESSAGE\",
              \"build_url\": \"$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)\",
              \"project\": \"$(System.TeamProject)\",
              \"repository\": \"$(Build.Repository.Name)\"
            },
            \"deployer\": {
              \"email\": \"$DEPLOYER_EMAIL\",
              \"name\": \"$DEPLOYER_NAME\"
            },
            \"environment\": \"staging\",
            \"sha\": \"$(Build.SourceVersion)\",
            \"timestamp\": \"$TIMESTAMP\",
            \"title\": \"Pipeline $PIPELINE_STATUS - $(Build.Repository.Name)\",
            \"type\": \"$DEPLOY_TYPE\",
            \"url\": \"$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)\"
          }"
        
        if [ $? -eq 0 ]; then
          echo "Successfully notified Cortex"
        else
          echo "Failed to notify Cortex, but continuing..."
        fi
      displayName: 'Send Cortex Notification'
      env:
        CORTEX_TOKEN: $(CORTEX_TOKEN)
Jenkins

In this example, it is assumed that the Jenkins job is associated with a repository. It uses the repository name to match with the Cortex entity tag. The job also assumes that you have defined a Global Credential called CORTEX_TOKEN that contains a valid Cortex API key.

pipeline {
    agent any
    
    environment {
        CORTEX_API_URL = "https://api.getcortexapp.com/api/v1/catalog"
    }
    
    stages {
        stage('Build') {
            steps {
                script {
                    echo "Building application..."
                }
                sh '''
                    node --version
                    npm --version
                    npm ci
                    npm run build
                '''
            }
            post {
                success {
                    script {
                        env.BUILD_STAGE_RESULT = 'SUCCESS'
                    }
                }
                failure {
                    script {
                        env.BUILD_STAGE_RESULT = 'FAILURE'
                    }
                }
            }
        }
        
        stage('Test') {
            steps {
                script {
                    echo "Running tests..."
                }
                sh 'npm test'
            }
            post {
                success {
                    script {
                        env.TEST_STAGE_RESULT = 'SUCCESS'
                    }
                }
                failure {
                    script {
                        env.TEST_STAGE_RESULT = 'FAILURE'
                    }
                }
            }
        }
        
        stage('Deploy') {
            steps {
                script {
                    echo "Deploying to staging..."
                    sh '''
                        sleep 2
                        echo "Deployment completed"
                    '''
                }
            }
            post {
                success {
                    script {
                        env.DEPLOY_STAGE_RESULT = 'SUCCESS'
                    }
                }
                failure {
                    script {
                        env.DEPLOY_STAGE_RESULT = 'FAILURE'
                    }
                }
            }
        }
    }
    
    post {
        always {
            script {
                notifyCortex()
            }
        }
    }
}

def notifyCortex() {
    try {
        echo "Build Stage Result: ${env.BUILD_STAGE_RESULT ?: 'SKIPPED'}"
        echo "Test Stage Result: ${env.TEST_STAGE_RESULT ?: 'SKIPPED'}"
        echo "Deploy Stage Result: ${env.DEPLOY_STAGE_RESULT ?: 'SKIPPED'}"
        
        // Determine overall pipeline status
        def buildResult = env.BUILD_STAGE_RESULT ?: 'SKIPPED'
        def testResult = env.TEST_STAGE_RESULT ?: 'SKIPPED'
        def deployResult = env.DEPLOY_STAGE_RESULT ?: 'SKIPPED'
        
        def pipelineStatus
        def deployType
        def message
        
        if (buildResult == 'SUCCESS' && testResult == 'SUCCESS' && deployResult == 'SUCCESS') {
            pipelineStatus = 'success'
            deployType = 'DEPLOY'
            message = 'Pipeline completed successfully'
        } else {
            pipelineStatus = 'failed'
            deployType = 'ROLLBACK'
            message = "Pipeline failed - one or more stages failed (Build: ${buildResult}, Test: ${testResult}, Deploy: ${deployResult})"
        }
        
        echo "Pipeline Status: ${pipelineStatus}"
        echo "Deploy Type: ${deployType}"
        echo "Message: ${message}"
        
        // Convert repo name to lowercase (extract from job name)
        def repoName = env.JOB_NAME.tokenize('/')[0].toLowerCase()
        echo "Repository: ${repoName}"
        
        // Get Git commit SHA and branch
        def gitCommit = sh(
            script: 'git rev-parse HEAD',
            returnStdout: true
        ).trim()
        
        def gitBranch = sh(
            script: 'git rev-parse --abbrev-ref HEAD',
            returnStdout: true
        ).trim()
        
        // Get current timestamp
        def timestamp = sh(
            script: 'date -u +"%Y-%m-%dT%H:%M:%SZ"',
            returnStdout: true
        ).trim()
        
        // Escape JSON special characters in message
        def escapedMessage = message.replaceAll('"', '\\\\"').replaceAll("'", "\\\\'")
        
        // Get deployer information
        def deployerEmail = env.BUILD_USER_EMAIL ?: '[email protected]'
        def deployerName = env.BUILD_USER ?: 'Jenkins'
        
        // Build JSON payload
        def jsonPayload = """
        {
            "customData": {
                "pipeline": "${env.JOB_NAME}",
                "build_number": "${env.BUILD_NUMBER}",
                "branch": "${gitBranch}",
                "pipeline_status": "${pipelineStatus}",
                "message": "${escapedMessage}",
                "build_url": "${env.BUILD_URL}",
                "jenkins_url": "${env.JENKINS_URL}"
            },
            "deployer": {
                "email": "${deployerEmail}",
                "name": "${deployerName}"
            },
            "environment": "staging",
            "sha": "${gitCommit}",
            "timestamp": "${timestamp}",
            "title": "Pipeline ${pipelineStatus} - ${env.JOB_NAME}",
            "type": "${deployType}",
            "url": "${env.BUILD_URL}"
        }
        """
        
        // Send notification to Cortex
        withCredentials([string(credentialsId: 'CORTEX_TOKEN', variable: 'CORTEX_TOKEN')]) {
            def curlResult = sh(
                script: """
                    curl -L \\
                      --request POST \\
                      --max-time 30 \\
                      --retry 2 \\
                      --url "${env.CORTEX_API_URL}/${repoName}/deploys" \\
                      --header "Authorization: Bearer \${CORTEX_TOKEN}" \\
                      --header "Content-Type: application/json" \\
                      --data '${jsonPayload}' \\
                      --write-out "%{http_code}" \\
                      --silent \\
                      --output /dev/null
                """,
                returnStdout: true
            ).trim()
            
            if (curlResult == '200' || curlResult == '201') {
                echo "Successfully notified Cortex (HTTP ${curlResult})"
            } else {
                echo "Failed to notify Cortex (HTTP ${curlResult}), but continuing..."
            }
        }
        
    } catch (Exception e) {
        echo "Failed to send Cortex notification: ${e.getMessage()}"
        // Don't fail the build if notification fails
    }
}

Add custom data to deployments

Adding a customData object to your API call allows you the flexibility to add metadata that is important to your organization.

If there is custom data included for a deploy, you can view it under "Recent activity" on an entity. Click the 3 dots icon to expand and view the custom data:

Viewing deploy data

View deployments on entity pages

Deployment information appears in several places on an entity page:

  • While viewing an entity page, you can see last deployment information near the top of the page:

    Deployment information appears near the top of an entity details page.
  • Near the bottom of an entity page, deploys will also appear under "Recent activity."

  • In the left sidebar of an entity, click Events. This page includes:

    • A visual chart of deploys.

      • By default the chart shows data from the last month. Click the Last month dropdown in the upper right to change the timeframe.

    • All recent events for the entity. In the upper right corner of the events list, click Filter to select and apply filters for this list. You can choose to only view the deploy event types.

    On an entity page, click "Events" in the sidebar to view recent events.

View deployments in Eng Intelligence

When you send deploy data into Cortex, you can use this information in Eng Intelligence reporting to gain insight into your deploy metrics. Deploy metrics include average number of deploys per week and deploy change failure rate.

In Eng Intelligence, click into an entity to open a side panel with a historical performance graph.

Read more about using Eng Intelligence in the documentation.

Use deployment data in CQL and Scorecards

You can use deploy data to write rules for Scorecards and to create CQL reports.

See more examples in the CQL Explorer in Cortex.

Deploys

Deploys added to an entity through the public API.

Definition: deploys(lookback: Duration, types: List): List

Example

In a Scorecard, you can write a rule to check whether an entity had fewer than 5 bug fixes in the last month:

deploys(lookback = duration("P1M"), types = ["DEPLOY"]).filter((deploy) => deploy.customData != null AND deploy.customData.get("bugFix") == true).length = 2

Write a rule to verify that there was, on average, less than 1 rollback for every 4 deploys in the past month:

deploys(lookback=duration("P1M"),types=["ROLLBACK"]).length / deploys(lookback=duration("P1M"),types=["DEPLOY", "ROLLBACK", "RESTART"]).length < 0.25

Last updated

Was this helpful?