From f17d37bc53fa1540412b954552d52b057b44b15e Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:34:35 +0100 Subject: [PATCH] added reminders + refactor Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- .github/management_bot/pulumi/__main__.py | 180 +++++++++++++++++- .../{src => reminder_handler}/__init__.py | 0 .../{src => reminder_handler}/helpers.py | 0 .../pulumi/reminder_handler/lambda_handler.py | 63 ++++++ .../pulumi/src/issues/handler.py | 106 ----------- .../pulumi/webhook_handler/__init__.py | 0 .../pulumi/{src => webhook_handler}/_types.py | 1 + .../pulumi/webhook_handler/boto3_handler.py | 39 ++++ .../pulumi/webhook_handler/helpers.py | 19 ++ .../pulumi/webhook_handler/issues/__init__.py | 0 .../pulumi/webhook_handler/issues/handler.py | 165 ++++++++++++++++ .../webhook_handler/issues/reminders.py | 68 +++++++ .../lambda_handler.py | 5 + .../pulumi/webhook_handler/prs/__init__.py | 0 .../{src => webhook_handler}/prs/actions.py | 0 .../{src => webhook_handler}/prs/handler.py | 0 16 files changed, 531 insertions(+), 115 deletions(-) rename .github/management_bot/pulumi/{src => reminder_handler}/__init__.py (100%) rename .github/management_bot/pulumi/{src => reminder_handler}/helpers.py (100%) create mode 100644 .github/management_bot/pulumi/reminder_handler/lambda_handler.py delete mode 100644 .github/management_bot/pulumi/src/issues/handler.py create mode 100644 .github/management_bot/pulumi/webhook_handler/__init__.py rename .github/management_bot/pulumi/{src => webhook_handler}/_types.py (99%) create mode 100644 .github/management_bot/pulumi/webhook_handler/boto3_handler.py create mode 100644 .github/management_bot/pulumi/webhook_handler/helpers.py create mode 100644 .github/management_bot/pulumi/webhook_handler/issues/__init__.py create mode 100644 .github/management_bot/pulumi/webhook_handler/issues/handler.py create mode 100644 .github/management_bot/pulumi/webhook_handler/issues/reminders.py rename .github/management_bot/pulumi/{src => webhook_handler}/lambda_handler.py (96%) create mode 100644 .github/management_bot/pulumi/webhook_handler/prs/__init__.py rename .github/management_bot/pulumi/{src => webhook_handler}/prs/actions.py (100%) rename .github/management_bot/pulumi/{src => webhook_handler}/prs/handler.py (100%) diff --git a/.github/management_bot/pulumi/__main__.py b/.github/management_bot/pulumi/__main__.py index d25d32f38..c655af340 100644 --- a/.github/management_bot/pulumi/__main__.py +++ b/.github/management_bot/pulumi/__main__.py @@ -3,7 +3,7 @@ import json import pulumi -from pulumi_aws import apigateway, iam +from pulumi_aws import apigateway, iam, scheduler from pulumi_aws import lambda_ config = pulumi.Config() @@ -13,9 +13,15 @@ region = config.get("region", "eu-west-2") tags = {"app": site_name} +# Reminders + +reminders_group = scheduler.ScheduleGroup("invoice_reminders_group", name=f"myfinances-github-bot-remind_me") + # lambda_layer lambda_access_role = iam.Role( "lambda_access_role", + name="lambda_role", + path="/myfinances/management_bot/", assume_role_policy=json.dumps( { "Version": "2012-10-17", @@ -33,23 +39,176 @@ ), ) +# +# This goes on Lambda to send comment +lambda_execution_policy = iam.Policy( + "lambda-execution-policy", + name=f"lambda-execution-policy", + path="/myfinances/management_bot/", + policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CreateLogDelivery", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "ssm:GetParametersByPath", + "ssm:GetParameters", + "ssm:GetParameter", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Resource": [ + "arn:aws:ssm:*:*:parameter/myfinances/github_bot/*", + "arn:aws:kms:*:*:key/*", + "arn:aws:logs:*:*:log-group:/aws/lambda/myfinances_github_bot_webhooks:*", + "arn:aws:logs:*:*:log-group:/aws/lambda/myfinances_github_bot_reminders:*", + "arn:aws:logs:*:*:log-group:/aws/lambda/myfinances_github_bot_webhooks:*:log-stream:*", + "arn:aws:logs:*:*:log-group:/aws/lambda/myfinances_github_bot_reminders:*:log-stream:*", + ], + } + ], + } + ), +) +# + +# +lambda_execution_policy_attachment = iam.RolePolicyAttachment( + "lambda-execution-policy-attach", + policy_arn=lambda_execution_policy.arn, + role=lambda_access_role.name, + opts=pulumi.ResourceOptions(depends_on=[lambda_access_role, lambda_execution_policy]), +) +# + +# + +scheduler_execution_policy = iam.Policy( + "scheduler-execution-policy", + name=f"reminder-scheduler-execution-policy", + path="/myfinances/management_bot/", + policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "InvokeLambda", + "Effect": "Allow", + "Action": ["lambda:InvokeFunction"], + "Resource": "arn:aws:lambda:*:*:function:myfinances_github_bot_reminders", + }, + { + "Sid": "AllowEventbridgeScheduler", + "Effect": "Allow", + "Action": ["scheduler:*"], + "Resource": "*", + }, + ], + } + ), +) +# + +# +scheduler_execution_role = iam.Role( + "scheduler-execution-role", + name="reminder-scheduler-execution-role", + path="/myfinances/management_bot/", + assume_role_policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Principal": {"Service": "scheduler.amazonaws.com"}, "Action": "sts:AssumeRole"}], + } + ), +) +# + +# +scheduler_execution_policy_attachment = iam.RolePolicyAttachment( + "scheduler-execution-policy-attach", + policy_arn=scheduler_execution_policy.arn, + role=scheduler_execution_role.name, + opts=pulumi.ResourceOptions(depends_on=[scheduler_execution_policy, scheduler_execution_role]), +) +# + +scheduler_user = iam.User("scheduler_user", name="myfinances-github-bot-scheduler") + +reminders_create_schedule_policy = iam.Policy( + "reminders_create_schedule_policy", + name="myfinances-github-bot-reminders-create-schedule-policy", + path="/myfinances/management_bot/", + policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowEventbridgeScheduler", + "Effect": "Allow", + "Action": ["scheduler:CreateSchedule"], + "Resource": "arn:aws:scheduler:*:*:schedule/myfinances-github-bot-remind_me/*", + }, + { + "Sid": "AllowRolePass", + "Effect": "Allow", + "Action": ["iam:PassRole"], + "Resource": "arn:aws:iam::*:role/myfinances/management_bot/reminder-scheduler-execution-role", + }, + ], + } + ), +) + +iam.UserPolicyAttachment( + "scheduler_user_policy_attachment", + policy_arn=reminders_create_schedule_policy.arn, + user=scheduler_user.name, + opts=pulumi.ResourceOptions(depends_on=[reminders_create_schedule_policy, scheduler_user]), +) + +scheduler_access_role_key = iam.AccessKey( + "lambda_access_role_key", user=scheduler_user.name, opts=pulumi.ResourceOptions(depends_on=[scheduler_user]) +) + lambda_layer = lambda_.LayerVersion( "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", -# ) +reminder_handler_lambda_func = lambda_.Function( + "reminder_lambda", + name="myfinances_github_bot_reminders", + role=lambda_access_role.arn, + code=pulumi.AssetArchive({".": pulumi.FileArchive("./reminder_handler")}), + handler="lambda_handler.lambda_handler", + timeout=8, + runtime=lambda_.Runtime.PYTHON3D12, + 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"}, +) -lambda_func = lambda_.Function( +main_lambda_func = lambda_.Function( "webhook_lambda", name="myfinances_github_bot_webhooks", role=lambda_access_role.arn, - code=pulumi.AssetArchive({".": pulumi.FileArchive("./src")}), + code=pulumi.AssetArchive({".": pulumi.FileArchive("./webhook_handler")}), handler="lambda_handler.lambda_handler", timeout=8, runtime=lambda_.Runtime.PYTHON3D12, - environment=lambda_.FunctionEnvironmentArgs(variables={"ssm_prefix": "/myfinances/github_bot/"}), + environment=lambda_.FunctionEnvironmentArgs( + variables={ + "ssm_prefix": "/myfinances/github_bot/", + "AWS_REMINDER_LAMBDA_ARN": reminder_handler_lambda_func.arn, + "AWS_REMINDER_LAMBDA_ROLE_ARN": scheduler_execution_role.arn, + "AWS_SCHEDULES_ACCESS_KEY_ID": scheduler_access_role_key.id, + "AWS_SCHEDULES_SECRET_ACCESS_KEY": scheduler_access_role_key.secret, + "AWS_SCHEDULES_REGION_NAME": region, + } + ), layers=[lambda_layer.arn, "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11"], tags={"project": "MyFinancesBot"}, ) @@ -82,7 +241,8 @@ integration_http_method="POST", type="AWS", timeout_milliseconds=8000, - uri=lambda_func.invoke_arn, + content_handling="CONVERT_TO_TEXT", + uri=main_lambda_func.arn.apply(lambda arn: arn + ":${stageVariables.lambda_function_version}"), # main_lambda_func.invoke_arn, ) api_gw_200_resp = apigateway.MethodResponse( @@ -107,7 +267,7 @@ "apigw_lambda", statement_id="AllowExecutionFromAPIGateway", action="lambda:InvokeFunction", - function=lambda_func.name, + function=main_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/"), ) @@ -123,3 +283,5 @@ ) pulumi.export("invoke_url", prod_stage.invoke_url) +pulumi.export("scheduler_execution_role", scheduler_execution_role.arn) +pulumi.export("reminder_handler_lambda_func", reminder_handler_lambda_func.arn) diff --git a/.github/management_bot/pulumi/src/__init__.py b/.github/management_bot/pulumi/reminder_handler/__init__.py similarity index 100% rename from .github/management_bot/pulumi/src/__init__.py rename to .github/management_bot/pulumi/reminder_handler/__init__.py diff --git a/.github/management_bot/pulumi/src/helpers.py b/.github/management_bot/pulumi/reminder_handler/helpers.py similarity index 100% rename from .github/management_bot/pulumi/src/helpers.py rename to .github/management_bot/pulumi/reminder_handler/helpers.py diff --git a/.github/management_bot/pulumi/reminder_handler/lambda_handler.py b/.github/management_bot/pulumi/reminder_handler/lambda_handler.py new file mode 100644 index 000000000..421a1cf91 --- /dev/null +++ b/.github/management_bot/pulumi/reminder_handler/lambda_handler.py @@ -0,0 +1,63 @@ +import json +import os, base64 +import urllib.request +from textwrap import dedent + +from github import Github, Issue, GithubIntegration, PullRequest +from github import Auth + +import logging +import helpers + +logging.basicConfig() +logging.getLogger().setLevel(logging.DEBUG if os.environ.get("DEBUG") else logging.DEBUG) # todo go back to info +logger = logging.getLogger(__name__) + +aws_session_token = os.environ.get("AWS_SESSION_TOKEN") + +REPOSITORY_NAME = "TreyWW/MyFinances" + + +def lambda_handler(event: dict, lambda_context): + print(f"EVENT_DETAILS: {event}") + # 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 = helpers.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() + + repository = g.get_repo(REPOSITORY_NAME) + + target: Issue.Issue | PullRequest.PullRequest + message: str = event.get("message") + + if issue_id := event.get("issue_id"): + target = repository.get_issue(issue_id) + elif pr_id := event.get("pr_id"): + target = repository.get_pull(pr_id) + else: + raise ValueError("No issue or pull request specified") + + target.create_comment( + dedent( + f""" + :wave: @{event.get("user")}, {message if message else "you set a reminder for now!"} + + {helpers.del_reply_comment()} + """ + ) + ) + + return {"statusCode": 200, "body": json.dumps({}), "headers": {"Content-Type": "application/json"}} diff --git a/.github/management_bot/pulumi/src/issues/handler.py b/.github/management_bot/pulumi/src/issues/handler.py deleted file mode 100644 index 52a7198c4..000000000 --- a/.github/management_bot/pulumi/src/issues/handler.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import re -from textwrap import dedent -import logging -import github.Issue - -import string - -import random - -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) -> list[str]: - if not re.match(r"^(bug|idea|implement|cleanup):\s*\S.*", context_objs.issue.title) and context_objs.sender.type == "User": - 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}") - context_objs.issue.create_comment( - dedent( - f""" - Hi @{context_objs.sender.login}, - - You have chosen a title that is slightly different to our title standards. Please, if possible, use the format: - (bug, idea, implement or cleanup) : Title - - e.g. "bug: xyz page doesn't work" - - {helpers.del_reply_comment()} - """ - ) - ) - return ["added_issue_comment (invalid title)"] - return [] - - -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 or context_objs.sender.type != "User": - return [] - - logger.info("Deleting comment due to DELREPLY in body") - - reference_code = match.group(1) - - logger.debug(f"Deleting comment with reference code: {reference_code}") - - 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 - 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) -> 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") - responses.extend(title_handler(context_dicts, context_objs)) - case "edited": - if context_dicts.changes.title and context_dicts.changes.title["from"]: - responses.extend(title_handler(context_dicts, context_objs)) - case "created": - if context_dicts.comment: - 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/webhook_handler/__init__.py b/.github/management_bot/pulumi/webhook_handler/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/.github/management_bot/pulumi/src/_types.py b/.github/management_bot/pulumi/webhook_handler/_types.py similarity index 99% rename from .github/management_bot/pulumi/src/_types.py rename to .github/management_bot/pulumi/webhook_handler/_types.py index b663a90d7..7163e01b9 100644 --- a/.github/management_bot/pulumi/src/_types.py +++ b/.github/management_bot/pulumi/webhook_handler/_types.py @@ -71,6 +71,7 @@ class Changes: @dataclass class Context: event: dict + lambda_context: Any action: str sender: User issue: Optional[Issue] = None diff --git a/.github/management_bot/pulumi/webhook_handler/boto3_handler.py b/.github/management_bot/pulumi/webhook_handler/boto3_handler.py new file mode 100644 index 000000000..bd77c796a --- /dev/null +++ b/.github/management_bot/pulumi/webhook_handler/boto3_handler.py @@ -0,0 +1,39 @@ +import json +import os +import logging +import uuid + +import boto3 +from botocore.config import Config + +config = Config(connect_timeout=5, retries={"max_attempts": 2}) + +Boto3HandlerSession = boto3.session.Session( + aws_access_key_id=os.environ.get("AWS_SCHEDULES_ACCESS_KEY_ID"), + aws_secret_access_key=os.environ.get("AWS_SCHEDULES_SECRET_ACCESS_KEY"), + region_name=os.environ.get("AWS_SCHEDULES_REGION_NAME", default="eu-west-2"), +) + +event_bridge_client = Boto3HandlerSession.client("events") +event_bridge_scheduler = Boto3HandlerSession.client("scheduler", config=config) + +logger = logging.getLogger(__name__) + + +def create_reminder(comment_id, date_time, pr_id, issue_id, message, user): + target = { + "Arn": os.environ.get("AWS_REMINDER_LAMBDA_ARN", ""), + "RoleArn": os.environ.get("AWS_REMINDER_LAMBDA_ROLE_ARN", ""), + "Input": json.dumps({"pr_id": pr_id, "issue_id": issue_id, "message": message, "user": user}), + } + logger.info(f"target: {target}") + logger.info(f"schedule_expression: at({date_time})") + logger.info(f"") + return event_bridge_scheduler.create_schedule( + Name=f"{str(uuid.uuid4())}", + GroupName="myfinances-github-bot-remind_me", + FlexibleTimeWindow={"Mode": "OFF"}, + ScheduleExpression=f"at({date_time})", + Target=target, + ActionAfterCompletion="DELETE", + ) diff --git a/.github/management_bot/pulumi/webhook_handler/helpers.py b/.github/management_bot/pulumi/webhook_handler/helpers.py new file mode 100644 index 000000000..751765bf6 --- /dev/null +++ b/.github/management_bot/pulumi/webhook_handler/helpers.py @@ -0,0 +1,19 @@ +import base64 +import random +import string + + +def encode_private_key(entire_key: str) -> str: + return base64.b64encode(entire_key.encode("ascii")).decode("ascii") + + +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/webhook_handler/issues/__init__.py b/.github/management_bot/pulumi/webhook_handler/issues/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/.github/management_bot/pulumi/webhook_handler/issues/handler.py b/.github/management_bot/pulumi/webhook_handler/issues/handler.py new file mode 100644 index 000000000..8122b4d4f --- /dev/null +++ b/.github/management_bot/pulumi/webhook_handler/issues/handler.py @@ -0,0 +1,165 @@ +import os +import re +from datetime import datetime +from textwrap import dedent +import logging +import github.Issue + +import string + +import random + +logger = logging.getLogger(__name__) +if os.environ.get("AWS_EXECUTION_ENV") is not None: + import _types + import helpers + from .reminders import validate_reminder_command + import boto3_handler +else: + from .. import _types + from .. import helpers + from ..issues.reminders import validate_reminder_command + from .. import boto3_handler + + +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) and context_objs.sender.type == "User": + 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}") + context_objs.issue.create_comment( + dedent( + f""" + Hi @{context_objs.sender.login}, + + You have chosen a title that is slightly different to our title standards. Please, if possible, use the format: + (bug, idea, implement or cleanup) : Title + + e.g. "bug: xyz page doesn't work" + + {helpers.del_reply_comment()} + """ + ) + ) + return ["added_issue_comment (invalid title)"] + return [] + + +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 or context_objs.sender.type != "User": + return [] + + logger.info("Deleting comment due to DELREPLY in body") + + reference_code = match.group(1) + + logger.debug(f"Deleting comment with reference code: {reference_code}") + + 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 + 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}") + + match command: + # + case "/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)"] + # + # + case "/remind": + logger.info("Using /remind") + if len(split) < 2: + logger.info("Invalid usage") + context_objs.issue.create_comment( + dedent( + f""" + Invalid usage. Example usage: + - `/remind 2d` + - `/remind 1w revamp this` + + {helpers.del_reply_comment()} + """ + ) + ) + return ["added_issue_comment (invalid /remind args)"] + + duration: str = split[1] + message: str = " ".join(split[2:]) if len(split) > 2 else "" + + datetime_or_error = validate_reminder_command(split, context_objs) + + logger.info(f"datetime_or_error: {datetime_or_error}") + + if isinstance(datetime_or_error, list): + return datetime_or_error + + resp = boto3_handler.create_reminder( + comment_id=context_objs, + date_time=datetime_or_error.strftime("%Y-%m-%dT%H:%M:%S"), + pr_id=None, + issue_id=context_objs.issue.number, + message=message, + user=context_objs.sender.login, + ) + logger.info(f"resp: {resp}") + + context_objs.issue.create_comment( + dedent( + f""" + @{context_objs.sender.login}, ok! I will remind you {datetime_or_error.strftime("on %A, %B %-m, %y at %-H:%M %p")}! + + {helpers.del_reply_comment()} + """ + ) + ) + return ["added_issue_comment (reminder success)"] + case _: + logger.info("No issue command") + return [] + # + + +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") + responses.extend(title_handler(context_dicts, context_objs)) + case "edited": + if context_dicts.changes.title and context_dicts.changes.title["from"]: + responses.extend(title_handler(context_dicts, context_objs)) + case "created": + if context_dicts.comment: + 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/webhook_handler/issues/reminders.py b/.github/management_bot/pulumi/webhook_handler/issues/reminders.py new file mode 100644 index 000000000..9a3a64ee7 --- /dev/null +++ b/.github/management_bot/pulumi/webhook_handler/issues/reminders.py @@ -0,0 +1,68 @@ +import os +import re +from textwrap import dedent +from datetime import datetime, timedelta + +if os.environ.get("AWS_EXECUTION_ENV") is not None: + import _types + import helpers + import boto3_handler +else: + from .. import _types + from .. import helpers + from .. import boto3_handler + + +def parse_duration(duration) -> timedelta | None: + pattern = re.compile(r"(\d+)([wdhm])") + matches = pattern.match(duration) + + if not matches: + return None + + value = int(matches.group(1)) + unit = matches.group(2) + delta: timedelta + + if unit == "w": + delta = timedelta(weeks=value) + elif unit == "d": + delta = timedelta(days=value) + elif unit == "h": + delta = timedelta(hours=value) + elif unit == "m": + delta = timedelta(minutes=value) + else: + return None + + return delta + + +def invalid_usage(context_objs) -> list[str]: + context_objs.issue.create_comment( + dedent( + f""" + Invalid usage. Example usage: + - `/remind 2d` + - `/remind 1w revamp this` + + {helpers.del_reply_comment()} + """ + ) + ) + return ["added_issue_comment (invalid /remind args)"] + + +def validate_reminder_command(split, context_objs) -> datetime | list[str]: + if len(split) < 2: + return invalid_usage(context_objs) + + duration = parse_duration(split[1]) + if not duration: + return invalid_usage(context_objs) + + print(f"duration: {duration}") + print(f"now: {datetime.now()}") + print(f"result: {datetime.now() + duration}") + + return datetime.now() + duration diff --git a/.github/management_bot/pulumi/src/lambda_handler.py b/.github/management_bot/pulumi/webhook_handler/lambda_handler.py similarity index 96% rename from .github/management_bot/pulumi/src/lambda_handler.py rename to .github/management_bot/pulumi/webhook_handler/lambda_handler.py index 3d2f10326..5d6cdd59b 100644 --- a/.github/management_bot/pulumi/src/lambda_handler.py +++ b/.github/management_bot/pulumi/webhook_handler/lambda_handler.py @@ -65,12 +65,16 @@ def lambda_handler(event: dict, lambda_context): req.add_header("X-Aws-Parameters-Secrets-Token", aws_session_token) config = urllib.request.urlopen(req).read() + logger.info(f"Using stage: {stage}") + 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"] + logger.info(f"Using app id: {APP_ID}") + auth = Auth.AppAuth(APP_ID, PRIVATE_KEY) gi = GithubIntegration(auth=auth) g: Github = gi.get_installations()[0].get_github_for_installation() @@ -79,6 +83,7 @@ def lambda_handler(event: dict, lambda_context): context_dicts = _types.Context( event=event, + lambda_context=lambda_context, action=event.get("action", ""), issue=_types.fill_dataclass_from_dict(_types.Issue, event.get("issue", {})), pull_request=_types.fill_dataclass_from_dict(_types.PullRequest, event.get("pull_request", {})), diff --git a/.github/management_bot/pulumi/webhook_handler/prs/__init__.py b/.github/management_bot/pulumi/webhook_handler/prs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/.github/management_bot/pulumi/src/prs/actions.py b/.github/management_bot/pulumi/webhook_handler/prs/actions.py similarity index 100% rename from .github/management_bot/pulumi/src/prs/actions.py rename to .github/management_bot/pulumi/webhook_handler/prs/actions.py diff --git a/.github/management_bot/pulumi/src/prs/handler.py b/.github/management_bot/pulumi/webhook_handler/prs/handler.py similarity index 100% rename from .github/management_bot/pulumi/src/prs/handler.py rename to .github/management_bot/pulumi/webhook_handler/prs/handler.py