Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions azure_pipelines_bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Azure Pipelines Bot for Microsoft Teams
description: Monitor Azure DevOps build completions and enable interactive pipeline management through Microsoft Teams conversations
integrations: ["azurebot"]
categories: ["DevOps"]
tags:
[
"webhook",
"interactive_workflows",
"user_interactions",
"subscribe",
"next_event",
"event_loops",
]
---

# Azure Pipelines Bot

[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?name=azure_pipelines_bot)

This automation connects Azure DevOps pipelines to Microsoft Teams, enabling teams to monitor build completions and interact with pipelines directly from Teams conversations. When a build completes, the bot posts a notification to a Teams channel and allows users to retry failed builds through simple chat commands.

## How It Works

1. Receives webhook notifications when Azure DevOps builds complete
2. Posts build completion messages to a Microsoft Teams channel
3. Listens for user interactions in the Teams conversation
4. Allows users to retry builds by typing "retry" in response to build notifications
5. Queues new builds with the same parameters as the original failed build

## Cloud Usage

1. Initialize your Azure Bot connection
2. Set these required project variables:
- `CHANNEL_CONVO_ID` - Microsoft Teams channel conversation ID
- `AZURE_DEVOPS_PAT` - Azure DevOps Personal Access Token (marked as secret)
- `AZURE_DEVOPS_ORG` - Azure DevOps organization name
3. Deploy project
4. Configure Azure DevOps webhook to point to the generated webhook URL

## Trigger Workflow

The workflow is triggered by Azure DevOps webhooks when builds complete with `eventType='build.complete'`. Users can then interact with the bot by:

- Typing `@<Bot Name> retry` in response to build completion messages to queue a new build
26 changes: 26 additions & 0 deletions azure_pipelines_bot/autokitteh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This YAML file is a declarative manifest that describes the setup
# of an AutoKitteh project that demonstrates how to poll for new emails
Copy link

Copilot AI Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment describes polling for emails, but this project is about Azure DevOps build notifications and Teams integration. Update the comment to accurately describe this project's purpose.

Suggested change
# of an AutoKitteh project that demonstrates how to poll for new emails
# of an AutoKitteh project that integrates Azure DevOps build notifications with Microsoft Teams

Copilot uses AI. Check for mistakes.
version: v1

project:
name: azure_pipelines_bot

vars:
- name: CHANNEL_CONVO_ID
value: ""
- name: AZURE_DEVOPS_PAT
value: ""
secret: true
- name: AZURE_DEVOPS_ORG
value: ""

connections:
- name: bot
integration: azurebot

triggers:
- name: azuredev_webhook
type: webhook
call: program.py:on_azuredev_webhook
event_type: POST
filter: data.body.json.eventType='build.complete'
69 changes: 69 additions & 0 deletions azure_pipelines_bot/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""MS Azure DevOps Pipelines -> MS Teams via Azure Bot"""

from os import getenv
from autokitteh import subscribe, next_event
from autokitteh.azurebot import azurebot_client

from azure.devops.connection import Connection
from azure.devops.v7_1.build.models import Build
from msrest.authentication import BasicAuthentication

Check failure on line 9 in azure_pipelines_bot/program.py

View workflow job for this annotation

GitHub Actions / lint-format-test

Ruff (I001)

azure_pipelines_bot/program.py:3:1: I001 Import block is un-sorted or un-formatted


_CHANNEL_CONVO_ID = getenv("CHANNEL_CONVO_ID")


devops_clients = Connection(
base_url=f"https://dev.azure.com/{getenv('AZURE_DEVOPS_ORG')}",
creds=BasicAuthentication("", getenv("AZURE_DEVOPS_PAT")),
).clients

build_client = devops_clients.get_build_client()

bot_client = azurebot_client("bot")


def on_azuredev_webhook(event):
data = event.data.body.json

activity_id = bot_client.send_conversation_activity(
{
"type": "message",
"text": data["detailedMessage"]["markdown"],
},
conversation_id=_CHANNEL_CONVO_ID,
)["id"]

convo_id = f"{_CHANNEL_CONVO_ID};messageid={activity_id}"

build_id, project_name = (
data["resource"]["id"],
data["resource"]["project"]["name"],
)

print(f"Build completed: {build_id} in {project_name}")

sub = subscribe("bot", filter=f"data.conversation.id='{convo_id}'")

while True:
evt = next_event(sub)
print(evt)

Copy link

Copilot AI Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing evt.text without checking if the event has a text attribute could result in an AttributeError. Add a check to ensure the event contains text before attempting to access it.

Suggested change
if not hasattr(evt, "text") or evt.text is None:
continue

Copilot uses AI. Check for mistakes.
text = evt.text.lower()

Comment on lines +47 to +52
Copy link

Copilot AI Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The infinite loop lacks an exit condition, which could lead to resource exhaustion or difficulty in stopping the process. Consider adding a timeout, maximum iteration count, or explicit break condition for scenarios like conversation inactivity or specific commands.

Suggested change
while True:
evt = next_event(sub)
print(evt)
text = evt.text.lower()
import time
INACTIVITY_TIMEOUT = 300 # seconds (5 minutes)
last_event_time = time.time()
while True:
# Try to get the next event, with a timeout for inactivity
try:
evt = next_event(sub)
last_event_time = time.time()
except Exception as e:
print(f"Error receiving event: {e}")
break
print(evt)
text = evt.text.lower()
# Exit loop if user sends "exit" or "quit"
if "exit" in text or "quit" in text:
bot_client.send_conversation_activity(
{"type": "message", "text": "Conversation ended by user."},
conversation_id=convo_id,
)
break
# Check for inactivity timeout
if time.time() - last_event_time > INACTIVITY_TIMEOUT:
bot_client.send_conversation_activity(
{"type": "message", "text": "Conversation ended due to inactivity."},
conversation_id=convo_id,
)
break

Copilot uses AI. Check for mistakes.
if "retry" in text:
build = build_client.get_build(project=project_name, build_id=build_id)

new_build = build_client.queue_build(
build=Build(
definition=build.definition,
source_branch=build.source_branch,
source_version=build.source_version,
parameters=build.parameters,
),
project=project_name,
)

bot_client.send_conversation_activity(
{"type": "message", "text": f"New build: {new_build.url}"},
conversation_id=convo_id,
)
Loading