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?