OpsDroid – connector, skills, bots

OpsDroid – connector, skills, bots

Hello everybody! My name is Makariy, I am a DevOps engineer in the cross-platform infrastructure team of the corporate superapp VK Teams. Today, I’ll continue the story of how we apply ChatOps practices to our workflows. The first part about the practices themselves and the mini-apps for their implementation can be read here.


Let me remind you that ChatOps is a model of work organization and communication within the team through communication, i.e. through messenger. This approach connects developers, DevOps engineers, QA specialists, support engineers, product managers, analysts and other process participants in a single communication platform. We will consider the open cross-platform framework OpsDroid, its capabilities, write a connector between OpsDroid and VK Teams, and also implement a bot. I’m sure this experience will open you up to new opportunities that ChatOps provides.

ChatOps and corporate messengers

In the previous part, we considered the main principles of building ChatOps:

We also talked about how process automation works using a corporate messenger:

This is where VK Teams comes to our aid – a corporate superapp from VK, which combines a messenger for communicating with colleagues, bots for automation, its own task tracker, video conferences for 100 users and a platform of mini-applications.

Successful ChatOps implementation requires not only choosing the right tools, but also creating a culture where teams are willing to actively use and invest in ChatOps principles.

OpsDroid Open-Source framework

OpsDroid

– This is a cross-platform framework for developing ChatOps solutions. Its main idea is to create a single control center for a bot that can work simultaneously on several platforms, such as Slack, MS Teams, Webex Teams, Facebook Messenger, Telegram, Matrix, Discord and others. This allows teams to use their existing messenger infrastructure to manage operations and interact with OpsDroid.

The interaction between OpsDroid and the messenger platform is done through connectors. They are an abstraction over the APIs and protocols used by various messengers and messaging services. Connectors provide a unified interface for interaction with platforms and allow:

  • receive and send messages,
  • manage channels and users,
  • receive event notifications and respond.

For example, the connection between OpsDroid and VK Teams will require a suitable connector that will connect the Bot API of VK Teams with the functionality of OpsDroid.

OpsDroid also provides a convenient interface for creating bots: in the form of skills – that is, modules that process incoming events, analyze them, interact with external services, and return the result for processing.

You can install OpsDroid in a convenient way for you on your own computer or server. To run the bot, you need to configure OpsDroid according to the instructions:

Linux, MacOS – Use Docker (manual). You can also install OpsDroid without using Docker, simply with:

pip3 install opsdroid[common]

Guide for Windows

.

OpsDroid-VK Teams connector

For VK Teams and OpsDroid communication, you can write a simple one

connector

.

Here is its code

import aiohttp
import asyncio

from opsdroid.connector import Connector
from opsdroid.events import Message


class ConnectorVKTeams(Connector):
"""A connector for VK Teams"""
	
    def __init__(self, config, opsdroid=None):
        # Инициализация коннектора
      
        super().__init__(config, opsdroid=opsdroid)
        self.name = "vkteams"

        self.opsdroid = opsdroid
        self.latest_update = None
        self.listening = True
        self.default_target = self.default_user
        self.session = None
        self._closing = asyncio.Event()
        self.loop = asyncio.get_event_loop()


        self.base_url = config.get("base-url", None)
        self.default_user = config.get("default-user", None)
        self.whitelisted_users = config.get("whitelisted-users", None)
        self.update_interval = config.get("update-interval", 1)
        # без токена бот работать не сможет
        self.token = config["token"]

    async def _get_messages(self):
        # Получение сообщений из ивентов. Ивенты получаем: events/get из Bot API

        data = {
            'token': self.token,
            'pollTime': 30,
            'lastEventId': 1
        }
        if self.latest_update is not None:
            data["lastEventId"] = self.latest_update

        await asyncio.sleep(self.update_interval)
        resp = await self.session.get(self.build_url("events/get"), params=data)

        if 200 == resp.status:
            json = await resp.json()
            await self._parse_message(json)

    async def _parse_message(self, response):
        # Отдаем сообщения, которые нашли в json-е ивента

        for event in response["events"]:
            user = self.get_user(event)
            target = self.get_target(event)
            parsed_event = await self.handle_messages(
                user=user,
                target=target,
                event_id=event.get("eventId"),
                raw_event=event
            )
            if parsed_event:
                if self.handle_user_permission(event, user):
                    await self.opsdroid.parse(parsed_event)
                else:
                    block_message = Message(
                        text="У вас нет доступа к взаимодействию с ботом",
                        user=user,
                        user_id=user,
                        target=target,
                        connector=self
                    )
                    await self.send(block_message)
                self.latest_update = event["eventId"]
            elif "eventId" in event:
                # игнорируем
                self.latest_update = event["eventId"]

