mirror of
https://github.com/AllSpiceIO/post-dr-comment.git
synced 2025-04-01 12:46:55 +00:00
Initialize
This commit is contained in:
commit
656b43d1d7
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: pip
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
31
.github/workflows/test.yml
vendored
Normal file
31
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Lint and test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-test.txt
|
||||
- name: Check formatting
|
||||
run: ruff format --diff .
|
||||
- name: Lint with ruff
|
||||
run: ruff check --target-version=py310 .
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.venv
|
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM python:3.12-bookworm
|
||||
|
||||
COPY requirements.txt /requirements.txt
|
||||
COPY post_dr_comment.py /post_dr_comment.py
|
||||
|
||||
RUN pip install -r /requirements.txt
|
||||
|
||||
ENTRYPOINT [ "/post_dr_comment.py" ]
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 AllSpice
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
87
README.md
Normal file
87
README.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Post Comment on a Design Review
|
||||
|
||||
Post a comment on a Design Review using a markdown file as the source of the
|
||||
comment on AllSpice Hub using
|
||||
[AllSpice Actions](https://learn.allspice.io/docs/actions-cicd).
|
||||
|
||||
## Usage
|
||||
|
||||
Add the following step to your actions:
|
||||
|
||||
```yaml
|
||||
- name: Post Comment on Design Review
|
||||
uses: https://hub.allspice.io/Actions/post-dr-comment@v0.1
|
||||
with:
|
||||
# The path to the markdown file containing the comment body.
|
||||
comment_path: path/to/comment.md
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. This action works only when used in a workflow triggered by a Design Review,
|
||||
as it will automatically pick up the associated design review.
|
||||
2. By default, successive runs of the action will edit the same comment.
|
||||
3. This action also reads YAML frontmatter from the markdown file to post
|
||||
attachments to the posted comment.
|
||||
|
||||
### Customizing the Comment Content
|
||||
|
||||
The action uses a markdown file as the source of the comment body. You can
|
||||
create a markdown file in your repository and specify its path using the
|
||||
`comment_path` input.
|
||||
|
||||
Example `comment.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
attachments:
|
||||
- path/to/attachment1.png
|
||||
- path/to/attachment2.pdf
|
||||
---
|
||||
|
||||
# Comment Title
|
||||
|
||||
This is the body of the comment.
|
||||
|
||||
- Point 1
|
||||
- Point 2
|
||||
- Point 3
|
||||
|
||||
[Link to more information](https://example.com)
|
||||
```
|
||||
|
||||
The YAML frontmatter at the beginning of the file (between `---`) can be used
|
||||
to specify attachments that will be added to the comment. The YAML frontmatter
|
||||
is optional, and when present, isn't included in the posted comment's body.
|
||||
|
||||
### Reusing Existing Comments
|
||||
|
||||
By default, the action will reuse the existing comment made by this action in
|
||||
successive runs. This behavior can be controlled using the
|
||||
`reuse_existing_comment` input. Set it to 'False' if you want to create a new
|
||||
comment on each run.
|
||||
|
||||
### Debugging
|
||||
|
||||
If you encounter any issues or need more detailed information about the
|
||||
action's execution, you can set the `log_level` input to 'DEBUG' for more
|
||||
verbose logging.
|
||||
|
||||
## SSL
|
||||
|
||||
If your instance is running on a self-signed certificate, you can tell the
|
||||
action to use your certificate by setting the `REQUESTS_CA_BUNDLE` environment
|
||||
variable.
|
||||
|
||||
```yaml
|
||||
- name: Post Comment on Design Review
|
||||
uses: https://hub.allspice.io/Actions/post-dr-comment@v0.1
|
||||
with:
|
||||
comment_path: path/to/comment.md
|
||||
env:
|
||||
REQUESTS_CA_BUNDLE: /path/to/your/certificate.cert
|
||||
```
|
||||
|
||||
For more information about AllSpice Actions and how to use them in your
|
||||
workflows, please refer to the
|
||||
[AllSpice Documentation](https://learn.allspice.io/docs/actions-cicd).
|
43
action.yml
Normal file
43
action.yml
Normal file
@ -0,0 +1,43 @@
|
||||
name: "Post Comment on a Design Review"
|
||||
description: >
|
||||
Post a comment on a Design Review using a markdown file as the source of the
|
||||
comment.
|
||||
|
||||
This works only when used in a workflow triggered by a Design Review, as it
|
||||
will automatically pick up the associated design review. By default,
|
||||
successive runs of the action will edit the same comment.
|
||||
|
||||
This action also reads YAML frontmatter from the markdown file to post
|
||||
attachments to the posted comment.
|
||||
|
||||
inputs:
|
||||
comment_path:
|
||||
description: The path to a markdown file containing the comment body.
|
||||
required: true
|
||||
reuse_existing_comment:
|
||||
description: Whether to reuse the existing comment made by this action in successive runs.
|
||||
required: false
|
||||
default: "True"
|
||||
log_level:
|
||||
description: The log level used by the action. Used for debugging.
|
||||
required: false
|
||||
default: "INFO"
|
||||
|
||||
runs:
|
||||
using: "docker"
|
||||
image: "Dockerfile"
|
||||
args:
|
||||
- "--allspice_hub_url"
|
||||
- ${{ github.server_url }}
|
||||
- "--repository"
|
||||
- ${{ github.repository }}
|
||||
- "--design-review-number"
|
||||
- ${{ github.event.number }}
|
||||
- "--comment-path"
|
||||
- "${{ github.workspace}}/${{ inputs.comment_path }}"
|
||||
- "--reuse-existing-comment"
|
||||
- ${{ inputs.reuse_existing_comment }}
|
||||
- "--log-level"
|
||||
- ${{ inputs.log_level }}
|
||||
env:
|
||||
ALLSPICE_AUTH_TOKEN: ${{ github.token }}
|
191
post_dr_comment.py
Normal file
191
post_dr_comment.py
Normal file
@ -0,0 +1,191 @@
|
||||
"""
|
||||
post_dr_comment.py: Post a comment to an AllSpice Hub Design Review.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
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
|
||||
|
||||
return front_matter, stripped_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()
|
||||
|
||||
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 = comment_body
|
||||
existing_comment.commit()
|
||||
|
||||
return existing_comment
|
||||
else:
|
||||
logger.info("Creating new comment.")
|
||||
return design_review.create_comment(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:
|
||||
comment.create_attachment(f)
|
||||
|
||||
|
||||
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()
|
1
requirements-test.txt
Normal file
1
requirements-test.txt
Normal file
@ -0,0 +1 @@
|
||||
ruff==0.6.3
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
py-allspice~=3.5.0
|
||||
PyYAML~=6.0.2
|
||||
|
Loading…
Reference in New Issue
Block a user