> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Azure DevOps Pipelines

Azure DevOps integration actions allow workflows to trigger pipelines and manage pull requests directly using your installed Azure DevOps integration.

Prerequisites​

  • An Azure DevOps integration installed in your Port organization in Single Account mode.
  • Actions processing must be enabled on your integration:
    • Hosted by Port / UI installations: actions processing is enabled automatically.
    • Self-hosted (Helm or Docker): actions processing is disabled by default and must be explicitly enabled. See Enable actions processing (self-hosted) below.
  • Your Azure DevOps credentials must be able to perform the operations you invoke:
    • Pipeline actions: queue pipeline runs and manage service hooks (webhooks) used for status reporting.
    • Pull request actions: read and write code in the target repository.
  • For pipeline actions, the pipeline you want to trigger must exist in the target Azure DevOps project.
  • To run Azure DevOps actions as the user who triggered the Port workflow (not the integration's shared credentials), enable identity propagation on the node. Self-hosted Ocean only.

Enable actions processing (self-hosted)​

If you installed the Azure DevOps Ocean integration as hosted by Port (via the UI), actions processing is enabled automatically and you can skip to Configuration.

For self-hosted deployments (Kubernetes/Helm or Docker), the actions processor is disabled by default. You need to enable it explicitly using the flags below, depending on how you deployed the integration:

Pass the following flags when installing or upgrading the Helm chart:

helm upgrade --install azure-devops port-labs/port-ocean \
--set actionsProcessor.enabled=true \
--set liveEvents.baseUrl=<YOUR_INTEGRATION_BASE_URL> \
# ... rest of your values
  • actionsProcessor.enabled=true - enables the actions processor so the integration can receive and execute Azure DevOps pipeline trigger requests from Port.
  • liveEvents.baseUrl=<YOUR_INTEGRATION_BASE_URL> - sets OCEAN__BASE_URL, which is required when reportPipelineStatus is enabled (the default). Port uses this URL to receive webhook events from Azure DevOps and update the action run status in real time. The URL must be reachable from Azure DevOps.
What is reportPipelineStatus?

By default, Port automatically updates the workflow node status when the triggered Azure DevOps pipeline finishes. This requires Azure DevOps to send a pipeline run-state-changed webhook event back to the integration. If the integration's base URL is not set, Port cannot receive this callback and the run status will not be updated automatically.

To disable this behavior, set reportPipelineStatus: false in the execution properties of your action node, or turn off the Report pipeline status toggle in the UI.

Configuration​

All Azure DevOps integration actions share the same node structure:

FieldTypeDescription
typestringRequired. Must be "INTEGRATION_ACTION"
installationIdstringRequired. Your Azure DevOps integration installation ID
integrationProviderstringRequired. Must be "azure-devops"
integrationInvocationTypeenumRequired. One of the values listed in available actions
integrationActionExecutionPropertiesobjectRequired. Properties for the selected action

The remaining sections describe the execution properties of each action.

Property value types

Execution properties are validated strictly. Pass strings to string properties (including pullRequestId and line) and booleans to boolean properties. A boolean property that receives the string "true" is rejected.

Available actions​

Each action is selected with the integrationInvocationType field:

ActionInvocation typeDescription
Trigger a pipelinetrigger_pipelineQueue a pipeline run and optionally wait for its result
Create a pull requestcreate_pull_requestOpen a pull request between two branches
Update a pull requestupdate_pull_requestChange a pull request's title, description, status, target branch, or completion options
Close a pull requestclose_pull_requestAbandon a pull request without merging it
Merge a pull requestmerge_pull_requestComplete a pull request by merging its source branch
Add a pull request labelupdate_pull_request_labelsAdd a label to a pull request
Create a pull request comment threadcreate_pull_request_threadAdd a comment thread to a pull request, optionally anchored to a file and line

The remaining sections describe the execution properties of each action.

Pipeline actions​

Trigger a pipeline​

Queues a run of an existing Azure DevOps pipeline. Set integrationInvocationType to trigger_pipeline.

Execution properties

FieldTypeDescription
projectstringRequired. Azure DevOps project name or ID that contains the pipeline
pipelineIdstringRequired. Numeric pipeline ID (for example, 12 from https://dev.azure.com/org/project/_build?definitionId=12)
branchstringBranch (ref) to run the pipeline on (for example, main). When omitted, the pipeline's default branch is used
templateParametersobjectRuntime template parameters declared in the pipeline YAML
variablesobjectQueue-time variables to pass to the pipeline run
reportPipelineStatusbooleanWhether to report pipeline completion status back to Port (default: true)

Basic example

Trigger an Azure DevOps pipeline (click to expand)

Trigger an Azure DevOps pipeline, passing environment and version from the trigger inputs:

{
"identifier": "trigger-azure-devops-pipeline",
"title": "Trigger Azure DevOps Pipeline",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "your-installation-id",
"integrationProvider": "azure-devops",
"integrationInvocationType": "trigger_pipeline",
"integrationActionExecutionProperties": {
"project": "My Project",
"pipelineId": "12",
"branch": "{{ .outputs.trigger.branch }}",
"templateParameters": {
"environment": "{{ .outputs.trigger.environment }}",
"version": "{{ .outputs.trigger.version }}"
}
}
}
}

Template parameters and variables

Template parameters and variables (click to expand)

Pass runtime values into your pipeline with templateParameters and variables. Values can be static or dynamically resolved from workflow outputs:

{
"integrationActionExecutionProperties": {
"project": "My Project",
"pipelineId": "12",
"branch": "main",
"templateParameters": {
"environment": "{{ .outputs.trigger.environment }}",
"version": "{{ .outputs.trigger.version }}"
},
"variables": {
"DRY_RUN": "{{ .outputs.trigger.dryRun | tostring }}"
}
}
}
  • templateParameters map to parameters declared in your Azure Pipelines YAML (parameters:).
  • variables are queue-time variables sent to the Azure DevOps Run Pipeline API.

Status reporting

Status reporting (click to expand)

By default, Port monitors the triggered pipeline and updates the workflow node status when it completes. To opt out:

{
"integrationActionExecutionProperties": {
"project": "My Project",
"pipelineId": "12",
"reportPipelineStatus": false
}
}

When reportPipelineStatus is false, Port does not track the pipeline's completion at all: the node is left in progress after the pipeline is queued, and it is up to you to report its outcome back to Port (for example, from a step in your Azure Pipelines YAML that calls the Port API).

When reportPipelineStatus is true (the default), Port correlates the Azure DevOps ms.vss-pipelines.run-state-changed-event webhook back to the workflow node and reports success or failure when the run completes.

Pull request actions​

Manage a pull request​

Create, update, close, or merge a pull request.

Opens a pull request between two branches. Set integrationInvocationType to create_pull_request.

Execution properties

FieldTypeDescription
organizationstringRequired. Azure DevOps organization name. Must match the organization configured on the integration
projectstringRequired. Project name or ID that contains the repository
repositoryIdstringRequired. Repository name or ID
titlestringRequired. Title of the new pull request
sourceRefNamestringRequired. Branch to merge from (for example, feature/add-caching or refs/heads/feature/add-caching)
targetRefNamestringRequired. Branch to merge into (for example, main or refs/heads/main)
descriptionstringDescription of the pull request

Branch names are normalized for you, so main and refs/heads/main are equivalent.

Basic example

Create a pull request (click to expand)

Open a pull request from a feature branch into main:

{
"identifier": "open-pull-request",
"title": "Open Pull Request",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "your-installation-id",
"integrationProvider": "azure-devops",
"integrationInvocationType": "create_pull_request",
"integrationActionExecutionProperties": {
"organization": "my-org",
"project": "My Project",
"repositoryId": "my-service",
"title": "{{ .outputs.trigger.title }}",
"sourceRefName": "{{ .outputs.trigger.branch }}",
"targetRefName": "main",
"description": "Opened from Port by {{ .trigger.by.user.email }}"
}
}
}

Add a pull request label​

Adds a label to an existing pull request. Set integrationInvocationType to update_pull_request_labels.

Each run adds one label. To apply several labels, use multiple action nodes or run the action more than once.

Execution properties

FieldTypeDescription
organizationstringRequired. Azure DevOps organization name. Must match the organization configured on the integration
projectstringRequired. Project name or ID that contains the repository
repositoryIdstringRequired. Repository name or ID
pullRequestIdstringRequired. ID of the pull request to label
labelstringRequired. Name of the label to add

Basic example

Add a pull request label (click to expand)
{
"identifier": "label-pull-request",
"title": "Add Pull Request Label",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "your-installation-id",
"integrationProvider": "azure-devops",
"integrationInvocationType": "update_pull_request_labels",
"integrationActionExecutionProperties": {
"organization": "my-org",
"project": "My Project",
"repositoryId": "my-service",
"pullRequestId": "{{ .outputs.trigger.pullRequestId }}",
"label": "approved"
}
}
}

Create a pull request comment thread​

Adds a comment thread to a pull request. Set integrationInvocationType to create_pull_request_thread.

Execution properties

FieldTypeDescription
projectstringRequired. Project name or ID that contains the repository
repositoryIdstringRequired. Repository name or ID
pullRequestIdstringRequired. ID of the pull request to comment on
contentstringRequired. Body of the first comment in the thread. Supports Markdown
statusstringThread status: active, fixed, wontFix, closed, byDesign, or pending (default: active)
filePathstringPath of the file to anchor the thread to (for example, /src/main.py). When omitted, the thread is not anchored
linestringLine number in filePath to anchor the thread to, starting at 1. Requires filePath. When omitted, the thread is anchored to the file as a whole

