Jenkinsfile
Copy
Ask AI
pipeline {
agent any
environment {
// Replace {slug} with your organization's tenant identifier
BINARLY_AUTH_URL = 'https://auth-{slug}.binarly.cloud/realms/BinarlyRealm/protocol/openid-connect/token'
BINARLY_API_URL = 'https://dashboard-{slug}.binarly.cloud'
BINARLY_CLIENT_ID = credentials('binarly-client-id')
BINARLY_CLIENT_SECRET = credentials('binarly-client-secret')
BINARLY_PRODUCT_ID = credentials('binarly-product-id')
}
stages {
stage('Build Firmware') {
steps {
sh 'make firmware'
}
}
stage('Authenticate') {
steps {
script {
def tokenResponse = sh(
script: """
curl -s --fail-with-body \\
-H "Content-Type: application/x-www-form-urlencoded" \\
--data-urlencode "grant_type=client_credentials" \\
--data-urlencode "client_id=${BINARLY_CLIENT_ID}" \\
--data-urlencode "client_secret=${BINARLY_CLIENT_SECRET}" \\
"${BINARLY_AUTH_URL}"
""",
returnStdout: true
)
env.BINARLY_TOKEN = readJSON(text: tokenResponse).access_token
}
}
}
stage('Upload Firmware') {
steps {
script {
// Generate upload URL
def uploadRes = sh(
script: """
curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
"${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/tempFiles:generateUploadUrl"
""",
returnStdout: true
)
def uploadData = readJSON(text: uploadRes)
// Upload binary
sh "curl -s -X PUT --data-binary @./build/firmware.bin '${uploadData.uploadUrl}'"
// Finalize
def imageRes = sh(
script: """
curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
-F "tempFileId=${uploadData.id}" \\
-F "imageName=Jenkins Build ${BUILD_NUMBER}" \\
-F "version=${BUILD_NUMBER}" \\
-F "file=@./build/firmware.bin" \\
"${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images:upload"
""",
returnStdout: true
)
env.IMAGE_ID = readJSON(text: imageRes).id
echo "Image uploaded: ${env.IMAGE_ID}"
}
}
}
stage('Wait for Scan') {
steps {
script {
def status = 'running'
while (status != 'completed') {
sleep 30
def scanRes = sh(
script: """
curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
"${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images/${IMAGE_ID}/scans?status=true"
""",
returnStdout: true
)
status = readJSON(text: scanRes).scans[0].latestScanState.type
echo "Scan status: ${status}"
if (status == 'failed') {
error 'Binarly scan failed'
}
}
echo 'Scan completed successfully'
}
}
}
stage('Verify Results') {
steps {
script {
echo 'Binarly Security Verification'
def failOnStatus = env.FAIL_ON_STATUS ?: 'new,inProgress'
echo "Failing on statuses: ${failOnStatus}"
// Get all images
def imagesRes = sh(
script: """
curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
"${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images"
""",
returnStdout: true
)
def imagesData = readJSON(text: imagesRes)
def imageCount = imagesData.images.size()
echo "Product has ${imageCount} image(s)"
// Compare if multiple images exist
if (imageCount > 1) {
// Sort images by createTime descending
def sortedImages = imagesData.images.sort { a, b -> b.createTime <=> a.createTime }
def latestId = sortedImages[0].id
def previousId = sortedImages[1].id
echo "Comparing: ${previousId} → ${latestId}"
def resolved = sh(
script: """
curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
-H "Content-Type: application/json" \\
-d '{"filters":[{"field":"compareLeftImageId","value":"${previousId}","comparator":"equals"},{"field":"compareRightImageId","value":"${latestId}","comparator":"equals"},{"field":"compareSide","value":"left","comparator":"equals"}]}' \\
"${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
""",
returnStdout: true
).trim()
def unchanged = sh(
script: """
curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
-H "Content-Type: application/json" \\
-d '{"filters":[{"field":"compareLeftImageId","value":"${previousId}","comparator":"equals"},{"field":"compareRightImageId","value":"${latestId}","comparator":"equals"},{"field":"compareSide","value":"both","comparator":"equals"}]}' \\
"${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
""",
returnStdout: true
).trim()
def newIssues = sh(
script: """
curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
-H "Content-Type: application/json" \\
-d '{"filters":[{"field":"compareLeftImageId","value":"${previousId}","comparator":"equals"},{"field":"compareRightImageId","value":"${latestId}","comparator":"equals"},{"field":"compareSide","value":"right","comparator":"equals"}]}' \\
"${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
""",
returnStdout: true
).trim()
echo "Resolved: ${resolved} | Unchanged: ${unchanged} | New: ${newIssues}"
}
// Check for findings matching configured statuses (using 'in' comparator with array)
def statusesArray = failOnStatus.split(',').collect { '"' + it.trim() + '"' }.join(',')
// Use latestId if available (from comparison), otherwise use IMAGE_ID from upload step
def checkImageId = latestId ?: env.IMAGE_ID
def totalActionable = sh(
script: """
curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
-H "Content-Type: application/json" \\
-d '{"filters":[{"field":"imageId","value":"${checkImageId}","comparator":"equals"},{"field":"issueStatus","value":[${statusesArray}],"comparator":"in"}]}' \\
"${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
""",
returnStdout: true
).trim().toInteger()
echo " Statuses checked: ${failOnStatus}"
echo "Total actionable findings: ${totalActionable}"
if (totalActionable > 0) {
error "BUILD FAILED: ${totalActionable} actionable finding(s)"
}
echo 'BUILD PASSED: No actionable findings'
}
}
}
}
}
Required Credentials
Configure in Manage Jenkins → Credentials:| Credential ID | Type | Description |
|---|---|---|
binarly-client-id | Secret text | Keycloak API Client ID |
binarly-client-secret | Secret text | Keycloak API Client Secret |
binarly-product-id | Secret text | Target product ID |
Pipeline Configuration
- Create a new Pipeline job
- Configure Pipeline script from SCM or paste the Jenkinsfile
- Ensure credentials are created in Jenkins