Add SNS format
This commit is contained in:
parent
e4308923b4
commit
b9e8747808
7 changed files with 1029 additions and 195 deletions
33
ops_bot/aws.py
Normal file
33
ops_bot/aws.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import json
|
||||||
|
from typing import Any, Tuple
|
||||||
|
|
||||||
|
from ops_bot.common import urgency_color
|
||||||
|
|
||||||
|
|
||||||
|
def handle_subscribe_confirm(payload: Any) -> Tuple[str, str]:
|
||||||
|
message = payload.get("Message")
|
||||||
|
url = payload.get("SubscribeURL")
|
||||||
|
plain = f"{message}\n\n{url}"
|
||||||
|
return plain, plain
|
||||||
|
|
||||||
|
|
||||||
|
def handle_notification(payload: Any) -> Tuple[str, str]:
|
||||||
|
message = payload.get("Message")
|
||||||
|
subject = payload.get("Subject")
|
||||||
|
|
||||||
|
plain = f"{subject}\n{message}"
|
||||||
|
color = urgency_color("high")
|
||||||
|
formatted = (
|
||||||
|
f"<strong><font color={color}>{subject}</font></strong>\n<p>{message}</p>"
|
||||||
|
)
|
||||||
|
return plain, formatted
|
||||||
|
|
||||||
|
|
||||||
|
def parse_sns_event(payload: Any) -> Tuple[str, str]:
|
||||||
|
if payload.get("Type") == "SubscriptionConfirmation":
|
||||||
|
return handle_subscribe_confirm(payload)
|
||||||
|
elif payload.get("Type") == "UnsubscribeConfirmation":
|
||||||
|
return handle_subscribe_confirm(payload)
|
||||||
|
elif payload.get("Type") == "Notification":
|
||||||
|
return handle_notification(payload)
|
||||||
|
raise Exception("Unnown SNS payload type")
|
||||||
6
ops_bot/common.py
Normal file
6
ops_bot/common.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
def urgency_color(urgency: str) -> str:
|
||||||
|
if urgency == "high":
|
||||||
|
return "#dc3545" # red
|
||||||
|
else:
|
||||||
|
return "#ffc107" # orange
|
||||||
|
# return "#17a2b8" # blue
|
||||||
|
|
@ -8,7 +8,7 @@ from fastapi import Depends, FastAPI, HTTPException, Request, status
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
from ops_bot import pagerduty
|
from ops_bot import aws, pagerduty
|
||||||
from ops_bot.matrix import MatrixClient, MatrixClientSettings
|
from ops_bot.matrix import MatrixClient, MatrixClientSettings
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,22 +77,27 @@ def get_destination(bot_settings: BotSettings, routing_key: str) -> Optional[str
|
||||||
return bot_settings.routing_keys.get(routing_key, None)
|
return bot_settings.routing_keys.get(routing_key, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def receive_helper(request: Request):
|
||||||
|
payload: Any = await request.json()
|
||||||
|
routing_key = request.path_params["routing_key"]
|
||||||
|
room_id = get_destination(request.app.state.bot_settings, routing_key)
|
||||||
|
if room_id is None:
|
||||||
|
logging.error(f"unknown routing key {routing_key}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Unknown routing key"
|
||||||
|
)
|
||||||
|
payload_str = json.dumps(payload, sort_keys=True, indent=2)
|
||||||
|
logging.info(f"received payload: \n {payload_str}")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
@app.post("/hook/pagerduty/{routing_key}")
|
@app.post("/hook/pagerduty/{routing_key}")
|
||||||
async def pagerduty_hook(
|
async def pagerduty_hook(
|
||||||
request: Request,
|
request: Request,
|
||||||
matrix_client: MatrixClient = Depends(get_matrix_service),
|
matrix_client: MatrixClient = Depends(get_matrix_service),
|
||||||
auth: bool = Depends(authorize),
|
auth: bool = Depends(authorize),
|
||||||
) -> Dict[str, str]:
|
) -> Dict[str, str]:
|
||||||
payload: Any = await request.json()
|
room_id, payload = await receive_helper(request)
|
||||||
room_id = get_destination(
|
|
||||||
request.app.state.bot_settings, request.path_params["routing_key"]
|
|
||||||
)
|
|
||||||
if room_id is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Unknown routing key"
|
|
||||||
)
|
|
||||||
payload_str = json.dumps(payload, sort_keys=True, indent=2)
|
|
||||||
logging.info(f"received pagerduty payload: \n {payload_str}")
|
|
||||||
msg_plain, msg_formatted = pagerduty.parse_pagerduty_event(payload)
|
msg_plain, msg_formatted = pagerduty.parse_pagerduty_event(payload)
|
||||||
await matrix_client.room_send(
|
await matrix_client.room_send(
|
||||||
room_id,
|
room_id,
|
||||||
|
|
@ -102,6 +107,22 @@ async def pagerduty_hook(
|
||||||
return {"message": msg_plain, "message_formatted": msg_formatted}
|
return {"message": msg_plain, "message_formatted": msg_formatted}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/hook/aws-sns/{routing_key}")
|
||||||
|
async def aws_sns_hook(
|
||||||
|
request: Request,
|
||||||
|
matrix_client: MatrixClient = Depends(get_matrix_service),
|
||||||
|
auth: bool = Depends(authorize),
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
room_id, payload = await receive_helper(request)
|
||||||
|
msg_plain, msg_formatted = aws.parse_sns_event(payload)
|
||||||
|
await matrix_client.room_send(
|
||||||
|
room_id,
|
||||||
|
msg_plain,
|
||||||
|
message_formatted=msg_formatted,
|
||||||
|
)
|
||||||
|
return {"message": msg_plain, "message_formatted": msg_formatted}
|
||||||
|
|
||||||
|
|
||||||
def start_dev() -> None:
|
def start_dev() -> None:
|
||||||
uvicorn.run("ops_bot.main:app", port=1111, host="127.0.0.1", reload=True)
|
uvicorn.run("ops_bot.main:app", port=1111, host="127.0.0.1", reload=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import json
|
import json
|
||||||
from typing import Any, Tuple
|
from typing import Any, Tuple
|
||||||
|
|
||||||
|
from ops_bot.common import urgency_color
|
||||||
def urgency_color(urgency: str) -> str:
|
|
||||||
if urgency == "high":
|
|
||||||
return "#dc3545" # red
|
|
||||||
else:
|
|
||||||
return "#ffc107" # orange
|
|
||||||
# return "#17a2b8" # blue
|
|
||||||
|
|
||||||
|
|
||||||
def parse_pagerduty_event(payload: Any) -> Tuple[str, str]:
|
def parse_pagerduty_event(payload: Any) -> Tuple[str, str]:
|
||||||
|
|
|
||||||
1068
poetry.lock
generated
1068
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,12 +14,13 @@ Markdown = "^3.4.1"
|
||||||
pydantic = {extras = ["dotenv"], version = "^1.9.1"}
|
pydantic = {extras = ["dotenv"], version = "^1.9.1"}
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "^5.2"
|
pytest = "^7.2.0"
|
||||||
black = "^22.6.0"
|
black = "^22.10.0"
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
mypy = "^0.971"
|
mypy = "^0.991"
|
||||||
bandit = "^1.7.4"
|
bandit = "^1.7.4"
|
||||||
flake8 = "^4.0.1"
|
flake8 = "^6.0.0"
|
||||||
|
flake8-black = "^0.3.5"
|
||||||
types-Markdown = "^3.4.0"
|
types-Markdown = "^3.4.0"
|
||||||
types-termcolor = "^1.1.5"
|
types-termcolor = "^1.1.5"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,62 @@
|
||||||
|
import json
|
||||||
from ops_bot import __version__
|
from ops_bot import __version__
|
||||||
|
from ops_bot import aws
|
||||||
|
|
||||||
|
|
||||||
def test_version() -> None:
|
def test_version() -> None:
|
||||||
assert __version__ == "0.1.0"
|
assert __version__ == "0.1.0"
|
||||||
|
|
||||||
|
sns_subscribtion_unsubscribe = """{
|
||||||
|
"Type" : "UnsubscribeConfirmation",
|
||||||
|
"MessageId" : "47138184-6831-46b8-8f7c-afc488602d7d",
|
||||||
|
"Token" : "2336412f37...",
|
||||||
|
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
|
||||||
|
"Message" : "You have chosen to deactivate subscription arn:aws:sns:us-west-2:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55.\\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.",
|
||||||
|
"SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb6...",
|
||||||
|
"Timestamp" : "2012-04-26T20:06:41.581Z",
|
||||||
|
"SignatureVersion" : "1",
|
||||||
|
"Signature" : "EXAMPLEHXgJm...",
|
||||||
|
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"
|
||||||
|
}"""
|
||||||
|
sns_subscribtion_confirm = """{
|
||||||
|
"Type" : "SubscriptionConfirmation",
|
||||||
|
"MessageId" : "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",
|
||||||
|
"Token" : "2336412f37...",
|
||||||
|
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
|
||||||
|
"Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\\nTo confirm the subscription, visit the SubscribeURL included in this message.",
|
||||||
|
"SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37...",
|
||||||
|
"Timestamp" : "2012-04-26T20:45:04.751Z",
|
||||||
|
"SignatureVersion" : "1",
|
||||||
|
"Signature" : "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",
|
||||||
|
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
sns_notification = """{
|
||||||
|
"Type" : "Notification",
|
||||||
|
"MessageId" : "22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324",
|
||||||
|
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
|
||||||
|
"Subject" : "My First Message",
|
||||||
|
"Message" : "Hello world!",
|
||||||
|
"Timestamp" : "2012-05-02T00:54:06.655Z",
|
||||||
|
"SignatureVersion" : "1",
|
||||||
|
"Signature" : "EXAMPLEw6JRN...",
|
||||||
|
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
|
||||||
|
"UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
def test_aws_sns_notification() -> None:
|
||||||
|
r = aws.parse_sns_event(json.loads(sns_notification))
|
||||||
|
assert r[0] == "My First Message\nHello world!"
|
||||||
|
assert r[1] == "<strong><font color=#dc3545>My First Message</font></strong>\n<p>Hello world!</p>"
|
||||||
|
|
||||||
|
def test_aws_sns_subscribe() -> None:
|
||||||
|
r = aws.parse_sns_event(json.loads(sns_subscribtion_confirm))
|
||||||
|
print(r)
|
||||||
|
expected = 'You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\nTo confirm the subscription, visit the SubscribeURL included in this message.\n\nhttps://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37...'
|
||||||
|
assert r == (expected, expected)
|
||||||
|
|
||||||
|
def test_aws_sns_unsubscribe() -> None:
|
||||||
|
r = aws.parse_sns_event(json.loads(sns_subscribtion_unsubscribe))
|
||||||
|
print(r)
|
||||||
|
expected = 'You have chosen to deactivate subscription arn:aws:sns:us-west-2:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.\n\nhttps://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37fb6...'
|
||||||
|
assert r == (expected, expected)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue