Example: CI Integration¶
This example shows how to enforce required diagnostics in CI and publish rich artifacts.
CI goal¶
- Fail builds on required issues
- Keep logs readable
- Preserve machine-readable reports for triage
Recommended command:
Why this works:
minimalkeeps scope to required checks onlyjsonprovides parser-safe structure- process exit code is gate signal (
0pass,1fail)
GitHub Actions workflow¶
name: Doctor Check
on:
pull_request:
push:
branches: [main]
jobs:
doctor:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install package
run: |
python -m pip install --upgrade pip
python -m pip install azure-functions-doctor
- name: Run doctor (required checks)
run: |
azure-functions doctor \
--profile minimal \
--format json \
--output doctor.json
- name: Upload doctor artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: doctor-report
path: doctor.json
Exit code behavior in CI¶
The doctor exits non-zero when required checks fail.
- Exit
0: job continues - Exit
1: step fails immediately unless explicitly tolerated
If you want to always upload artifacts before failing, split gate and report steps.
Pattern: always upload + explicit gate¶
- name: Run doctor and capture status
id: doctor
run: |
set +e
azure-functions doctor --profile minimal --format json --output doctor.json
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
- name: Upload doctor artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: doctor-report
path: doctor.json
- name: Fail job when doctor failed
if: steps.doctor.outputs.exit_code != '0'
run: exit 1
Parse JSON to produce summary¶
Python parser snippet¶
import json
from pathlib import Path
def summarize(path: str) -> tuple[int, int, int]:
payload = json.loads(Path(path).read_text(encoding="utf-8"))
passed = warned = failed = 0
for section in payload["results"]:
for item in section["items"]:
if item["status"] == "pass":
passed += 1
elif item["status"] == "warn":
warned += 1
elif item["status"] == "fail":
failed += 1
return passed, warned, failed
jq parser snippet¶
fails=$(jq '[.results[].items[] | select(.status=="fail")] | length' doctor.json)
warns=$(jq '[.results[].items[] | select(.status=="warn")] | length' doctor.json)
passes=$(jq '[.results[].items[] | select(.status=="pass")] | length' doctor.json)
echo "doctor: $fails fails, $warns warnings, $passes passed"
Fail only on required failures¶
You usually do not need custom logic because exit codes already represent required failures.
Still, explicit JSON-based checks can be useful for custom messaging:
required_failures=$(jq '[.results[].items[] | select(.status=="fail")] | length' doctor.json)
if [ "$required_failures" -gt 0 ]; then
echo "Required diagnostics failed: $required_failures"
exit 1
fi
Multi-format publishing¶
For richer ecosystem integration, emit multiple formats in separate steps:
azure-functions doctor --profile minimal --format json --output doctor.json
azure-functions doctor --profile minimal --format sarif --output doctor.sarif
azure-functions doctor --profile minimal --format junit --output doctor-junit.xml
Then upload artifacts for each format.
Security guidance for CI usage¶
- Do not execute untrusted custom rules files from forked PRs
- Keep path target explicit in monorepos (
--path) - Store artifacts for failed runs to speed triage
Monorepo pattern¶
Run one job per function project:
strategy:
matrix:
app_path:
- apps/orders-function
- apps/payments-function
steps:
- run: azure-functions doctor --path ${{ matrix.app_path }} --profile minimal --format json --output doctor-${{ matrix.app_path }}.json
Common CI pitfalls¶
- Running in wrong working directory
- Forgetting to install project dependencies before checks
- Treating warnings as failures without clear team policy
- Parsing display-only fields instead of status fields
Azure DevOps Pipelines¶
Add a pipeline step to gate deployments on Azure Functions project health:
# azure-pipelines.yml
trigger:
branches:
include:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "3.12"
addToPath: true
- script: |
python -m pip install --upgrade pip
pip install azure-functions-doctor
displayName: Install azure-functions-doctor
- script: |
set +e
azure-functions doctor \
--profile minimal \
--format json \
--output $(Build.ArtifactStagingDirectory)/doctor.json
echo "##vso[task.setvariable variable=DOCTOR_EXIT_CODE]$?"
displayName: Run azure-functions-doctor
- task: PublishBuildArtifacts@1
condition: always()
inputs:
pathToPublish: $(Build.ArtifactStagingDirectory)/doctor.json
artifactName: doctor-report
displayName: Publish doctor report
- script: |
if [ "$(DOCTOR_EXIT_CODE)" != "0" ]; then
echo "##vso[task.logissue type=error]azure-functions-doctor found required failures"
exit 1
fi
displayName: Fail pipeline on required failures
For SARIF output, replace --format json with --format sarif --output doctor.sarif and publish that artifact instead.
Pre-commit hook¶
Run doctor as a local pre-commit check to catch issues before push:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: azure-functions-doctor
name: Azure Functions Doctor
language: system
entry: azure-functions doctor --profile minimal
pass_filenames: false
always_run: true
Install and run:
pip install pre-commit azure-functions-doctor
pre-commit install
pre-commit run azure-functions-doctor
This runs the required-checks profile on every commit attempt. Use --profile minimal to keep the hook fast and focused on blocking issues only.
VS Code task¶
Run doctor from the VS Code command palette via a workspace task:
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Azure Functions Doctor",
"type": "shell",
"command": "azure-functions doctor --profile minimal --format json --output doctor.json",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
}
]
}
Run it with Terminal → Run Task → Azure Functions Doctor. The JSON report is written to doctor.json in the workspace root for inspection.
SARIF upload to GitHub Code Scanning¶
Upload doctor results as SARIF to surface findings in the GitHub Security tab:
- name: Run doctor (SARIF output)
id: doctor
run: |
set +e
azure-functions doctor \
--profile minimal \
--format sarif \
--output doctor.sarif
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
- name: Upload SARIF to GitHub Code Scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: doctor.sarif
category: azure-functions-doctor
- name: Fail job on required failures
if: steps.doctor.outputs.exit_code != '0'
run: exit 1
The SARIF report includes:
ruleIdmapped to the canonical rule identifier (e.g.check_host_json_exists)levelset toerrorfor required failures,warningfor optional checkslocationspointing to the project rootproperties.hintwith the actionable fix suggestiondriver.rulesarray listing all rules with description and category
Requires security-events: write permission on the job: