# Diagrams in Azure Pipelines

> Page: https://docs.plotwork.dev/diagrams-in-azure-pipelines. Index of every page: https://docs.plotwork.dev/llms.txt

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](https://docs.plotwork.dev/diagrams-in-ci.md) does, for anyone who keeps Plotwork diagrams in a repository built by Azure Pipelines, in Azure Repos or on GitHub.

## 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](https://docs.plotwork.dev/cli/ci.md#azure-pipelines) 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](https://docs.plotwork.dev/cli/ci.md#azure-pipelines) 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](https://plotwork.dev/contact) with the name of your Azure DevOps organisation, or run the command line in a `script` step as [CI usage](https://docs.plotwork.dev/cli/ci.md#azure-pipelines) 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

```yaml
trigger: none
pr:
  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/**/*.diagram
```

For 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](https://docs.plotwork.dev/repository-settings.md) 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](https://docs.plotwork.dev/diagrams-as-code.md) describes. Then run the task in `drift` mode:

```yaml
steps:
  - task: UseNode@1
    inputs:
      version: 22.x
  - task: PlotworkDiagrams@1
    inputs:
      mode: drift
```

Each 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:

```yaml
steps:
  - checkout: self
    fetchDepth: 0
  - task: UseNode@1
    inputs:
      version: 22.x
  - task: PlotworkDiagrams@1
    inputs:
      mode: drift
      since: origin/main
```

## 5. 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:

```yaml
trigger: none
pr: 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: pr
```

Azure 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](https://docs.plotwork.dev/diagrams-as-code.md) 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

1. 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.
2. In Azure DevOps, open Project settings, Service connections, New service connection, and choose Plotwork.
3. 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.
4. Set `plotworkConnection` to the connection's name:

```yaml
  - task: PlotworkDiagrams@1
    inputs:
      mode: drift
      reportDrift: true
      plotworkConnection: plotwork
      organization: acme
```

To 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:

```yaml
  - task: PlotworkDiagrams@1
    inputs:
      mode: drift
      reportDrift: true
    env:
      PLOTWORK_API_KEY: $(PLOTWORK_API_KEY)
```

### With no key, as a trusted publisher

1. 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.
2. In the Plotwork portal, open the organisation, choose Keys & CI, then CI/CD. Under Trusted publishers, choose Add trusted publisher, then Azure Pipelines.
3. Fill in the Azure DevOps organisation (`acme` in `https://dev.azure.com/acme`), the project, and the pipeline id. With the [Azure DevOps connector](https://docs.plotwork.dev/connectors/azure-devops.md) connected, choose each from a list instead, and Branch suggests the pipeline's default branch.
4. Optionally name a branch (`main`, or a ref pattern such as `refs/heads/release/*`). Leave it blank for any branch. A pull request build's ref is `refs/pull/<n>/merge`, so an entry limited to `main` refuses pull request runs.
5. 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.
6. 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](https://docs.plotwork.dev/trusted-publishers.md#6-azure-pipelines) 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:

```yaml
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: true
```

It 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](https://docs.plotwork.dev/repository-settings.md). 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](#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](https://docs.plotwork.dev/cli/ci.md) | The same check as a `script` step, on Azure Pipelines and every other CI |
| [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Sources, overrides, publishing, and the drift check |
| [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The GitHub Action |
| [Trusted publishers](https://docs.plotwork.dev/trusted-publishers.md) | Reporting drift and publishing with no key |
| [Diagrams in GitLab CI/CD](https://docs.plotwork.dev/diagrams-in-gitlab-ci.md) | The same check as a GitLab component |
| [Repository settings](https://docs.plotwork.dev/repository-settings.md) | Defaults for every header, and rules a pull request must pass |
| [The drift dashboard](https://docs.plotwork.dev/drift-dashboard.md) | Where a reported run shows |
| [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) | The check across many repositories |
