Skip to content

Update existing qhelp comment, if it exists#8618

Merged
hmac merged 15 commits intogithub:mainfrom
hmac:hmac/qlhelp-comment-workflow
Apr 21, 2022
Merged

Update existing qhelp comment, if it exists#8618
hmac merged 15 commits intogithub:mainfrom
hmac:hmac/qlhelp-comment-workflow

Conversation

@hmac
Copy link
Contributor

@hmac hmac commented Mar 30, 2022

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?

@hmac hmac force-pushed the hmac/qlhelp-comment-workflow branch 2 times, most recently from 6a7ee0a to 8afaf75 Compare March 31, 2022 01:26
@github github deleted a comment from github-actions bot Mar 31, 2022
@github github deleted a comment from github-actions bot Mar 31, 2022
@github github deleted a comment from github-actions bot Mar 31, 2022
@github github deleted a comment from github-actions bot Mar 31, 2022
@hmac hmac force-pushed the hmac/qlhelp-comment-workflow branch from a6e275e to 4312f8f Compare March 31, 2022 01:48
@github github deleted a comment from github-actions bot Mar 31, 2022
@hmac hmac requested a review from aibaars March 31, 2022 01:52
@hmac hmac marked this pull request as ready for review March 31, 2022 01:53
Copy link
Contributor

@aibaars aibaars left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to run this code as part of the unprivileged workflow?


- id: get-pr-number
run: |
pr="$(cat pr.txt)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hmac hmac force-pushed the hmac/qlhelp-comment-workflow branch 2 times, most recently from e6c24f5 to 10c82c1 Compare April 1, 2022 02:19
@hmac
Copy link
Contributor Author

hmac commented Apr 1, 2022

I've updated the on: pull_request workflow so it uploads the following artifacts:

  • pr_number.txt - the number of the PR
  • comment_body.txt - the comment text containing the rendered QHelp
  • comment_id.txt - the ID of the existing QHelp comment, if it exists. This file is empty otherwise.

The on: workflow_run workflow then does the following:

  1. Download the artifacts.
  2. Check the PR SHA matches the workflow SHA.
  3. If comment_id.txt contains a comment ID, update that comment with the contents of comment_body.txt.
  4. Otherwise, create a new comment with the contents of comment_body.txt.

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.

@hmac hmac requested a review from aibaars April 1, 2022 02:25
@@ -0,0 +1,57 @@
# This workflow is the second part of the process described in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not rename this file. It can actually be used by other (future) jobs that need to post PR comments.

Copy link
Contributor

@aibaars aibaars left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +51 to +54
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 -
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@hmac hmac force-pushed the hmac/qlhelp-comment-workflow branch from 10c82c1 to 32585ad Compare April 3, 2022 22:14
@hmac hmac requested a review from a team April 3, 2022 22:14
@github-actions github-actions bot added the JS label Apr 3, 2022
hmac added 3 commits April 4, 2022 10:16
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.
@hmac hmac force-pushed the hmac/qlhelp-comment-workflow branch from 32585ad to c2b94e8 Compare April 3, 2022 22:16
@hmac hmac removed the JS label Apr 3, 2022
@hmac hmac removed the request for review from a team April 3, 2022 22:17
Copy link
Contributor

@aibaars aibaars left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good. @adityasharad and @esbena Could you have a quick look at this too to check for potential security issues?

Comment on lines +43 to +55
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${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.

Suggested change
echo "Comment ${COMMENT_ID} did not pass validations: not editing."
>&2 echo "error: comment ID did not pass validations: not editing."
exit 1

Comment on lines +56 to +60
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}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.***).

aibaars
aibaars previously approved these changes Apr 14, 2022
| 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}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit that we can merge without addressing: jq ... --arg ... should be used instead of environment variable interpolation. )

Copy link
Contributor

@esbena esbena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now. Thanks for the perseverance!

@hmac hmac merged commit 3ea6ba5 into github:main Apr 21, 2022
@hmac hmac deleted the hmac/qlhelp-comment-workflow branch April 21, 2022 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants