Broadcast message to API consumers
In this guide, we will create a self-service action in Port that executes a GitHub workflow to broadcast a Slack message to all service owners who consume an API. The required form input for this action is a text box with information about the broadcast message.
Prerequisites​
- Install Port's GitHub app by clicking here.
- Configure a Slack app that can post a message to a Slack channel.
- The app should have a
chat:write
bot scope under OAuth & Permissions. - Then, go to the Incoming Webhooks page and create a new webhook, specifying the target channel for the messages.
- The app should have a
- A GitHub repository in which you can trigger a workflow that we will use in this guide.
Below you can find the JSON for the Service
and API
blueprints required for the guide:
Service blueprint (click to expand)
{
"identifier": "service",
"title": "Service",
"icon": "Microservice",
"schema": {
"properties": {
"readme": {
"title": "README",
"type": "string",
"format": "markdown",
"icon": "Book"
},
"url": {
"title": "URL",
"format": "url",
"type": "string",
"icon": "Link"
},
"language": {
"type": "string",
"title": "Language",
"icon": "Git"
},
"badges": {
"type": "array",
"title": "Badges",
"icon": "Git"
},
"slack": {
"icon": "Slack",
"type": "string",
"title": "Slack",
"format": "url"
},
"tier": {
"title": "Tier",
"type": "string",
"description": "How mission-critical the service is",
"enum": [
"Mission Critical",
"Customer Facing",
"Internal Service",
"Other"
],
"enumColors": {
"Mission Critical": "turquoise",
"Customer Facing": "green",
"Internal Service": "darkGray",
"Other": "yellow"
},
"icon": "DefaultProperty"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {
"consumes_api": {
"title": "Consumes API",
"target": "api",
"required": false,
"many": true
}
}
}
API blueprint (click to expand)
{
"identifier": "api",
"title": "API",
"icon": "RestApi",
"schema": {
"properties": {
"status": {
"icon": "DefaultProperty",
"title": "Status",
"type": "string",
"default": "Active",
"enum": [
"Active",
"Deprecated",
"Development",
"Scheduled for Deprecation"
],
"enumColors": {
"Active": "green",
"Deprecated": "red",
"Development": "turquoise",
"Scheduled for Deprecation": "orange"
}
},
"description": {
"title": "Description",
"type": "string",
"icon": "DefaultProperty"
},
"tags": {
"title": "Tags",
"type": "array",
"items": {
"enum": [
"Payments",
"Transactions",
"Refunds",
"Credit Card"
],
"enumColors": {
"Payments": "blue",
"Transactions": "turquoise",
"Refunds": "orange",
"Credit Card": "purple"
},
"type": "string"
},
"icon": "DefaultProperty"
},
"visibility": {
"icon": "DefaultProperty",
"title": "Visibility",
"type": "string",
"default": "Internal",
"enum": [
"Public",
"Internal"
],
"enumColors": {
"Public": "blue",
"Internal": "pink"
}
},
"documentation": {
"title": "Documentation",
"icon": "Confluence",
"type": "string",
"format": "url"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {}
}
Our Service blueprint has a relation to the API blueprint called consumes_api
. When the action is triggered on an API
entity, we will get all Service
entities that are related and send the message to the slack webhook urls configured in them.
Create Github workflow​
Follow these steps to get started:
- Create the following GitHub Action secrets:
PORT_CLIENT_ID
- Port Client ID learn morePORT_CLIENT_SECRET
- Port Client Secret learn more
- Create the Port action in the self-service page on the
API
blueprint with the following JSON definition:
Port Action: Send Announcement (click to expand)
<GITHUB-ORG>
- your GitHub organization or user name.<GITHUB-REPO-NAME>
- your GitHub repository name.
{
"identifier": "send_announcement",
"title": "Send Announcement",
"icon": "Slack",
"userInputs": {
"properties": {
"message": {
"title": "Message",
"type": "string"
}
},
"required": [],
"order": []
},
"invocationMethod": {
"type": "GITHUB",
"org": "<GITHUB-ORG>",
"repo": "<GITHUB-REPO-NAME>",
"workflow": "send-announcement.yml",
"omitUserInputs": false,
"omitPayload": false,
"reportWorkflowStatus": true
},
"trigger": "DAY-2",
"description": "Send Announcement to the consumers of the API",
"requiredApproval": false
}
- In your Github repository, create a python script file under
/scripts/broadcast_messages.py
with the following content:
Python script to broadcast message (click to expand)
Whereas you can simply send a message with the text field, the block kit framework provides a rich pool of components and layouts to design your message and allows you to add interactivity. Try it out here to compose your own blocks. You can then replace the blocks
field in the request below.
import logging
import os
import requests
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PortClient:
def __init__(self, client_id: str, client_secret: str):
self.api_url = "https://api.getport.io"
self.access_token = self.get_token(client_id, client_secret)
self.headers = {
"Authorization": f"Bearer {self.access_token}",
"User-Agent": "port-message-service",
}
def get_token(self, client_id, client_secret):
credentials = {"clientId": client_id, "clientSecret": client_secret}
token_response = requests.post(
f"{self.api_url}/v1/auth/access_token", json=credentials
)
token_response.raise_for_status()
return token_response.json()["accessToken"]
def search_entities(self, query):
search_req = requests.post(
f"{self.api_url}/v1/entities/search",
json=query,
headers=self.headers,
params={},
)
search_req.raise_for_status()
return search_req.json()["entities"]
def send_notification(entity, message, api):
title = entity["title"]
slack_webhook = entity["properties"].get("slack")
if slack_webhook:
payload = {
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"Hi _*{title.lower()}*_ team! :wave: We've made an update to the *{api}*:",
},
},
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f":white_large_square: *Details* \n{message}.",
},
},
],
}
requests.post(slack_webhook, json=payload)
else:
print(f"No Slack webhook found for: {title}")
if __name__ == "__main__":
port_client_id = os.environ.get("PORT_CLIENT_ID")
port_client_secret = os.environ.get("PORT_CLIENT_SECRET")
message = os.environ.get("MESSAGE")
sending_api = os.environ.get("SENDING_API")
print("Initializing Port client", port_client_id, port_client_secret)
port_client = PortClient(port_client_id, port_client_secret)
logger.info(f"Fetching entities for query: sending_api {sending_api},")
search_query = {
"combinator": "and",
"rules": [
{"property": "$blueprint", "operator": "=", "value": "service"},
{
"blueprint": "api",
"operator": "relatedTo",
"value": sending_api,
},
],
}
entities = port_client.search_entities(search_query)
print(f"Found {len(entities)} entities for {sending_api}")
for entity in entities:
print(f"Sending notification to {entity['title']}")
send_notification(entity, message, sending_api)
- Then, create a workflow file under
.github/workflows/lock-service.yml
with the following content:
GitHub workflow to call python script (click to expand)
name: Send Message to Service Owners
on:
workflow_dispatch:
inputs:
message:
description: "Message to send to service owners"
required: true
type: string
port_payload:
required: true
description: "Port's payload, including details for who triggered the action and general context (blueprint, run id, etc...)"
type: string
jobs:
notify_api_consumers:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: Run python script
env:
MESSAGE: ${{ github.event.inputs.message }}
SENDING_API: ${{ fromJson(github.event.inputs.port_payload).payload.entity.identifier }}
PORT_CLIENT_ID: ${{ secrets.PORT_CLIENT_ID }}
PORT_CLIENT_SECRET: ${{ secrets.PORT_CLIENT_SECRET }}
run: |
python3 ./scripts/broadcast_messages.py
- Trigger the actions from the self-service page of your Port application.
Done! You can now broadcast a message to all consumers of an API.