Each run creates a new thread, so a node that runs more than once on the same pull request adds a comment each time rather than editing an existing one.

Basic example

Create a pull request comment thread (click to expand)

Post a review summary produced by an earlier AI node, anchored to the file it refers to:

{
"identifier": "comment-on-pull-request",
"title": "Comment on Pull Request",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "your-installation-id",
"integrationProvider": "azure-devops",
"integrationInvocationType": "create_pull_request_thread",
"integrationActionExecutionProperties": {
"project": "My Project",
"repositoryId": "my-service",
"pullRequestId": "{{ .outputs.trigger.pullRequestId }}",
"content": "{{ .outputs.review.summary }}",
"status": "active",
"filePath": "/src/main.py",
"line": "42"
}
}
}

Complete workflow examples​

Deploy a service with a pipeline (click to expand)

A self-service deployment workflow that triggers an Azure DevOps pipeline and updates the service entity on completion:

{
"identifier": "deploy-with-azure-devops",
"title": "Deploy Service with Azure DevOps",
"icon": "AzureDevops",
"description": "Trigger a deployment pipeline using Azure DevOps",
"nodes": [
{
"identifier": "trigger",
"title": "Request Deployment",
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": {
"properties": {
"service": {
"type": "string",
"format": "entity",
"blueprint": "service",
"title": "Service"
},
"environment": {
"type": "string",
"title": "Environment",
"enum": ["staging", "production"]
},
"version": {
"type": "string",
"title": "Version",
"description": "Git tag or commit SHA"
}
},
"required": ["service", "environment", "version"]
}
}
},
{
"identifier": "trigger-pipeline",
"title": "Trigger Azure DevOps Pipeline",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "ado-integration-123",
"integrationProvider": "azure-devops",
"integrationInvocationType": "trigger_pipeline",
"integrationActionExecutionProperties": {
"project": "My Project",
"pipelineId": "12",
"branch": "{{ .outputs.trigger.version }}",
"templateParameters": {
"environment": "{{ .outputs.trigger.environment }}",
"version": "{{ .outputs.trigger.version }}"
},
"reportPipelineStatus": true
}
}
},
{
"identifier": "update-entity",
"title": "Update Service Status",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "service",
"mapping": {
"identifier": "{{ .outputs.trigger.service }}",
"properties": {
"lastDeployedVersion": "{{ .outputs.trigger.version }}",
"lastDeployedEnvironment": "{{ .outputs.trigger.environment }}",
"lastDeployedAt": "{{ now | todateiso8601 }}"
}
}
}
}
],
"connections": [
{
"sourceIdentifier": "trigger",
"targetIdentifier": "trigger-pipeline"
},
{
"sourceIdentifier": "trigger-pipeline",
"targetIdentifier": "update-entity"
}
]
}
Open and announce a pull request (click to expand)

