Skip to content

Commit

Permalink
Baselined from internal Repository
Browse files Browse the repository at this point in the history
last_commit:50f1df9c67467914f1f5267cc92f48472e2001e6
  • Loading branch information
GVE Devnet Admin committed Mar 8, 2024
0 parents commit 50159f8
Show file tree
Hide file tree
Showing 15 changed files with 347 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .env example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LINE_CHANNEL_ACCESS_TOKEN=12345678
LINE_CHANNEL_SECRET=12345678
CERTIFICATE=/path/to/fullchain.pem
PRIVATE_KEY=/path/to/privkey.pem
53 changes: 53 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Docker Build & Publish

on:
push:
branches:
- main
- master
tags:
- '*'

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}


jobs:
push:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Setup Docker buildx
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf

- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
*.pem
telinebotvenv/
.DS_Store
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.9-slim-buster
WORKDIR /app
COPY ./requirements.txt /app

# Modify the example paths to pem files for setting up SSL
COPY ${CERTIFICATE} /var/servercredentials/
COPY ${PRIVATE_KEY} /var/servercredentials/
ENV CERTIFICATE=/var/servercredentials/fullchain.pem
ENV PRIVATE_KEY=/var/servercredentials/privkey.pem

RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "./bot_line.py"]
Binary file added IMAGES/0image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added IMAGES/TE_sidebar_scaled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added IMAGES/TEdashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added IMAGES/TEwebhookconfig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added IMAGES/chat_demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added IMAGES/custom_integrations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added IMAGES/line_app_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# gve_devnet_thousandeyes_line_bot
This repo is a flask server that receives ThousandEyes webhook alerts and re-format them to be sent to users via an official account on LINE.


## Contacts
* Kevin Chen

## Solution Components
* ThousandEyes


