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