A self-service workflow that opens a pull request and adds a comment thread linking back to the requester:

{
"identifier": "open-pull-request-with-azure-devops",
"title": "Open Pull Request with Azure DevOps",
"icon": "AzureDevops",
"description": "Open a pull request and announce who requested it",
"nodes": [
{
"identifier": "trigger",
"title": "Request Pull Request",
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": {
"properties": {
"repository": {
"type": "string",
"title": "Repository"
},
"branch": {
"type": "string",
"title": "Source branch"
},
"title": {
"type": "string",
"title": "Pull request title"
}
},
"required": ["repository", "branch", "title"]
}
}
},
{
"identifier": "create-pr",
"title": "Create Pull Request",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "ado-integration-123",
"integrationProvider": "azure-devops",
"integrationInvocationType": "create_pull_request",
"integrationActionExecutionProperties": {
"organization": "my-org",
"project": "My Project",
"repositoryId": "{{ .outputs.trigger.repository }}",
"title": "{{ .outputs.trigger.title }}",
"sourceRefName": "{{ .outputs.trigger.branch }}",
"targetRefName": "main",
"description": "Opened from Port by {{ .trigger.by.user.email }}"
}
}
}
],
"connections": [
{
"sourceIdentifier": "trigger",
"targetIdentifier": "create-pr"
}
]
}

Identity propagation​

Identity propagation lets a workflow integration action call the downstream platform as the user who triggered the run.

Availability

Identity propagation is only supported on self-hosted Ocean integrations.

How it works​

When a node run needs authentication, Port sends Ocean a short-lived OIDC token. Ocean checks its vault for a token stored for the triggering user. If none exists, the run pauses and prompts the user to authenticate through an OAuth flow hosted by Ocean. Once approved, Ocean stores the token in the user's vault entry, resumes the run, and executes the action with the user's own credentials.

The user sees a waiting for authentication state on the node, along with an Authenticate action in the workflow run page and in an Inbox notification.

Requirements on Ocean​

  • Actions processing enabled (OCEAN__ACTIONS_PROCESSOR__ENABLED=true, or Helm actionsProcessor.enabled=true).
  • Identity propagation enabled (OCEAN__IDENTITY_PROPAGATION__ENABLED=true, or Helm identityPropagation.enabled=true). Helm fails install or upgrade if this is true without actionsProcessor.enabled and a public liveEvents.baseUrl.
  • A publicly reachable base URL (OCEAN__BASE_URL, or Helm liveEvents.baseUrl). Ocean registers {baseUrl}/v1/oauth-broker/authorize on startup. If the URL isn't public, the OAuth flow cannot be completed.
  • An OAuth application with the downstream provider, using {baseUrl}/v1/oauth-broker/callback as the redirect URI. Provider-specific setup steps are in the Setup section below.
  • OAuth app credentials on the integration. Set identityOauthClientId and identityOauthClientSecret (and identityOauthTenantId for Azure DevOps) in Helm integration.config / integration.secrets, or as OCEAN__INTEGRATION__CONFIG__IDENTITY_OAUTH_* in Docker. Authorize and token URLs come from the integration's existing host or tenant settings.
  • A vault for storing tokens (see below). AWS Secrets Manager is supported out of the box.

Each Ocean process hosts one integration and one OAuth provider. If you run more than one supported integration, each is a separate Ocean deployment with its own OAuth application and vault entries.

Restart the integration after changing OCEAN__BASE_URL so it re-registers its broker URL. A resync alone doesn't do this.

Helm example (OAuth client values are integration-specific; see Setup):

identityPropagation:
enabled: true
actionsProcessor:
enabled: true
liveEvents:
baseUrl: https://ocean.example.com