## Installation/Configuration
1. [Sign up](https://developers.line.biz/en/docs/basics/channel-access-token/) to LINE business account and acquire a channel access token and channel secret.
2. Acquire an SSL certificate to enable HTTPS connection. LINE requires HTTPS for programatic access to its APIs. You can use a certificate from any Certificate Authority, for example [Let's Encrypt](https://letsencrypt.org/getting-started/) and save the certificate and private key files.
3. Configure the environment variables shown below
```python
# Set environment variable in a .env file (see .env example)
LINE_CHANNEL_ACCESS_TOKEN=12345678
LINE_CHANNEL_SECRET=12345678
CERTIFICATE=/path/to/fullchain.pem
PRIVATE_KEY=/path/to/privkey.pem
```
4. Set up a virtual environment and install dependencies
```
python -m venv myvenv
pip install -r requirements.txt
```
5. Configure ThousandEyes webhook integration
From your ThousandEyes dashboard, access the integrations page via the left nagivation pane.
![navigation_pane.png](/IMAGES/TE_sidebar_scaled.png)

Create a new integration by presseing the "New Integration" button.
![navigation_pane.png](/IMAGES/TEdashboard.png)
Select Custom Webhook and you can now configure the integration to point to your bot server. Put in the URL associated with your server and optionally select a preset configuration (generic is used for this example).

![navigation_pane.png](/IMAGES/TEwebhookconfig.png)

A basic configuration is shown in the screenshot above. The URL of the webhook server should be the IP address of your bot server or your custom domain. Select the generic preset and the following fields would be auto-filled with example headers and body.

## Usage

Run the app:

```
python bot_line.py
```
NOTE: the app may require sudo permissions to run

Once the server is running, to register for an alert from ThousandEyes, the user must first add the official account created above, and send a message to the bot. The bot will inform the user that they are now subscribed to ThousandEyes alerts. Now all future ThousandEyes alerts will be forwarded to that user. Multiple users can be added at the same time, however if the server is restarted, the users must re-register with the bot by sending a new subscribe message.
![chat_demo.png](IMAGES/chat_demo.png)
# Screenshots

The bot requires channel access token to authenticate with LINE's servers.
![line_app_diagram.png](IMAGES/line_app_diagram.png)

# Docker
This repo contains a dockerfile, which can be used to create an image to run this app in a Docker container. Please provide the full path to fullchain.pem and privkey.pem in the .env file before running the dockerfile.

### LICENSE

Provided under Cisco Sample Code License, for details see [LICENSE](LICENSE.md)

### CODE_OF_CONDUCT

Our code of conduct is available [here](CODE_OF_CONDUCT.md)

### CONTRIBUTING

See our contributing guidelines [here](CONTRIBUTING.md)

#### DISCLAIMER:
<b>Please note:</b> This script is meant for demo purposes only. All tools/ scripts in this repo are released for use "AS IS" without any warranties of any kind, including, but not limited to their installation, use, or performance. Any use of these scripts and tools is at your own risk. There is no guarantee that they have been through thorough testing in a comparable environment and we are not responsible for any damage or data loss incurred with their use.
You are responsible for reviewing and testing any scripts you run thoroughly before use in any non-testing environment.
142 changes: 142 additions & 0 deletions bot_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""
Copyright (c) 2024 Cisco and/or its affiliates.
This software is licensed to you under the terms of the Cisco Sample
Code License, Version 1.1 (the "License"). You may obtain a copy of the
License at
https://developer.cisco.com/docs/licenses
All use of the material herein must be in accordance with the terms of
the License. All rights not expressly granted by the License are
reserved. Unless required by applicable law or agreed to separately in
writing, software distributed under the License is distributed on an "AS
IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied.
"""


import os
import sys
import json
from argparse import ArgumentParser

from flask import Flask, request, abort
from linebot.v3 import (
WebhookHandler
)
from linebot.v3.exceptions import (
InvalidSignatureError
)
from linebot.v3.webhooks import (
MessageEvent,
TextMessageContent,
)
from linebot.v3.messaging import (
Configuration,
ApiClient,
MessagingApi,
ReplyMessageRequest,
TextMessage,
PushMessageRequest
)

from dotenv import load_dotenv

load_dotenv()
user_ids = []

app = Flask(__name__)

# get channel_secret and channel_access_token from your environment variable
channel_secret = os.environ.get('LINE_CHANNEL_SECRET')
channel_access_token = os.environ.get('LINE_CHANNEL_ACCESS_TOKEN')
if channel_secret is None:
print('Specify LINE_CHANNEL_SECRET as environment variable.')
sys.exit(1)
if channel_access_token is None:
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
sys.exit(1)

handler = WebhookHandler(channel_secret)

configuration = Configuration(
access_token=channel_access_token
)


def parse_alert(alert_body):
alert_id = alert_body['alert']['id']
alert_name = alert_body['alert']['test']['name']
alert_domain = alert_body['alert']['targets']
alert_rule = alert_body['alert']["rule"]["expression"]
alert_details = len(alert_body["alert"]["details"])
alert_message = "alert id: " + \
str(alert_id) + "\nalert name: " + str(alert_name) + "\nalert domain: " + str(alert_domain) + \
"\nalert rule: " + str(alert_rule) + \
"\nalert details: " + str(alert_details)
return alert_message


@app.route("/", methods=['POST'])
def verifyServer():
# test webhook endpoint
return 'OK'


@app.route("/te_alerts", methods=['POST'])
def receive_te_alerts():
alert_body = request.get_json()
alert_message = parse_alert(alert_body)
print("received webhook from TE.")
for user in user_ids:
with ApiClient(configuration) as api_client:
line_bot_api = MessagingApi(api_client)
line_bot_api.push_message(PushMessageRequest(
to=str(user), messages=[TextMessage(text=alert_message)]))

return 'OK'


@ app.route("/callback", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']

# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
print(body)

# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)

return 'OK'


@ handler.add(MessageEvent, message=TextMessageContent)
def message_text(event):
with ApiClient(configuration) as api_client:
line_bot_api = MessagingApi(api_client)
line_bot_api.reply_message_with_http_info(
ReplyMessageRequest(
reply_token=event.reply_token,
messages=[TextMessage(
text="you are now subscribed to ThousandEyes Alert")]
)
)
user_ids.append(event.source.user_id)
# line_bot_api.push_message(PushMessageRequest(
# to=event.source.user_id, messages=[TextMessage(text="TE Alerts")]))


if __name__ == "__main__":
arg_parser = ArgumentParser(
usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
)
arg_parser.add_argument('-p', '--port', default=5100, help='port')
arg_parser.add_argument('-d', '--debug', default=False, help='debug')
options = arg_parser.parse_args()

app.run(host='0.0.0.0', debug=options.debug, port=options.port, ssl_context=(
os.environ.get("CERTIFICATE"), os.environ.get("PRIVATE_KEY")))
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: "3.5"

services:
line-bot:
container_name: line_bot_server
# environment:
volumes:
- /usr/src/app
command: python bot_line.py
build:
context: .

restart: "always"
44 changes: 44 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
aenum==3.1.15
aiohttp==3.9.1
aiosignal==1.3.1
annotated-types==0.6.0
async-timeout==4.0.3
attrs==23.2.0
autopep8==2.0.4
blinker==1.7.0
certifi==2023.11.17
chardet==3.0.4
charset-normalizer==3.3.2
click==8.1.7
colorful==0.5.6
Deprecated==1.2.14
Flask==3.0.1
frozenlist==1.4.1
future==0.18.3
gunicorn==20.0.4
idna==2.10
itsdangerous==2.1.2
Jinja2==3.1.3
line-bot-sdk==3.7.0
MarkupSafe==2.1.4
multidict==6.0.4
pi==0.1.2
prettyprinter==0.18.0
pycodestyle==2.11.1
pydantic==2.5.3
pydantic_core==2.14.6
Pygments==2.17.2
PyJWT==2.8.0
python-dateutil==2.8.2
python-dotenv==1.0.1
PyYAML==5.3.1
requests==2.31.0
requests-toolbelt==1.0.0
six==1.16.0
tomli==2.0.1
typing_extensions==4.9.0
urllib3==2.1.0
webexteamssdk==1.6
Werkzeug==3.0.1
wrapt==1.16.0
yarl==1.9.4

0 comments on commit 50159f8

Please sign in to comment.