-
-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added a management bot for github comments
Signed-off-by: Trey <[email protected]>
- Loading branch information
Showing
8 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
venv/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
name: myfinances | ||
main: . | ||
stackConfigDir: . | ||
runtime: | ||
name: python | ||
options: | ||
virtualenv: venv | ||
description: MyFinances Management Bot infrastructure | ||
config: | ||
pulumi:tags: | ||
value: | ||
pulumi:template: aws-python |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
"""An AWS Python Pulumi program""" | ||
|
||
from __future__ import annotations | ||
|
||
import json | ||
from contextlib import closing | ||
|
||
import pulumi | ||
|
||
from pulumi_aws import apigateway, iam | ||
from pulumi_aws import lambda_ | ||
|
||
config = pulumi.Config() | ||
|
||
site_name: str = pulumi.get_project() | ||
account_id = config.get("accountId", ":") | ||
region = config.get("region", "eu-west-2") | ||
tags = {"app": site_name} | ||
|
||
# lambda_layer | ||
lambda_access_role = iam.Role( | ||
"lambda_access_role", | ||
assume_role_policy=json.dumps( | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Action": "sts:AssumeRole", | ||
"Principal": { | ||
"Service": "lambda.amazonaws.com", | ||
}, | ||
"Effect": "Allow", | ||
"Sid": "", | ||
} | ||
], | ||
} | ||
), | ||
) | ||
|
||
lambda_layer = lambda_.LayerVersion( | ||
"lambda_layer", code=pulumi.FileArchive(config.require("lambda_zip_path")), layer_name="PyGithub_for_myfinances_management_bot" | ||
) | ||
|
||
lambda_func = lambda_.Function( | ||
"webhook_lambda", | ||
name="myfinances_github_bot_webhooks", | ||
role=lambda_access_role.arn, | ||
code=pulumi.AssetArchive({".": pulumi.FileArchive("./src")}), | ||
handler="lambda_handler.lambda_handler", | ||
runtime=lambda_.Runtime.PYTHON3D12, | ||
environment=lambda_.FunctionEnvironmentArgs( | ||
variables={"app_id": config.require_secret("app_id"), "private_key": config.require_secret("private_key")} | ||
), | ||
layers=[lambda_layer.arn], | ||
tags={"project": "MyFinancesBot"}, | ||
) | ||
|
||
rest_api = apigateway.RestApi("managementApi") | ||
|
||
# rest_api_main_resource = apigateway.Resource("main_resource", rest_api=rest_api.id, parent_id=rest_api.root_resource_id, path_part="/") | ||
|
||
rest_api_main_resource_post = apigateway.Method( | ||
"main_post_method_for_webhook", | ||
rest_api=rest_api.id, | ||
resource_id=rest_api.root_resource_id, | ||
http_method="POST", | ||
authorization="NONE", | ||
api_key_required=False, | ||
) | ||
|
||
rest_api_lambda_integration = apigateway.Integration( | ||
"lambda_integration", | ||
rest_api=rest_api.id, | ||
resource_id=rest_api.root_resource_id, | ||
http_method="POST", | ||
integration_http_method="POST", | ||
type="AWS", | ||
timeout_milliseconds=4000, | ||
uri=lambda_func.invoke_arn, | ||
) | ||
|
||
api_gw_200_resp = apigateway.MethodResponse( | ||
"200_resp", rest_api=rest_api.id, resource_id=rest_api.root_resource_id, http_method="POST", status_code="200" | ||
) | ||
|
||
api_gw_lambda = lambda_.Permission( | ||
"apigw_lambda", | ||
statement_id="AllowExecutionFromAPIGateway", | ||
action="lambda:InvokeFunction", | ||
function=lambda_func.name, | ||
principal="apigateway.amazonaws.com", | ||
source_arn=pulumi.Output.all(rest_api.id).apply(lambda id: f"arn:aws:execute-api:{region}:{account_id}:{id[0]}/*/POST/"), | ||
) | ||
|
||
deployment = apigateway.Deployment("deployment_resource", rest_api=rest_api.id) | ||
|
||
prod_stage = apigateway.Stage("productionStage", stage_name="production", rest_api=rest_api.id, deployment=deployment.id) | ||
|
||
pulumi.export("invoke_url", prod_stage.invoke_url) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pulumi>=3.0.0,<4.0.0 | ||
pulumi-aws>=6.0.2,<7.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import json, os, base64 | ||
|
||
from github import Github, Issue | ||
from github import Auth | ||
|
||
raw_private_key = os.environ.get("private_key") | ||
PRIVATE_KEY = base64.b64decode(raw_private_key).decode("ascii") | ||
APP_ID = os.environ.get("app_id") | ||
|
||
|
||
def check_if_user_perm_issue(issue: dict, sender: dict, repository: dict): | ||
return issue.get("user", {}).get("id") == sender.get("id") or sender.get("id") == repository.get("owner", {}).get("id") | ||
|
||
|
||
TEMPLATE_ERROR_MESSAGE = """ | ||
{body} | ||
_Replying to @{sender}_ | ||
Invalid command, you inserted {msg_len} out of the {required} required parameters. | ||
<details><summary>Additional Details</summary> | ||
<p> | ||
> Example: `/{example_cmd}` | ||
> You may use `/help` for more commands. | ||
</p> | ||
</details>""" | ||
|
||
|
||
def send_error(issue, *, body, sender, msg_len, required, example_cmd): | ||
return issue.create_comment( | ||
TEMPLATE_ERROR_MESSAGE.format(sender=sender, body=body, msg_len=msg_len, required=required, example_cmd=example_cmd) | ||
) | ||
|
||
|
||
def lambda_handler(event, context): | ||
auth = Auth.AppAuth(APP_ID, PRIVATE_KEY).get_installation_auth(event.get("installation", {}).get("id")) | ||
g = Github(auth=auth) | ||
|
||
ACTION = event.get("action", {}) | ||
ISSUE = event.get("issue", {}) | ||
COMMENT = event.get("comment", {}) | ||
SENDER = event.get("sender", {}) | ||
REPOSITORY = event.get("repository", {}) | ||
|
||
repo = g.get_repo(event.get("repository", {}).get("full_name")) | ||
|
||
if ISSUE and COMMENT and str(SENDER.get("id")) != "171095439": | ||
if ACTION == "created": # sent comment | ||
issue = repo.get_issue(number=ISSUE.get("number")) | ||
msg = COMMENT.get("body", "") | ||
msg_stripped = msg.strip().split(" ") | ||
msg_len = len(msg_stripped) | ||
|
||
if msg_stripped[0] == "/add_label": | ||
if not msg_len == 2: | ||
send_error( | ||
issue, sender=SENDER["login"], body=COMMENT["body"], msg_len=msg_len, required=2, example_cmd="add_label bug" | ||
) | ||
|
||
return g.close() | ||
|
||
issue.add_to_labels(msg_stripped[1]) | ||
issue.create_comment(f"Okay @{SENDER['login']}, I have added the label '{msg_stripped[1]}'") | ||
elif msg_stripped[0] == "/add_labels": | ||
issue.add_to_labels(*msg_stripped[1:]) | ||
issue.create_comment(f"Okay @{SENDER['login']}, I have added the labels \"{', '.join(msg_stripped[1:])}\"") | ||
elif msg_stripped[0] == "/remove_label": | ||
if not msg_len == 2: | ||
send_error( | ||
issue, sender=SENDER["login"], body=COMMENT["body"], msg_len=msg_len, required=2, example_cmd="remove_label bug" | ||
) | ||
|
||
return g.close() | ||
|
||
issue.remove_from_labels(msg_stripped[1]) | ||
issue.create_comment(f"Okay @{SENDER['login']}, I have removed the label \"{msg_stripped[1]}\"") | ||
elif msg_stripped[0] == "/remove_labels": | ||
issue.remove_from_labels(*msg_stripped[1:]) | ||
issue.create_comment(f"Okay @{SENDER['login']}, I have removed the labels \"{', '.join(msg_stripped[1:])}\"") | ||
elif msg_stripped[0] == "/help": | ||
issue.create_comment( | ||
f""" | ||
Hi @{SENDER["login"]}, | ||
<details><summary>My available commands:</summary> | ||
<p> | ||
| Command | Description | Arg Types | Example | | ||
|---------|-------------|--------|--------| | ||
| /add_label | Adds one label | string | /add_label bug | | ||
| /add_labels | Adds multiple labels | list[string] | /add_label bug enhancement | | ||
| /remove_label | Removes one label | string | /remove_label bug | | ||
| /remove_labels | Removes multiple labels | list[string] | /remove_labels bug enhancement | | ||
</p> | ||
</details> | ||
""" | ||
) | ||
|
||
return {"statusCode": 200, "body": json.dumps("Success")} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import base64 | ||
|
||
from django.core.management import BaseCommand | ||
from django.utils.termcolors import colorize | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Encrypt a value" | ||
requires_system_checks = [] | ||
requires_migrations_checks = False | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument("type", type=str, help="file/text", choices=["file", "text"], default="text") | ||
parser.add_argument("value", type=str, help="your value") | ||
# parser.add_argument("encryption", ) | ||
|
||
def handle(self, *args, **kwargs): | ||
if kwargs["type"] == "file": | ||
with open(kwargs["value"], "r") as file: | ||
self.stdout.write(colorize(str(base64.b64encode(file.read().encode("ascii"))), fg="green")) | ||
else: | ||
self.stdout.write(colorize(str(base64.b64encode(kwargs["value"].encode("ascii"))), fg="green")) | ||
|
||
# opts=("bold",))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Management Bot | ||
|
||
1. In terminal, type | ||
``` | ||
cd .github/management_bot/pulumi/ | ||
``` | ||
2. Go to [Github Apps Settings](https://github.com/settings/apps) and press `New GitHub App` | ||
- Any app name | ||
- Any homepage url | ||
- No callback URL | ||
- Set the webhook URL temporarily to anything like https://example.com | ||
- `Create Github App` | ||
3. Now press "edit" on the github app | ||
4. **copy** the `App ID` field (may look like 912345) | ||
5. Now go back to terminal, and type: | ||
``` | ||
pulumi config set --secret app_id <your copied App ID> | ||
``` | ||
6. Scroll down to `Private keys` and press `Generate a private key` | ||
7. Copy the generated path of the file it generated | ||
8. Open a new terminal and type | ||
``` | ||
python ../../../manage.py encrypt_value file <path> | ||
|
||
# e.g. | ||
python ../../../manage.py encrypt_value file "E:\Downloads\myfinances-management.2024-05-31.private-key.pem" | ||
``` | ||
- Copy the value that is inputted (not the quotation marks or the first b letter though!) | ||
9. Now go back to terminal, and type: | ||
``` | ||
pulumi config set --secret private_key <private_key_value> | ||
``` | ||
|
||
!!! danger "Note" | ||
If you have spaces in your value, you may need to manually put the value in the `Pulumi.xyz.yaml` file with `>` before | ||
the first line. | ||
|
||
|
||
10. Go to [Github Actions](https://github.com/TreyWW/MyFinances/actions) | ||
- "Click on `"Lambda Zip for Management Bot"` | ||
- Click on the latest run | ||
- Scroll down on the "Summary" tab down to "Artifacts" | ||
- Press on `"python-package"` | ||
11. Unzip the zip to reveal python.zip | ||
12. Copy the path to python.zip | ||
13. Go back to the terminal and type | ||
|
||
``` | ||
pulumi config set lambda_zip_path <path> | ||
``` | ||
14. Finally you can run | ||
``` | ||
pulumi up | ||
``` | ||
15. Copy the `"Invoke URL"` from the output, head back over to https://github.com/settings/apps and go to your app settings | ||
- scroll down to `"Webhook"` and paste the Invoke URL into the Webhook URL field and press `Save changes` | ||
16. Now scroll back up and click on the `"Permissions & Events"` | ||
17. In `"Repository Permissions"` select: | ||
- Issues (read + write) | ||
- Metadata (read only) | ||
- Pull requests (read + write) | ||
18. Scroll down to Subscribe to events and tick | ||
- Issue Comment | ||
19. Scroll down and press Save Changes | ||
20. Scroll up to `Install App`, and install it to your repositories! Now you can use `/help` in issue comments |