Update existing qhelp comment, if it exists#8618
Conversation
6a7ee0a to
8afaf75
Compare
a6e275e to
4312f8f
Compare
aibaars
left a comment
There was a problem hiding this comment.
We better keep things as simple as possible to reduce possible security issues and make security review easier.
| pr="$(cat pr.txt)" | ||
| echo "::set-output name=pr-number::$pr" | ||
|
|
||
| - name: Find existing comment, if it exists |
There was a problem hiding this comment.
Is it possible to run this code as part of the unprivileged workflow?
|
|
||
| - id: get-pr-number | ||
| run: | | ||
| pr="$(cat pr.txt)" |
There was a problem hiding this comment.
Above we use PR="$(grep -o '^[0-9]\+$' pr.txt)" which performs a bit of input validation.
| direction: last | ||
| token: ${{ github.token }} | ||
|
|
||
| - name: Create or update comment |
There was a problem hiding this comment.
It's probably best to replace this Action with a call to gh api.
e6c24f5 to
10c82c1
Compare
|
I've updated the
The
I think the result is even a bit simpler than the previous version because we don't have to jump through hoops to get the data into the third-party actions anymore. |
| @@ -0,0 +1,57 @@ | |||
| # This workflow is the second part of the process described in | |||
There was a problem hiding this comment.
Let's not rename this file. It can actually be used by other (future) jobs that need to post PR comments.
aibaars
left a comment
There was a problem hiding this comment.
Looks good to me. Could you undo the file rename? I think there is no need to rename the file, and it would make it easier to review the differences.
@adityasharad could you have a look at this too from a security point of view.
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" -X POST --input - | ||
| else | ||
| # Update existing comment | ||
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" -X PATCH --input - |
There was a problem hiding this comment.
It's unlikely that ${{github.repository}} contains dangerous content like " or $(...) that could cause command injection. However, better safe than sorry. Using a normal environment variable should be a little safer .
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" -X POST --input - | |
| else | |
| # Update existing comment | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" -X PATCH --input - | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" -X POST --input - | |
| else | |
| # Update existing comment | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -X PATCH --input - |
| run: | | ||
| # Find the latest comment starting with "QHelp previews" | ||
| COMMENT_PREFIX="QHelp previews" | ||
| gh api "repos/${{ github.repository }}/issues/${{ github.event.number }}/comments?per_page=100" --jq "[.[] | select(.body|startswith(\"${COMMENT_PREFIX}\")) | .id] | max" > comment_id.txt |
There was a problem hiding this comment.
Perhaps we should use the --paginate flag of gh api to fetch all pages of comments so we're sure to get the latest even if there are over 100 comments.
10c82c1 to
32585ad
Compare
If we've already commented on a PR with a preview of the QHelp changes, then update the existing comment instead of creating a new one.
Also move more steps to the unprivileged workflow.
This workflow can (pretty much) be used by any other workflow that wants to post a PR comment.
32585ad to
c2b94e8
Compare
aibaars
left a comment
There was a problem hiding this comment.
This looks really good. @adityasharad and @esbena Could you have a quick look at this too to check for potential security issues?
| if [ -s comment_id.txt ] | ||
| then | ||
| COMMENT_ID="$(grep -o '^[0-9]\+$' comment_id.txt)" | ||
| fi | ||
|
|
||
| if [ -z "$COMMENT_ID" ] | ||
| then | ||
| # Create new comment | ||
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" -X POST --input - | ||
| else | ||
| # Update existing comment | ||
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -X PATCH --input - | ||
| fi |
There was a problem hiding this comment.
There is no need for a double if. The case where comment_id.txt exists but does not contain a number is an error condition that should never happen. I think the workflow wouldn't even fall through to the -z "${COMMENT_ID" check because grep fails with a non-zero exit code if the pattern isn't matched.
| if [ -s comment_id.txt ] | |
| then | |
| COMMENT_ID="$(grep -o '^[0-9]\+$' comment_id.txt)" | |
| fi | |
| if [ -z "$COMMENT_ID" ] | |
| then | |
| # Create new comment | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" -X POST --input - | |
| else | |
| # Update existing comment | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -X PATCH --input - | |
| fi | |
| if [ -s comment_id.txt ] | |
| then | |
| COMMENT_ID="$(grep -o '^[0-9]\+$' comment_id.txt)" | |
| # Update existing comment | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -X PATCH --input - | |
| else | |
| # Create new comment | |
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" -X POST --input - | |
| fi |
There was a problem hiding this comment.
Unfortunately, the jq expression to select the ID of the most recent matching comment ([ ... ] | max) will return null if there are no matching comments, and gh will for some reason render that as an empty line. This means we end up with a comment_id.txt containing just a newline, which bash considers a non-empty file (i.e. -s comment_id.txt == true).
The approach I've gone with is to catch the grep failure and then use [ $COMMENT_ID ] as the test.
| @@ -1,7 +1,25 @@ | |||
| name: Query help preview | |||
| # This workflow checks for any changes in .qhelp files in pull requests. | |||
| # For any changed files, it renders them to HTML in a file called `comment_body.txt`. | |||
There was a problem hiding this comment.
| # For any changed files, it renders them to HTML in a file called `comment_body.txt`. | |
| # For any changed files, it renders them to markdown in a file called `comment_body.txt`. |
| # Update existing comment | ||
| jq --rawfile body comment_body.txt '{"body":$body}' -n | gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -X PATCH --input - | ||
| else | ||
| echo "Comment ${COMMENT_ID} did not pass validations: not editing." |
There was a problem hiding this comment.
${COMMENT_ID} will be empty so printing it is not useful. Let's print the error message to stderr, and exit with a non-zero exit code to make the workflow fail.
| echo "Comment ${COMMENT_ID} did not pass validations: not editing." | |
| >&2 echo "error: comment ID did not pass validations: not editing." | |
| exit 1 |
| FILTER="select(.issue_url | test(\"${GITHUB_REPOSITORY}/issues/${PR_NUMBER}$\")) \ | ||
| | select(.body | test(\"^${COMMENT_PREFIX}\")) \ | ||
| | select(.user.login == \"${COMMENT_AUTHOR}\") \ | ||
| | .id" | ||
| COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" | jq "${FILTER}") |
There was a problem hiding this comment.
| FILTER="select(.issue_url | test(\"${GITHUB_REPOSITORY}/issues/${PR_NUMBER}$\")) \ | |
| | select(.body | test(\"^${COMMENT_PREFIX}\")) \ | |
| | select(.user.login == \"${COMMENT_AUTHOR}\") \ | |
| | .id" | |
| COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" | jq "${FILTER}") | |
| FILTER="select(.issue_url | endswith($urlsuffix) \ | |
| | select(.body | startswith($bodyprefix)) \ | |
| | select(.user.login == $login) \ | |
| | .id" | |
| COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" | jq --arg urlsuffix "${GITHUB_REPOSITORY}/issues/${PR_NUMBER}" --arg bodyprefix "${COMMENT_PREFIX}" --arg login "${COMMENT_AUTHOR}" "${FILTER}") |
Here we are using string interpolations to construct scripts. We are dealing with a jq script instead of a shell script, and we control the inputs, so it is probably safe in practice. Still, it is preferable to use --arg instead. See my (untested) suggestion.
Also, the suggestion has the startswith and endswith jq functions to avoid the potential special characters in the (hardwired inputs) (especially .* which is not unreasonable to have in markdown like ***QHelp Preview.***).
And print an error message to STDERR.
| | select(.body | startswith($prefix)) | ||
| | select(.user.login == $author) | ||
| | .id' | ||
| COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" | jq --arg repo "${GITHUB_REPOSITORY}" --arg pr "${PR_NUMBER}" --arg prefix "${COMMENT_PREFIX}" --arg author "${COMMENT_AUTHOR}" "${FILTER}") |
There was a problem hiding this comment.
| COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" | jq --arg repo "${GITHUB_REPOSITORY}" --arg pr "${PR_NUMBER}" --arg prefix "${COMMENT_PREFIX}" --arg author "${COMMENT_AUTHOR}" "${FILTER}") | |
| COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${RAW_COMMENT_ID}" | jq --arg repo "${GITHUB_REPOSITORY}" --arg pr "${PR_NUMBER}" --arg prefix "${COMMENT_PREFIX}" --arg author "${COMMENT_AUTHOR}" "${FILTER}") |
typo?
| run: | | ||
| # Find the latest comment starting with "QHelp previews" | ||
| COMMENT_PREFIX="QHelp previews" | ||
| gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate --jq "[.[] | select(.body|startswith(\"${COMMENT_PREFIX}\")) | .id] | max" > comment_id.txt |
There was a problem hiding this comment.
(nit that we can merge without addressing: jq ... --arg ... should be used instead of environment variable interpolation. )
esbena
left a comment
There was a problem hiding this comment.
LGTM now. Thanks for the perseverance!
Update the Ruby QHelp preview workflow so that if there's already a comment on the PR with the QHelp preview, it will update that comment instead of creating a new one. The old versions are still visible in the comment history.
This makes use of https://github.com/peter-evans/create-or-update-comment and https://github.com/peter-evans/find-comment, both of which are mentioned in the GH Actions docs so I feel they're relatively legitimate.
I've tested this on my fork and it seems to work correctly.
I've also renamed some things and added some comments to try to explain the split workflow design (as I understand it).
@aibaars I believe you wrote the existing workflow - how do you feel about this change?