diff --git a/.github/management_bot/pulumi/__main__.py b/.github/management_bot/pulumi/__main__.py index f3c10818..d25d32f3 100644 --- a/.github/management_bot/pulumi/__main__.py +++ b/.github/management_bot/pulumi/__main__.py @@ -37,6 +37,10 @@ "lambda_layer", code=pulumi.FileArchive(config.require("lambda_zip_path")), layer_name="PyGithub_for_myfinances_management_bot" ) +# lambda_ssm_layer = lambda_.LayerVersion( +# "lambda_ssm_layer", layer_name="AWS-Parameters-and-Secrets-Lambda-Extension", +# ) + lambda_func = lambda_.Function( "webhook_lambda", name="myfinances_github_bot_webhooks", @@ -45,10 +49,8 @@ handler="lambda_handler.lambda_handler", timeout=8, 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], + environment=lambda_.FunctionEnvironmentArgs(variables={"ssm_prefix": "/myfinances/github_bot/"}), + layers=[lambda_layer.arn, "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11"], tags={"project": "MyFinancesBot"}, ) @@ -63,6 +65,13 @@ http_method="POST", authorization="NONE", api_key_required=False, + request_parameters={ + "method.request.header.X-GitHub-Delivery": True, + "method.request.header.X-GitHub-Event": True, + "method.request.header.X-GitHub-Hook-ID": True, + "method.request.header.X-GitHub-Hook-Installation-Target-ID": True, + "method.request.header.X-GitHub-Hook-Installation-Target-Type": True, + }, ) rest_api_lambda_integration = apigateway.Integration( @@ -105,6 +114,12 @@ 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) +prod_stage = apigateway.Stage( + "productionStage", + stage_name="production", + rest_api=rest_api.id, + deployment=deployment.id, + opts=pulumi.ResourceOptions(ignore_changes=["variables", "deployment"]), +) pulumi.export("invoke_url", prod_stage.invoke_url) diff --git a/.github/management_bot/pulumi/src/_types.py b/.github/management_bot/pulumi/src/_types.py index 1c55def6..b663a90d 100644 --- a/.github/management_bot/pulumi/src/_types.py +++ b/.github/management_bot/pulumi/src/_types.py @@ -3,13 +3,14 @@ # from github import Github import github -import github.Repository -import github.Issue -import github.PullRequest -import github.Label -import github.IssueComment -import github.PullRequestComment -import github.NamedUser +from github.MainClass import Github as MainGithub +from github.Repository import Repository as GithubRepository +from github.Issue import Issue as GithubIssue +from github.PullRequest import PullRequest as GithubPullRequest +from github.Label import Label as GithubLabel +from github.IssueComment import IssueComment as GithubIssueComment +from github.PullRequestComment import PullRequestComment as GithubPullRequestComment +from github.NamedUser import NamedUser as GithubNamedUser from github.PaginatedList import PaginatedList T = TypeVar("T") @@ -81,14 +82,14 @@ class Context: @dataclass class Objects: - github: github.MainClass.Github + github: MainGithub dict_context: Context - repository: github.Repository.Repository - sender: Optional[github.NamedUser] = None - issue: Optional[github.Issue.Issue] = None - pull_request: Optional[github.PullRequest.PullRequest] = None - labels: PaginatedList[github.Label.Label] = None - comment: Optional[github.IssueComment.IssueComment | github.PullRequestComment.PullRequestComment] = None + repository: GithubRepository + sender: GithubNamedUser = None + issue: Optional[GithubIssue] = None + pull_request: Optional[GithubPullRequest] = None + labels: PaginatedList[GithubLabel] = None + comment: Optional[GithubIssueComment | GithubPullRequestComment] = None def __post_init__(self): print("post_init") diff --git a/.github/management_bot/pulumi/src/helpers.py b/.github/management_bot/pulumi/src/helpers.py index f5240bef..751765bf 100644 --- a/.github/management_bot/pulumi/src/helpers.py +++ b/.github/management_bot/pulumi/src/helpers.py @@ -1,4 +1,6 @@ import base64 +import random +import string def encode_private_key(entire_key: str) -> str: @@ -7,3 +9,11 @@ def encode_private_key(entire_key: str) -> str: def decode_private_key(raw_private_key) -> str: return base64.b64decode(raw_private_key.encode("ascii")).decode("ascii") + + +def del_reply_comment() -> str: + return ( + "> If you would like to ignore this message, please reply with the reference `DELREPLY-" + + "".join(random.choices(string.ascii_uppercase + string.digits, k=8)) + + "` (you may delete this reply afterwards)" + ) diff --git a/.github/management_bot/pulumi/src/issues/handler.py b/.github/management_bot/pulumi/src/issues/handler.py index 8a8cfcf8..f336ff6f 100644 --- a/.github/management_bot/pulumi/src/issues/handler.py +++ b/.github/management_bot/pulumi/src/issues/handler.py @@ -2,12 +2,8 @@ import re from textwrap import dedent import logging -import secrets - import github.Issue -from re import match - import string import random @@ -15,11 +11,13 @@ logger = logging.getLogger(__name__) if os.environ.get("AWS_EXECUTION_ENV") is not None: import _types + import helpers else: from .. import _types + from .. import helpers -def title_handler(context_dicts: _types.Context, context_objs: _types.Objects) -> None: +def title_handler(context_dicts: _types.Context, context_objs: _types.Objects) -> list[str]: if not re.match(r"^(bug|idea|implement|cleanup):\s*\S.*", context_objs.issue.title): logger.info(f"Regex title doesn't match. {context_objs.issue.title} doesnt start with bug|idea|implement|cleanup:") logger.info(f"Commenting on {context_objs.issue.html_url}") @@ -33,18 +31,19 @@ def title_handler(context_dicts: _types.Context, context_objs: _types.Objects) - e.g. "bug: xyz page doesn't work" - > If you would like to ignore this message, please reply with the reference `DELREPLY- - {''.join(random.choices(string.ascii_uppercase + string.digits, k=8))}` (you may delete this reply afterwards) + {helpers.del_reply_comment()} """ ) ) + return ["added_issue_comment (invalid title)"] + return [] -def delete_reply_handler(context_dicts: _types.Context, context_objs: _types.Objects) -> None: +def delete_reply_handler(context_dicts: _types.Context, context_objs: _types.Objects) -> list[str]: match = re.search(r"DELREPLY-(.{8})", context_dicts.comment.body) if not match: - return + return [] logger.info("Deleting comment due to DELREPLY in body") @@ -52,24 +51,56 @@ def delete_reply_handler(context_dicts: _types.Context, context_objs: _types.Obj logger.debug(f"Deleting comment with reference code: {reference_code}") - context_objs.issue: github.Issue.Issue - for comment in context_objs.issue.get_comments(): if f"DELREPLY-{reference_code}" in comment.body.upper(): # comment.delete() # delete users reply comment context_objs.issue.get_comment(context_dicts.comment.id).delete() # delete bots comment - break + return ["deleted_issue_comment (DEL REPLY)"] + + +def command_handler(context_dicts: _types.Context, context_objs: _types.Objects) -> list[str]: + base_message = context_dicts.comment.body + split = base_message.split() + command = split[0] + + logger.info(f"Extracted Command: {command}") + + if command == "/project_info": + context_objs.issue.create_comment( + dedent( + f""" + You can view our documentation at\ + [docs.myfinances.cloud](https://docs.myfinances.cloud/?utm_source=issue_{context_objs.issue.number}). + + There you can find info such as: + - setting up guides + - code styles + - changelogs + - our discord server + - (soon) user usage guide + + {f"> Mentioning @{split[1]}" if len(split) > 1 else ""} + + {helpers.del_reply_comment()} + """ + ) + ) + return ["added_issue_comment (project info)"] + return [] -def handler(context_dicts: _types.Context, context_objs: _types.Objects) -> None: +def handler(context_dicts: _types.Context, context_objs: _types.Objects) -> list[str]: logger.info(f"action: {context_dicts.action}") + responses = [] match context_dicts.action: case "opened": logger.info("Using title handler due to opened issue") - title_handler(context_dicts, context_objs) + responses.extend(title_handler(context_dicts, context_objs)) case "edited": if context_dicts.changes.title and context_dicts.changes.title["from"]: - title_handler(context_dicts, context_objs) + responses.extend(title_handler(context_dicts, context_objs)) case "created": if context_dicts.comment: - delete_reply_handler(context_dicts, context_objs) + responses.extend(delete_reply_handler(context_dicts, context_objs)) + responses.extend(command_handler(context_dicts, context_objs)) + return responses diff --git a/.github/management_bot/pulumi/src/lambda_handler.py b/.github/management_bot/pulumi/src/lambda_handler.py index 1575ae10..6d5133b2 100644 --- a/.github/management_bot/pulumi/src/lambda_handler.py +++ b/.github/management_bot/pulumi/src/lambda_handler.py @@ -1,5 +1,6 @@ import json import os, base64 +import urllib.request from textwrap import dedent import github.GithubException @@ -16,8 +17,7 @@ logging.getLogger().setLevel(logging.DEBUG if os.environ.get("DEBUG") else logging.DEBUG) # todo go back to info logger = logging.getLogger(__name__) -PRIVATE_KEY = decode_private_key(os.environ.get("private_key")) -APP_ID = os.environ.get("app_id") +aws_session_token = os.environ.get("AWS_SESSION_TOKEN") REPOSITORY_NAME = "TreyWW/MyFinances" @@ -56,7 +56,22 @@ def is_trey(sender): return str(sender["id"]) == "171095439" -def lambda_handler(event: dict, _): +def lambda_handler(event: dict, lambda_context): + print(lambda_context) + # https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html + stage = "production" + req = urllib.request.Request( + f"http://localhost:2773/systemsmanager/parameters/get?withDecryption=true&name=%2Fmyfinances%2Fgithub_bot%2F{stage}" + ) + req.add_header("X-Aws-Parameters-Secrets-Token", aws_session_token) + config = urllib.request.urlopen(req).read() + + ssm_result: json = json.loads(config) + ssm_value: json = json.loads(ssm_result["Parameter"]["Value"]) + + PRIVATE_KEY = decode_private_key(ssm_value["private_key"]) + APP_ID = ssm_value["app_id"] + auth = Auth.AppAuth(APP_ID, PRIVATE_KEY) gi = GithubIntegration(auth=auth) g: Github = gi.get_installations()[0].get_github_for_installation() @@ -80,13 +95,15 @@ def lambda_handler(event: dict, _): logger.debug(context_objs) + actions_taken = [] + if context_dicts.pull_request: logger.debug("Using PR handler") - pr_handler.handler(context_dicts, context_objs) + actions_taken.extend(pr_handler.handler(context_dicts, context_objs)) elif context_dicts.issue: logger.debug("Using issue handler") - issue_handler.handler(context_dicts, context_objs) + actions_taken.extend(issue_handler.handler(context_dicts, context_objs)) else: logger.debug("Using no handler; invalid request.") - return {"statusCode": 200, "body": json.dumps({})} + return {"statusCode": 200, "body": json.dumps({"actions_taken": actions_taken}), "headers": {"Content-Type": "application/json"}} diff --git a/.github/management_bot/pulumi/src/prs/handler.py b/.github/management_bot/pulumi/src/prs/handler.py index 9394c5c6..9b88ef0d 100644 --- a/.github/management_bot/pulumi/src/prs/handler.py +++ b/.github/management_bot/pulumi/src/prs/handler.py @@ -10,4 +10,4 @@ from .. import _types -def handler(context_dicts: _types.Context, context_objs: _types.Objects) -> None: ... +def handler(context_dicts: _types.Context, context_objs: _types.Objects) -> list[str]: ...