post-dr-comment/post_dr_comment.py
2024-09-05 14:11:08 -07:00

204 lines
5.7 KiB
Python
Executable File

#! /usr/bin/env -S python3
"""
post_dr_comment.py: Post a comment to an AllSpice Hub Design Review.
"""
import argparse
import logging
import os
import sys
import yaml
from typing import Tuple
from allspice import AllSpice, Comment, DesignReview
COMMENT_IDENTIFIER = "<!-- AllSpice Hub Auto-DR Comment -->"
logger = logging.getLogger(__name__)
def parse_bool(input: str | bool) -> bool:
"""
Parse a YAML-like boolean string as a boolean.
"""
if isinstance(input, bool):
return input
if input.lower() in ("yes", "true", "t", "y", "1"):
return True
elif input.lower() in ("no", "false", "f", "n", "0"):
return False
else:
raise argparse.ArgumentTypeError(
"One of: yes, no, true, false, t, f, y, n, 1, 0 expected."
)
def parse_front_matter(comment_body: str) -> Tuple[dict, str]:
"""
Check if the comment body has a front matter and parse it.
Returns the front matter as a dictionary and the comment body without the
front matter.
If the front matter is empty or missing, the front matter dictionary will
be empty.
"""
front_matter = {}
found_front_matter = False
stripped_comment_body = comment_body.strip()
if stripped_comment_body.startswith("---"):
split_comment = stripped_comment_body.split("---", 2)
if len(split_comment) == 3:
found_front_matter = True
try:
front_matter = yaml.safe_load(split_comment[1])
comment_body = split_comment[2].lstrip()
found_front_matter = True
except yaml.YAMLError as e:
logger.error(f"Failed to parse front matter: {e}")
if not found_front_matter:
logger.info("No front matter found in comment body.")
return front_matter, comment_body
def upsert_comment(design_review: DesignReview, comment_body: str) -> Comment:
"""
Upsert a comment on a Design Review.
If a comment with the same identifier already exists, update the existing
comment. Otherwise, create a new comment.
Returns the created or updated comment.
"""
existing_comment = None
comments = design_review.get_comments()
updated_comment_body = f"{COMMENT_IDENTIFIER}\n{comment_body}"
for comment in comments:
if COMMENT_IDENTIFIER in comment.body:
existing_comment = comment
break
if existing_comment:
logger.info("Updating existing comment.")
existing_comment.body = updated_comment_body
existing_comment.commit()
return existing_comment
else:
logger.info("Creating new comment.")
return design_review.create_comment(updated_comment_body)
def upsert_attachments(comment: Comment, attachments: list[str]):
"""
Upsert attachments to a comment.
This clears all existing attachments and then adds new attachments.
"""
existing_attachments = comment.get_attachments()
for attachment in existing_attachments:
comment.delete_attachment(attachment)
for attachment in attachments:
with open(attachment, "rb") as f:
try:
comment.create_attachment(f)
except Exception as e:
if "500" in str(e):
logger.error(
f"Failed to upload attachment {attachment}. "
"The file may be too large, or it may be of a file type "
"that is not supported by the AllSpice Hub."
)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description=__doc__,
)
parser.add_argument(
"--allspice-hub-url",
required=False,
default="https://hub.allspice.io",
help="The URL of the AllSpice Hub DR to post the comment to.",
)
parser.add_argument(
"--repository",
required=True,
help="The repository that the design review is associated with.",
)
parser.add_argument(
"--design-review-number",
required=True,
help="The number of the design review to post the comment to.",
)
parser.add_argument(
"--comment-path",
required=True,
help="The path to the Comment Markdown file.",
)
parser.add_argument(
"--reuse-existing-comment",
required=False,
default=True,
type=parse_bool,
help="Whether to reuse an existing comment if it exists.",
)
parser.add_argument(
"--log-level",
required=False,
default="INFO",
help="The logging level to use.",
)
token = os.getenv("ALLSPICE_AUTH_TOKEN")
if not token:
raise ValueError("ALLSPICE_AUTH_TOKEN environment variable not set.")
args = parser.parse_args()
logger.setLevel(args.log_level.upper())
client = AllSpice(
args.allspice_hub_url,
token_text=token,
log_level=args.log_level.upper(),
)
owner, repo = args.repository.split("/")
design_review = DesignReview.request(client, owner, repo, args.design_review_number)
with open(args.comment_path, "r") as f:
comment_body = f.read()
front_matter, comment_body = parse_front_matter(comment_body)
attachments = []
if front_matter:
logger.debug(f"Front matter: {front_matter}")
if "attachments" in front_matter:
attachments = front_matter["attachments"]
if args.reuse_existing_comment:
comment = upsert_comment(design_review, comment_body)
else:
comment = design_review.create_comment(comment_body)
if attachments:
upsert_attachments(comment, attachments)
logger.info("Comment posted successfully.")
if __name__ == "__main__":
main()