Skip to content

feat(devops): add the Elsa.DevOps.AzureDevOps module - #199

Open
RalfvandenBurg wants to merge 1 commit into
elsa-workflows:mainfrom
RalfvandenBurg:feature/azure-devops
Open

RalfvandenBurg wants to merge 1 commit into
elsa-workflows:mainfrom
RalfvandenBurg:feature/azure-devops

Conversation

@RalfvandenBurg

Copy link
Copy Markdown

Adds the Elsa.DevOps.AzureDevOps module, so workflows can drive Azure DevOps repositories, pull requests, work items and builds, and can be driven by them. It follows the structure and conventions of the neighbouring Elsa.DevOps.GitHub module.

Supersedes #125, which was closed because it targeted the obsolete 3.6 release train. This is a fresh branch off main, not a rebase: the retarget turned out to be more than a replay, and the module has grown since. Closes #124.

Tasks

  • Retarget against main instead of release/3.6.0
  • Move to central package management and multi-target net8.0;net9.0;net10.0
  • Move from Elsa 3.7.1 to 3.8.0-preview.5557
  • Adopt the repository's test stack (xunit 2.9.3 + NSubstitute)
  • Address the three review comments left on Feature/add azure dev ops #125
  • Register the module in Elsa.Extensions.sln, Directory.Packages.props and the README table

What it contains

Area Activities
Repositories GetRepository, GetBranch, ListBranches, DisplayRepository
Pull requests CreatePullRequest, GetPullRequest, ListPullRequests, WatchAzureDevOpsPullRequest, DisplayPullRequest
Work items CreateWorkItem, GetWorkItem, UpdateWorkItem, QueryWorkItems, AddWorkItemComment, AddWorkItemHyperlink, AddWorkItemRelation, DisplayWorkItem
Builds GetBuild, ListBuilds, QueueBuild, DisplayBuild

Besides the activities:

  • Service Hook triggers for work item, pull request, build and push events, with a webhook endpoint at webhooks/azure-devops behind a shared-secret basic auth check.
  • Polling workflows per event family, for hosts that Azure DevOps cannot reach. They are off by default and sit behind both a master switch and a per-family switch.
  • Display activities that suspend on a bookmark until a person confirms what they were shown. The display target travels as a string rather than as a shared enum, so a host can add a viewer without every other viewer having to learn the value.
  • Per-user credentials: an activity can run under the PAT of the user who started the workflow (UserTokenSecretNameFormat), so work it creates is attributed to that person rather than to a shared service identity. Off unless configured.

Review comments from #125

All three were fixed before this branch was cut, and the fixes are in this diff:

  • PAT stored in plain text as a dictionary key. AzureDevOpsConnectionFactory now keys the cache on organizationUrl|SHA256(token).
  • VssConnection cached indefinitely without disposal. The factory implements IDisposable and disposes every cached connection.
  • UpdateWorkItem creating connections twice. It now takes the connection once and reuses it.

One thing worth flagging

ISecretResolver looked like the right dependency for reading credentials, but it throws when a secret is absent, and absence is the ordinary case here: the token lookup walks several rungs (the secret named after the calling user, then the configured default) and has to pass over an empty one, and the webhook endpoint has to be able to answer "no credential is configured" rather than fail the request with a 500.

So the module defines IAzureDevOpsSecretReader, a thin adapter over ISecretManager that returns null for a name it does not know. It is registered with TryAdd, so a host that reads credentials from somewhere else can supply its own. If you would rather see a nullable overload on ISecretResolver in elsa-core, I am happy to drop the adapter and follow that instead.

How to verify

dotnet build src/modules/devops/Elsa.DevOps.AzureDevOps/Elsa.DevOps.AzureDevOps.csproj -c Release
dotnet test test/modules/devops/Elsa.DevOps.AzureDevOps.UnitTests/Elsa.DevOps.AzureDevOps.UnitTests.csproj -c Release

295 unit tests, all passing; the module builds for all three target frameworks. The tests are offline — nothing reaches Azure DevOps.

On the size of this PR

CONTRIBUTING asks for one concern per PR, and this is larger than that ideal. It is one module arriving whole rather than several concerns mixed together, but if you would rather review it in pieces, the natural split is:

  1. activities only (roughly what Feature/add azure dev ops #125 was),
  2. Service Hook triggers and the webhook endpoint,
  3. the polling workflows,
  4. the display activities.

Say the word and I will split it that way.

🤖 Generated with Claude Code

Integrates Azure DevOps with Elsa Workflows, following the structure and
conventions of the neighbouring Elsa.DevOps.GitHub module.

Activities cover repositories, pull requests, work items and builds, plus
Service Hook triggers, a webhook endpoint, polling workflows for hosts that
cannot receive webhooks, and display activities that suspend on a bookmark
until a person confirms what they were shown.

Credentials are read through IAzureDevOpsSecretReader, a thin adapter over
ISecretManager. ISecretResolver would be the obvious dependency but it throws
when a secret is absent, and absence is the ordinary case here: the token
lookup walks several rungs and the webhook endpoint has to be able to report
that no credential is configured rather than fail the request.

This supersedes elsa-workflows#125, which targeted the obsolete 3.6 release train.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 14, 2026

Copy link
Copy Markdown

PR author is not in the allowed authors list.

@RalfvandenBurg

Copy link
Copy Markdown
Author

@sfmskywalker as requested a new pr based upon main

@RalfvandenBurg

Copy link
Copy Markdown
Author

@sfmskywalker — a design question before you spend review time on this one.

Roughly a third of this PR (36 files, ~3,200 of 8,800 lines) is polling: five system workflows, four dispatchers, the poll activities, and an IHostedService that starts them at host startup. None of that is the primary way to get events out of Azure DevOps — Service Hooks are, and this PR has those too.

The polling exists as a workaround. Creating a Service Hook subscription needs project-level permission that a workflow author often does not have, and where that is centrally administered, waiting for a ticket is the difference between shipping and not. Polling gets the same events over the REST API with no permission beyond the PAT the activities already use. It is off by default, behind a master switch and a per-family switch, and it deliberately raises the same stimulus as the webhook path, so a trigger cannot tell which one woke it.

So: should it live in its own assembly — Elsa.DevOps.AzureDevOps.Polling, say?

For:

A host that can use webhooks still pays for it today: a hosted service at startup, five workflow definitions appearing in the designer, and the Elsa.Scheduling reference, which nothing outside the polling workflows uses.
It is the part most likely to need per-host tuning (intervals, page sizes, checkpoints), which may warrant its own release cadence.
Elsa.DevOps.GitHub next door is activities-only and references nothing but Elsa. Splitting would keep this module closer to that shape.
Against:

The dispatchers share credential resolution, the event handler and the event types with the webhook path. A split turns an internal seam into a public one, and I would not want that to erode the guarantee that both paths produce an identical stimulus.
Happy to do the split, either in this PR or as a follow-up — whichever you prefer. What is your call?

@sfmskywalker

Copy link
Copy Markdown
Member

Triage: needs review — community/non-draft PR on main with no review decision yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Azure DevOps integration

3 participants