The vault​

Ocean stores each user's downstream token in a vault, keyed by organization, actor, and integration. Default is AWS Secrets Manager:

OCEAN__IDENTITY_PROPAGATION__VAULT__TYPE="aws_secrets_manager"
OCEAN__IDENTITY_PROPAGATION__VAULT__SECRET_PREFIX="port/tokens"
OCEAN__IDENTITY_PROPAGATION__VAULT__AWS_REGION="<your-region>"

Helm equivalent: identityPropagation.vault.type, identityPropagation.vault.secretPrefix, and identityPropagation.vault.awsRegion. Prefer an IAM role on the pod over static awsAccessKeyId / awsSecretAccessKey.

That role needs secretsmanager:CreateSecret, secretsmanager:PutSecretValue, and secretsmanager:GetSecretValue on {secretPrefix}/* (default port/tokens/*). Ocean writes {secretPrefix}/{orgId}/{actorId}/{target} on the first successful OAuth callback, then updates the same secret later. Without those three actions the callback cannot store the token, and the failure comes from the vault client.

Bringing your own vault​

Ocean only ships an AWS Secrets Manager vault by default. To use a different store (HashiCorp Vault, GCP Secret Manager, your own database, etc.), write your own vault client:

  1. Import VaultClient and TokenRecord from port_ocean.identity_propagation.vault.base.

  2. Create a class that subclasses VaultClient and implements its two methods, read and write, against your store.

  3. In your integration's startup code, assign an instance of your class to ocean.app.vault_client. Ocean calls this instance instead of the built-in AWS client.

    from port_ocean.identity_propagation.vault.base import VaultClient, TokenRecord

    class MyVaultClient(VaultClient):
    async def read(self, org_id: str, actor_id: str, target: str) -> TokenRecord | None:
    ... # look up and return the stored record, or None if there isn't one

    async def write(self, org_id: str, actor_id: str, target: str, record: TokenRecord) -> None:
    ... # store the record

    ocean.app.vault_client = MyVaultClient()

    TokenRecord holds access_token, an optional refresh_token, and an optional expires_at.

Networking requirements​

  • OCEAN__BASE_URL (Helm liveEvents.baseUrl) must be reachable from the browsers of everyone who triggers workflows with identity propagation enabled.
  • The same URL must be reachable from the downstream provider, since it redirects the user's browser back there after approval.
  • After OAuth, Ocean redirects the browser to Port's app. Set Helm port.appUrl (OCEAN__PORT__APP_URL) when that URL cannot be derived from port.baseUrl (for example staging, where api. does not map to app.).

Setup​

Azure DevOps authenticates through Microsoft Entra ID, not Azure DevOps itself.

If your integration already uses a Microsoft Entra ID app for Service Principal authentication (Multiple Accounts mode), you can reuse it, just add the redirect URI below. Otherwise, register a new app:

  1. Go to the Microsoft Entra admin center > Identity > Applications > App registrations > New registration.

  2. Platform Web, Redirect URI {baseUrl}/v1/oauth-broker/callback (your OCEAN__BASE_URL, Helm liveEvents.baseUrl).

  3. Under Certificates & secrets, generate a client secret.

  4. Copy the Application (client) ID, the secret, and the Directory (tenant) ID from Overview.

  5. Put those values on the integration. Authorize and token URLs are derived from identityOauthTenantId.

    identityPropagation:
    enabled: true
    actionsProcessor:
    enabled: true
    liveEvents:
    baseUrl: https://ocean.example.com
    integration:
    config:
    identityOauthClientId: "<application-client-id>"
    identityOauthTenantId: "<directory-tenant-id>"
    secrets:
    identityOauthClientSecret: "<client-secret>"

    Set enableIdentityPropagation: true on the node's configuration.

Limitations​

  • Single Account mode only: Azure DevOps integration actions are supported when the integration is configured in Single Account mode. Multi-account (service principal across many organizations) is not supported for these actions yet.
  • Run expiry: Runs that remain unprocessed for more than 3 hours are automatically expired and will not be executed.