It is also necessary to implement methods for sending messages in the connector, coordinating with the Bot API.

Here is the implementation code

import aiohttp

from opsdroid.connector import register_event
from opsdroid.events import Message, File

...


@register_event(Message)
async def send_message(self, message):

data = dict()
data["token"] = self.token
data["chatId"] = message.target
data["text"] = message.text

resp = await self.session.post(self.build_url("messages/sendText"), data=data)


@register_event(File)
async def send_file(self, file_event):

data = aiohttp.FormData()
data.add_field(
      	"token", str(self.token))
data.add_field(
        "chatId", str(file_event.target), content_type="multipart/form-data")
data.add_field(
        "file",
        await file_event.get_file_bytes(),
        content_type="multipart/form-data")

async with aiohttp.ClientSession() as session:
      	resp = await session.post(self.build_url("messages/sendFile"), data=data) 

For more details, see the connector example

repositories

. The connector allows OpsDroid to understand when a new message arrives in VK Teams and receive its content as well as send the message. The connector can be further expanded. For example, add support for all possible

events

from OpsDroid:

from opsdroid.events import JoinGroup

...

if raw_event.get("type") == "newChatMembers":
    return JoinGroup(
        user=f"@[{user}]",
        user_id=user,
        event_id=event_id,
        target=target,
        connector=self,
        raw_event=raw_event,
    )

Or implement support for all types of events available on the platform (for VK Teams, see /events/get).

Implementation code

from opsdroid.events import Message
from . import vkt_events

...

if raw_event['payload']['parts'][0].get("type") == "forward":
return vkt_events.Forward(
        user=f"@[{user}]",
        user_id=user,
        event_id=event_id,
        target=target,
        message=Message(
            text=raw_event['payload']['text'],
            user=f"@[{first_part['payload']['message']['from']['userId']}]",
            user_id=first_part['payload']['message']['from']['userId'],
            connector=self,
            raw_event=first_part,
        ),
        connector=self,
        raw_event=raw_event
)

Expanding the connector increases the skill options your bots will have. In our case, we will add support for the following

events

:

Reply

,

Forward

,

File

,

Voice

,

Stickers

,

Mention

for a new message as well

Edited Message

,

Deleted Message

,

Pinned/Unpinned Message

,

Join/Left chat

for other chat events.

Also, OpsDroid provides ample opportunities for testing. It is accepted to completely cover the new connector with tests.

An example of a bot from OpsDroid

Let’s implement the same bot, which sends a message to the specified email if it was answered with the keyword “send_email”. We will use OpsDroid to create a cross-platform bot that will work in both VK Teams and Slack and, for example, Matrix. The code won’t change much, we’ll wrap it in OpsDroid. Actions that we previously performed with unpacking

payload

of the attached message, transferred to the connector code discussed above, parsing the type event

Reply

. In this case, all we have to do is get the content of the event, which is tied to the main one:

message.linked_event.text

The skill that will be used in our cross-platform bot in the file configuration.yamllooks like this:

from opsdroid.skill import Skill
from opsdroid.matchers import match_regex
from opsdroid.constraints import constrain_connectors


class SendEmailSkill(Skill):

    @match_regex(r'send_email', matching_condition="fullmatch")
    async def send_email(self, message):
        if not isinstance(message, Reply):
            await message.respond("Сделайте реплай на сообщение, "
                                  "которое хотите переслать на имейл")
            return

        message_for_email = f"{message.linked_event.user_id}:\n\n" \
                             f"{message.linked_event.text}\n\n"

        send_email(to=["[email protected]"],
                       subject=f"Сообщение из {message.connector.name}(opsdroid)",
                       text=message_for_email)
        await message.respond(f"Сообщение переслано на имейл")


    @match_regex(r'send_email', matching_condition="fullmatch")
    @constrain_connectors(['slack'])
    async def send_email_slack(self, message):
        slack = self.opsdroid.get_connector('slack')

        thread_timestamp = message.raw_event.get('thread_ts', None)
        replies = await slack.slack_web_client.conversations_replies(
            channel=message.target, ts=thread_timestamp
        )
        messages = replies['messages']

        thread_head = messages[0]
        users_info = await slack.slack_web_client.users_info(user=thread_head['user'])

        message_for_email += f"{users_info.get('user').get('real_name')}:" \
                             f"\n\n{thread_start['text']}\n\n"

        send_email(to=["[email protected]"],
                   subject=f"Сообщение из Slack (opsdroid)",
                   text=remove_emoji(message_for_email))
        await message.respond(f"Сообщение переслано на имейл")

The code for this skill is different for Slack because the Slack connector does not support an event type Reply. Here we use partitioning using constrain_connectorsSo that if the bot received an event in Slack, it would steam it in one way, and if it received it in VK Teams or in Matrix, then in another, more suitable way. Skill codes for VK Teams and Matrix are identical.

For more details, see the example skill in the repository. For a deeper dive, use the full OpsDroid documentation.

Then we need to organize the structure of the project like this:

.
├── Dockerfile*
├── configuration.yaml
└── skills
└── email_sender (name of custom skill)
└── __init__.py

* Dockerfile – if you run OpsDroid with docker.

To do this, execute the following commands:

cd /home/username/Projects

mkdir -p my_opsdroid_project/skills/email_sender

cd my_opsdroid_project

vim __init__.py  <- сюда положим код навыка

Let’s collect our cross-platform robot:

configuration.yaml

logging:
  level: debug
  timestamp: true
 
welcome-message: false
 
# Web server
web:
  host: '0.0.0.0'
  port: 8080
 
## Parsers
parsers: []

## Connectors modules
connectors:
  vkteams:
    token: "VKT_BOT_TOKEN"  # <- токен, который получили у Метабота
    bot-name: "email_sender_bot"  # <- имя бота, которое задали Метаботу при создании
    base-url: "api.my-company.myteam.mail.ru/bot/v1/"  # <- base-url бот-апи
    repo: https://github.com/mboriskin/connector-vkteams  # <- код коннектора

  # как настроить slack - https://docs.opsdroid.dev/en/stable/connectors/slack.html
  slack:
    bot-token: "SLACK_BOT_TOKEN"
    bot-name: "email_sender_bot"
    socket-mode: true
    app-token: "SLACK_APP_TOKEN"

  # как настроить matrix - https://docs.opsdroid.dev/en/stable/connectors/matrix.html
  matrix:
    mxid: "@bot_account_username:matrix.org"
    password: "bot_account_password"
    rooms:
      'main': '#my-corporate-bots:matrix.org'
    nick: "Botty EmailSender"
   

## Skill modules
skills:
  ## Hello (https://github.com/opsdroid/skill-hello)
  hello: {}  # <- как пример, навык по умолчанию
 
  ## Отправить сообщение на имейл
  email_sender:
    path: /opt/opsdroid/skills/email_sender  # <- там лежит навык (если через docker)
    no-cache: true
    app-name: my_opsdroid_vkt

Let’s collect the bot:

opsdroid config -f configuration.yaml build

Let’s start working:

opsdroid start

To run through Docker, download the script

startup.sh

and

Dockerfile

from the repository with examples. Then run the script to build the bot:

IMAGE_NAME="my_opsdroid_vkt" ./startup.sh --build

We can run the script directly (for debugging and monitoring logs):

