Diagrams in Azure Pipelines
How to check, redraw, and regenerate Plotwork diagrams in Azure Pipelines with the Plotwork extension for Azure DevOps and its task PlotworkDiagrams@1: fail a pull request whose pictures are stale or whose diagrams have drifted from their files, mark each problem on its line, and open a pull request with the new diagrams. It does what the GitHub Action does, for anyone who keeps Plotwork diagrams in a repository built by Azure Pipelines, in Azure Repos or on GitHub.
- Azure Pipelines
TL;DR
| Question | Answer |
|---|---|
| What is the extension? | Plotwork diagrams for Azure DevOps. It adds the task PlotworkDiagrams@1 and a Plotwork service connection type. Until its public Marketplace listing opens, it is shared privately on request. |
| Is it released? | Not yet as a stable release. Its first stable version, 0.1.0, brings the task PlotworkDiagrams@1; until then, run the command line in a script step, as CI usage shows. |
| What does the task run? | The pinned release of the command line, npx -y @buun_group/plotwork@0.3.0, on the agent. It is the same check you run on your machine. |
| Which modes are there? | check, validate, sync, render, drift, and generate, the commands of the GitHub Action. |
| Where do problems show? | Each one is an issue on its file and line, in the run and on the pull request, and the run gets a Plotwork summary tab. |
| What does the agent need? | Node 22 or later (add UseNode@1 first), git, and access to the npm registry. Microsoft-hosted agents have all three. |
| Does it need a key? | No. Reporting drift, publishing, and an organisation's style rules need a key from a service connection or a secret variable, or no key at all once the pipeline is a trusted publisher. |
| Does it send my files anywhere? | No. The command line reads the checkout and makes no request. reportDrift sends a run's metadata, and publish the generated diagram source, never a file. |
| How does it connect to my organisation? | With a Plotwork service connection that holds an organisation API key, or with no key as a trusted publisher (section 6). |
| What if I cannot install extensions? | Run the command line in a script step, as CI usage shows. |
1. Install the extension
The extension is shared privately with each Azure DevOps organisation that asks for it until its public listing in the Visual Studio Marketplace opens. To get it now, ask us for access (opens in a new tab) with the name of your Azure DevOps organisation, or run the command line in a script step as CI usage shows.
Once the extension is shared with your organisation or listed, an organisation administrator installs it (Organization settings, Extensions, Shared or Browse marketplace, Plotwork diagrams, Install). Every project of the organisation can then use the task and the Plotwork service connection type.
2. Check every pull request
trigger: nonepr: branches: include: [main]
pool: vmImage: ubuntu-latest
steps: - task: UseNode@1 inputs: version: 22.x - task: PlotworkDiagrams@1 inputs: mode: check paths: | README.md docs/**/*.md diagrams: | diagrams/**/*.diagramFor a repository in Azure Repos, a pr trigger in the file does nothing: add the pipeline as a build validation policy of the branch (Project settings, Repositories, Policies, Branch policies) instead.
check runs plotwork sync --check on the Markdown and plotwork validate on each .diagram file. Each stale picture and each mistake is an issue on its line, which the run's summary and the pull request show, and the Plotwork tab of the run lists every file with how to fix it. To fix a stale picture, run npx -y @buun_group/plotwork@0.3.0 sync on the same files and commit the Markdown and the SVGs it writes.
| Input | What it changes |
|---|---|
failOn | error (the default) fails on errors and stale pictures, warning also on warnings, never only reports. |
convertMermaid | true reads each Mermaid fence that converts to Plotwork as a diagram with no picture yet. |
rulesOrg | Applies an organisation's saved style rules instead of the rules of the settings file. |
A rules section in the settings is checked too, with plotwork check.
3. Validate, redraw, and render
| Mode | What it does |
|---|---|
validate | Reports the errors and warnings of the Markdown and .diagram files, and draws nothing. |
sync | Redraws the stale pictures of the Markdown in the workspace and commits nothing; with convertMermaid, replaces each Mermaid fence that converts first. files lists what it wrote, for a later step. |
render | Draws each .diagram file light and dark into outDir: docs/flow.diagram becomes plotwork-svg/docs/flow.svg and flow.dark.svg. |
4. Fail a pull request on drift
When diagrams are made from Terraform, OpenAPI, Compose and other files, name them in sources of plotwork.config.json, as Diagrams as code describes. Then run the task in drift mode:
steps: - task: UseNode@1 inputs: version: 22.x - task: PlotworkDiagrams@1 inputs: mode: driftEach drifted diagram is an issue on its file, and the task publishes a test run in which each generated diagram is a test, failed when it drifted. On a pull request build in Azure Repos it writes one comment with the change table, the owners, and how to fix it, and edits that comment on later runs, to say so when everything is in step again (comment: auto, or always, or never). The comment needs Contribute to pull requests for the build service.
On a large repository, check only the sources whose files changed on the branch. The check needs the history up to the ref:
steps: - checkout: self fetchDepth: 0 - task: UseNode@1 inputs: version: 22.x - task: PlotworkDiagrams@1 inputs: mode: drift since: origin/main5. Write the diagrams again in a pull request
This pipeline runs every night on main, makes every diagram again from its files, and opens or updates one pull request with the ones that drifted:
trigger: nonepr: none
schedules: - cron: '0 2 * * *' displayName: Regenerate diagrams branches: include: [main]
pool: vmImage: ubuntu-latest
steps: - checkout: self fetchDepth: 0 - task: UseNode@1 inputs: version: 22.x - task: PlotworkDiagrams@1 name: plotwork inputs: mode: generate onDrift: prAzure Pipelines skips a scheduled run when the branch has no new commit since the last successful scheduled run. Add always: true under the schedule to run every night anyway, for example so the drift dashboard always shows a recent run.
The task makes every diagram again in the workspace and uploads plotwork.patch as the pipeline artifact plotwork. Then onDrift says what to do with the drifted diagrams:
onDrift | What happens |
|---|---|
pr (default) | One commit on top of the run's branch, pushed to prBranch (plotwork/diagrams), and one pull request into the run's branch, opened or updated. When nothing drifts any more, the task abandons its pull request and deletes the branch. It never replaces a branch whose last commit is not Plotwork's. |
commit | One commit on the branch the run is on. A commit of its own (it carries Plotwork-Generated: true) never starts another. |
fail | The step fails, and the files and the patch stay. |
patch | The files and the patch stay, and the step passes. |
pr and commit push with the job's own access token, and only in Azure Repos. The task reads the token itself, so no env: mapping is needed. A pull request build runs on a merge commit, so pr and commit run on a push, a schedule, or a manual run. In a GitHub or Bitbucket repository the task cannot push: it says why, and the files and the patch stay for a later step of yours.
Build service permissions
Open Project settings, Repositories, the repository, Security, and choose the user <project> Build Service (<organisation>). Set these to Allow:
| Permission | Needed for |
|---|---|
| Contribute | onDrift: pr and commit: pushing the commit |
| Create branch | onDrift: pr: creating plotwork/diagrams the first time |
| Contribute to pull requests | onDrift: pr: opening, updating, and abandoning the pull request; comment in drift and generate |
A branch policy on prBranch refuses the push too, so leave that branch free of policies. A pipeline whose job authorization scope is the whole collection runs as Project Collection Build Service (<organisation>) instead, so grant that user.
With publish: true, each source of the settings that has a publish section is published to Plotwork after the files are written, as Diagrams as code describes.
6. Connect to your organisation
check, validate, sync, render, and drift without reporting need no Plotwork account. Reporting drift, publishing, and an organisation's style rules (rulesOrg) do. The task takes the first credential it finds, in this order:
| Credential | When to choose it |
|---|---|
| A Plotwork service connection | You keep credentials in Azure DevOps service connections, or Azure DevOps Server runs your pipelines. |
A secret variable, PLOTWORK_API_KEY | You keep secrets in a variable group or a key vault. |
| No key: a trusted publisher | You want no secret anywhere. Pro and Enterprise, on Azure DevOps Services. |
With a service connection and an organisation key
- In the Plotwork portal, open the organisation, choose Keys & CI, and make a key that can do diagrams ("Diagrams" or "Diagrams and the audit log"). A key made "Audit log only" cannot report or publish, and a personal key is refused.
- In Azure DevOps, open Project settings, Service connections, New service connection, and choose Plotwork.
- Leave the API address as it is, paste the key into Organisation API key, name the connection (such as
plotwork), and save. Grant it to the pipelines that use it, or approve it on the first run. - Set
plotworkConnectionto the connection's name:
- task: PlotworkDiagrams@1 inputs: mode: drift reportDrift: true plotworkConnection: plotwork organization: acmeTo keep the key in a secret variable instead, map it into the step, because Azure Pipelines never puts a secret in the environment by itself:
- task: PlotworkDiagrams@1 inputs: mode: drift reportDrift: true env: PLOTWORK_API_KEY: $(PLOTWORK_API_KEY)With no key, as a trusted publisher
- In Azure DevOps, open Pipelines and choose the pipeline. Its address ends in
?definitionId=42: that number is the pipeline id. Renaming the pipeline keeps it. - In the Plotwork portal, open the organisation, choose Keys & CI, then CI/CD. Under Trusted publishers, choose Add trusted publisher, then Azure Pipelines.
- Fill in the Azure DevOps organisation (
acmeinhttps://dev.azure.com/acme), the project, and the pipeline id. With the Azure DevOps connector connected, choose each from a list instead, and Branch suggests the pipeline's default branch. - Optionally name a branch (
main, or a ref pattern such asrefs/heads/release/*). Leave it blank for any branch. A pull request build's ref isrefs/pull/<n>/merge, so an entry limited tomainrefuses pull request runs. - Choose what its runs can do: Publish diagrams to the library, Report drift to the Drift tab, or both. Give it a name you will recognise in the usage and the audit log, and save.
- Run the pipeline with no service connection and no
PLOTWORK_API_KEY. Choose Workflow beside the entry to copy a pipeline made for it.
The task asks Azure Pipelines for the job's OpenID Connect token with the job's own access token, which needs no service connection, and exchanges it for a Plotwork token that lasts 15 minutes. The first run that passes every other check pins the ids of the Azure DevOps organisation and project, and the entry shows a pin until then. The output signedInAs names the organisation and the trusted publisher. Trusted publishers says what is checked.
When more than one Plotwork organisation trusts the pipeline, set organization to the slug of the one to sign in to.
7. Report drift to the dashboard
An organisation on a plan with the drift dashboard sees every repository's drift in one place. This pipeline checks every pull request and every push to main, and sends each run to the dashboard:
trigger: branches: include: [main]pr: branches: include: [main]
pool: vmImage: ubuntu-latest
steps: - checkout: self - task: UseNode@1 inputs: version: 22.x - task: PlotworkDiagrams@1 inputs: mode: drift reportDrift: trueIt signs in as a trusted publisher; add plotworkConnection to use a key instead. To report from the nightly pipeline of section 5, add reportDrift: true, and publish: true to publish too, to its generate step.
What reportDrift sends | What is never sent |
|---|---|
| The repository, the branch, the commit, the build reason, the run and pull request links | A diagram, a file, or a name of what changed |
| Per source: its name, out path, status, counts of what was added, removed, and changed, errors, and CODEOWNERS owners | The key or the token, which the task registers as secrets so the log masks them |
A refusal, such as a plan with no dashboard, is a note and fails nothing. The dashboard names a repository as owner/name: a GitHub repository as it is, and an Azure Repos repository as project/repository. A project whose name has a space is not sent yet, and the note says so.
8. Inputs
| Input | Default | Meaning |
|---|---|---|
mode | check | check, validate, sync, render, generate, or drift. |
paths | **/*.md | check, validate, sync: the Markdown files, one glob, file, or folder per line; a line that starts with ! leaves out a match. |
diagrams | **/*.diagram | check, validate, render: the .diagram files. |
outDir | plotwork-svg | render: the folder the SVGs go to. |
failOn | error | error, warning, or never. |
convertMermaid | false | check and sync: treat each Mermaid fence that converts as a diagram. |
since | empty | drift and generate: a git ref; only the sources whose files changed since it run. |
publishTestResults | true | drift: publish the JUnit report as a test run. |
onDrift | pr | generate: pr, commit, fail, or patch. |
prBranch | plotwork/diagrams | generate: the branch the task owns. |
prTitle | docs: regenerate diagrams from their sources | generate: the pull request's title. |
commitMessage | docs: regenerate diagrams from their sources | generate: the commit message. |
publish | false | generate: publish each source that has a publish section. |
comment | auto | drift and generate: the pull request comment. |
rulesOrg | empty | check and validate: an organisation whose saved style rules apply. |
reportDrift | false | drift and generate: send the run's metadata to the drift dashboard. |
plotworkConnection | empty | The Plotwork service connection with the organisation key. Empty reads PLOTWORK_API_KEY, then signs in as a trusted publisher. |
organization | empty | The organisation to report to and sign in to. Empty is rulesOrg, then brand.organization of the settings, then the key's. |
config | empty | The settings file. Empty finds plotwork.config.json, then .plotwork/config.json. |
workingDirectory | . | The folder to run in, from the sources folder. |
summary | true | Add the Plotwork tab to the run's summary. |
apiUrl | https://api.plotwork.dev | The Plotwork API. |
cliVersion | the release the extension was tested with | The release of @buun_group/plotwork the task runs. |
| Output variable | Meaning |
|---|---|
stale, errors, warnings | The counts the run found. |
files | A JSON array of paths from the repository root: what to sync (check), what has an error (validate), or what the run wrote (sync, render, generate). |
changes | drift and generate: one entry per drifted diagram, with a sentence naming its changes. |
pullRequest | generate: the pull request it opened or updated. |
published | generate with publish: what was published, with links. |
commentUrl | The pull request comment it wrote or edited. |
driftUrl | reportDrift: the drift dashboard the run was sent to. |
signedInAs | The organisation and trusted publisher a run with no key signed in as. |
junitFile | drift: the JUnit report. |
patchFile, changedFiles | generate: the patch, and a JSON array of the files the run changed. |
Give the task a name, such as plotwork, and a later step reads an output as $(plotwork.errors).
9. Versions and agents
The task is PlotworkDiagrams@1; its major version moves only with a change that breaks a pipeline. Each release of the extension pins a stable release of the command line in cliVersion, so an update of the extension never changes your pictures by itself. To try a newer command line first, set cliVersion, run sync with that release on your machine, and commit the pictures in the same change.
A self-hosted agent needs Node 22 or later with npx on its path, git, and the npm registry or a mirror of it (set NPM_CONFIG_REGISTRY on the agent). Signing in as a trusted publisher needs Azure DevOps Services; Azure DevOps Server sets no System.OidcRequestUri.
10. Troubleshooting
| The run says | Why, and what to do |
|---|---|
| Unable to locate executable file: 'npx' | The agent has no Node. Add UseNode@1 with version: 22.x before the task, or install Node 22 or later on a self-hosted agent. |
| The agent did not set System.OidcRequestUri | Azure DevOps Server gives no ID token. Use a Plotwork service connection or PLOTWORK_API_KEY instead of a trusted publisher. |
| reportDrift is on, but there is no Plotwork key | No service connection, no PLOTWORK_API_KEY in the step's environment, and no trusted publisher sign-in. A secret variable reaches the task only through env:; check its name, and that the pipeline may read its variable group. |
oidc.no_trusted_publisher | No entry trusts this run. Check the entry's organisation, project, pipeline id and branch. The audit log's orgs.trusted_publisher.refused event says which did not match. |
oidc.organization_required | More than one Plotwork organisation trusts this pipeline. Set organization to the one to sign in to. |
| Azure Repos refused it: grant the build service | The build service lacks Contribute, Create branch, or Contribute to pull requests, or a branch policy covers prBranch. See Build service permissions. |
| The repository is on GitHub, not Azure Repos | onDrift: pr and commit, and the pull request comment, need Azure Repos. The new diagrams stay in the workspace and the patch is the artifact plotwork. |
| onDrift pr needs a branch, and a pull request build runs on a merge commit | Run generate with pr or commit on a push, a schedule, or a manual run, and drift on pull requests. |
| The pipeline never runs on a pull request | In Azure Repos a pr trigger in the file does nothing. Add the pipeline as a build validation policy of the branch (Project settings, Repositories, Policies, Branch policies). |
| No pull request comment | The build service lacks Contribute to pull requests, or the run is not a pull request build. The run's Plotwork tab has the report either way. |
auth.key_network_refused | The organisation accepts its keys only from some networks, and Microsoft-hosted agents are not in them. Run on a self-hosted agent in an allowed network. |
See also
| Page | Why |
|---|---|
| CI usage | The same check as a script step, on Azure Pipelines and every other CI |
| Diagrams as code | Sources, overrides, publishing, and the drift check |
| Keep diagrams in sync in CI | The GitHub Action |
| Trusted publishers | Reporting drift and publishing with no key |
| Diagrams in GitLab CI/CD | The same check as a GitLab component |
| Repository settings | Defaults for every header, and rules a pull request must pass |
| The drift dashboard | Where a reported run shows |
| Enterprise rollout | The check across many repositories |