./startup.sh --run

And in the background:

./startup.sh --runbg

And when the time comes to stop the bot:

./startup.sh --stop

We can also delete the image that has worked:

./startup.sh --remove

After starting the bot, you can write it a personal message or a message in the group chat where it is added, and it will perform the necessary operation by sending a message to the specified email.

For example, in Slack:

In VK Teams:

And in the Matrix:

OpsDroid is a powerful cross-platform framework for developing ChatOps solutions. With the ability to work simultaneously on multiple platforms such as Slack, MS Teams, VK Teams and others, OpsDroid provides teams with flexibility and convenience in managing operations and interacting with bots. The concept of connectors facilitates integration with various services, providing a single interface for interaction. Skills in OpsDroid allow developers to create bot functionality, handle events, and interact with external services. With easy installation and configuration, OpsDroid is an effective tool for developing cross-platform bots that can quickly complete tasks and provide effective user interaction.

Ready-made OpsDroid solutions can be browsed between messengers.

You can find many ready-made solutions for OpsDroid and then integrate them for use in VK Teams or another service.

List of ready-made solutions

  • skill-ssh – a skill for interacting with Linux servers using SSH commands;
  • skill-devops – skill for interaction with Docker and Gitlab;
  • skill-sysanalytics – system monitoring when the OpsDroid instance is running;
  • skill-nginx-rtmp – monitoring the start, stop of nginx-rtmp and the state in general;
  • skill-github-linker – providing links to GitHub Issues and PR when mentioned;
  • skill-jenkins – a skill for interacting with Jenkins;
  • skill-repohook – providing links to events in Git {Hub | Lab};
  • skill-docker-daemon – skill for managing Docker;
  • skill-k8s – a skill for working with Kubernetes;
  • skill-shell – a skill for running shell scripts;
  • skill-yourextip – getting your external IP address in the chat;
  • skill taginfo – a skill for obtaining data from OpenStreetMap;
  • skill-awx – skill for interaction with AWX;
  • skill-grafana-annotation – a skill for creating and viewing Grafana annotations;
  • skill-prometheus-scrape – a skill for collecting metrics through Prometheus directly from the chat;
  • skill-github – a skill for interacting with GitHub;
  • skill-cloudhealth – a skill for interacting with CloudHealth;
  • skill-reminders – for setting reminders;
  • skill-minecraft – a skill for tracking and publishing Minecraft server logs;
  • skill-google-it – to answer with a link to a Google search;
  • skill-iss – skill for determining the location of the ISS;
  • skill-formula1-schedule – processing the “Formula-1” calendar and sending reminders;
  • skill-random – skill for example: display of random events;
  • skill-words – a skill that uses the NLTK word game module;
  • skill-word-of-the-day – send a word of the day from the Oxford English Dictionary.

Conclusion

ChatOps is a model of work organization and communication within the team through messengers, combining various roles and functions into a single communication platform.

As a corporate superapp, VK Teams provides a wide range of tools for communication, automation, task management and video conferencing. The successful implementation of ChatOps requires the creation of an appropriate culture in the team, readiness to use ChatOps tools and principles.

OpsDroid is a cross-platform framework for developing ChatOps solutions. It allows you to create a single centralized bot management system that can work on different platforms such as Slack, MS Teams, VK Teams, Matrix and others. Interaction with messengers is carried out through connectors that provide a unified interface for exchanging messages, managing users and receiving event notifications.

As an example of the use of OpsDroid, a simple bot capable of sending a message to the specified email upon receiving a certain keyword was considered. Due to the flexibility of OpsDroid and its ability to work on different platforms, such a bot can be used both in VK Teams and in other messengers, for example, Slack or Matrix.

We noted the possibility of using ready-made OpsDroid solutions that can be adapted and integrated for use in VK Teams or other messengers. This allows developers to leverage existing capabilities and skills to manage operations and interact with bots.

In general, thanks to OpsDroid and integration with VK Teams, teams can create effective and flexible ChatOps solutions, improving communication, automating routine tasks.

Related posts