Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create single status comment and correctly dismiss reviews #2171

Merged
merged 4 commits into from
Mar 6, 2024

Conversation

gregfurman
Copy link
Contributor

Closes #1862


Overview

This PR allows for a single status comment to be repeatedly created and updated, illustrating an overview state of the minder evaluation. This summary comment is made in conjunction with the minder review comment.

Summary of changes

  • New interface for comments that includes formatting, more info, and emojis :octocat:
  • Creates or updates a single summary comment atop a PR that is edited whenever a new evaluation is run
  • Finds correct prior minder review from GitHub API and (when applicable) dismisses it and creates a new review
  • Include separate internal/engine/eval/vulncheck/report.go that handles review and status comment templating
  • Checks if review or comment is from user stacklokbot -- preventing a user from abusing magic comment checks

To do


Example comments

Example Summary comment


Minder Vulnerability Report ⚠️

Minder found vulnerable dependencies in this PR. Either push an updated version or accept the proposed changes. Note that accepting the changes will include Minder as a co-author of this PR.

Vulnerability scan of 27d6810b:

📊 View Full Review

  • 🐞 vulnerabilities: 1
  • 🛠 fixes: 1
Package Version #Vulnerabilities #Fixes Patch
mongodb 0.5.0 1 1 0.6.0

📬 Have feedback? Share it here.

Example Review Comment


Summary of vulnerabilities found

Minder found the following vulnerabilities in this PR:

Ecosystem Name Version Vulnerability ID Summary Introduced Fixed
npm mongodb 0.5.0 mongodb 0.6.0

📬 Have feedback on the report? Share it here.

@JAORMX JAORMX marked this pull request as ready for review January 23, 2024 06:29
@JAORMX JAORMX requested a review from a team as a code owner January 23, 2024 06:29
@JAORMX
Copy link
Contributor

JAORMX commented Jan 23, 2024

I accidentally marked it off draft since I was reviewing from my phone.

@JAORMX JAORMX marked this pull request as draft January 23, 2024 07:24
Copy link
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

This is great, thanks for working on improving the UX and fixing the bug! I left a couple of comments inline.

const (
reviewBodyMagicComment = "<!-- minder: pr-review-body -->"
statusBodyMagicComment = "<!-- minder: pr-status-body -->"
separatorComment = "<!--[SEP]-->"
Copy link
Contributor

Choose a reason for hiding this comment

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

separatorComment seems to be unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Separator is meant for adding metadata to the body of the review and allowing us to easily parse it. So we can include data from a previous review as a comment in JSON form and use it to provide a comparison to the current review. i.e the below will provide info on the previous status report:

<!-- minder: pr-status-body --><!--[SEP]--><!--{ {"VulnerabilityCount":1,"RemediationCount":0,"TrackedDepsCount":1,"CommitSHA":"27d6810b861c81e8c61e09c651875f5a976781d1"} }--><!--[SEP]-->

        <h2>Minder Vulnerability Report &#9888;&#65039;</h2>
...

Probably should have made this more clear. It was also mentioned it here #2149 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, but the constant separatorComment is still unused, the "string constant is used verbatim in minderTemplateString`.

Previous Minder review was dismissed because the PR was updated.
`
vulnFoundWithNoPatch = "Vulnerability found, but no patched version exists yet."
stacklokBotUser = "stacklokbot"
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, I don't think we want to hardcode a bot username here. Later the code does:

		isMinder := review.GetUser().GetLogin() == stacklokBotUser && strings.HasPrefix(review.GetBody(), reviewBodyMagicComment)

Could we instead use:

		isMinder := review.GetUser().GetLogin() == ghCli.GetAuthenticatedUser() && strings.HasPrefix(review.GetBody(), reviewBodyMagicComment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haven't looked into that function. Do we expect ghCli.GetAuthenticatedUser() to return stacklokbot or the user who triggered the pipeline?

Copy link
Contributor

Choose a reason for hiding this comment

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

It would return the user who enrolled that particular org or personal account, so in the case of stacklok's deployment, it would be stacklokbot.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

for _, r := range reviews {
if strings.HasPrefix(r.GetBody(), reviewBodyMagicComment) && r.GetState() != "DISMISSED" {
ra.minderReview = r
for i := len(reviews) - 1; i >= 0; i-- {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is done because one of the calls returns reviews in reverse order, right? Could you add a comment here so that we remember the reason later?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes exactly. Very fair point. Will add a comment.

}

func (ra *reviewPrHandler) findPreviousStatusComment(ctx context.Context) error {
comments, err := ra.cli.ListComments(ctx, ra.pr.RepoOwner, ra.pr.RepoName, int(ra.pr.Number),
Copy link
Contributor

Choose a reason for hiding this comment

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

oh so ListComments allows you to set the sorting, but ListReviews does not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I think this is the crux of the issues I was seeing. So the ra.cli.ListComments method wraps c.client.PullRequests.ListComments, its result is what the code iterates over and saves into ra.minderStatusReport. But, the comments are created with ra.cli.CreateComment which uses c.client.Issues.CreateComment - it's a different API. The Issues one is for what's usually referred to as comments, but the PullRequest API is for iterating over review comments, those that are submitted either inline or as the review summary.

I know this work has been going on for some time, I wonder if there's a reason those two got mixed up?

fixHtmlEmoji = "&#128736;"
warningHtmlEmoji = "&#9888;&#65039;"
successHtmlEmoji = "&#9989;"
reviewHtmlEmoji = "&#128202;"
Copy link
Contributor

Choose a reason for hiding this comment

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

Love the bling!

@gregfurman gregfurman force-pushed the 1862-single-review branch 3 times, most recently from be86330 to 2a33343 Compare January 23, 2024 12:59
@gregfurman gregfurman marked this pull request as ready for review January 23, 2024 13:00
@jhrozek
Copy link
Contributor

jhrozek commented Jan 24, 2024

Thanks for handling the comments!

I've tested the PR and there seems to be a case that we don't handle correctly - when I ran minder as myself (meaning the same user who submitted the PR was reviewing) and pushed a new version of a patch (I started with vulnerable python-requests=2.19.0 and as an update I tried to push 2.20.0 which still contain CVEs) I saw:

{"level":"debug","profile":"acme-github-profile-pr-vuln-check","ruleType":"pr_vulnerability_check","eval_status":"error","projectId":"ded12968-7cc4-456e-bc1d-75f28fd2bac9","repositoryId":"40a157bc-2af1-44f6-a458-701aa415bcce","exception.message":"failed to submit pr action: could not dismiss previous review: could not dismiss review: error dismissing review 1840
858980 for PR jakubtestorg/bad-python/73: PUT https://api.github.com/repos/jakubtestorg/bad-python/pulls/73/reviews/1840858980/dismissals: 422 Validation Failed [{Resource: Field: Code: Message:Can not dismiss a commented pull request review}]","Timestamp":1706087836440010000,"message":"result - evaluation"}
{"level":"info","action":"remediate","action_status":"skipped","Timestamp":1706087836440025000,"message":"result - action"}

and I didn't see any more updates from minder..

It seems like we regressed handling dismissing of comment reviews in this patch?

@gregfurman
Copy link
Contributor Author

gregfurman commented Jan 24, 2024

Hi @jhrozek

That is strange. So to clarify, one was previously able to dismiss reviews made by the same own author as the PR?

Here is what I think is happening, and am not sure if these changes introduce this issue. The newReviewPrHandler function in review.go stipulates the following:

	// if the user wants minder to request changes on a pull request, they need to
	// be different identities
	var failStatus *string
	if pr.AuthorId == cliUser.GetID() {
		failStatus = github.String("COMMENT")
		logger.Debug().Msg("author is the same as the authenticated user, can only comment")
	} else {
		failStatus = github.String("REQUEST_CHANGES")
		logger.Debug().Msg("author is different than the authenticated user, can request changes")
	}

So in the case of your PR, jhrozek is both the author and the de-facto reviewer. This response is therefore to be expected since the author (you) is triggering minder reviews -- which are of type COMMENT as opposed to REQUESTED_CHANGES. Since comment reviews cannot be dismissed this error is thrown (see MorrisonCole/pr-lint-action#155 (comment)).

Also, are you sure you are using this branch version of minder? I don't see any tables being generated in the review comment for the PR you are testing on.

@jhrozek
Copy link
Contributor

jhrozek commented Jan 24, 2024

Hi @jhrozek

That is strange. So to clarify, one was previously able to dismiss reviews made by the same own author as the PR?

I don't think it would dismiss the review, just create another comment. Here is a PR where I started with .20 and then went to .19 and saw a subsequent comment. This one was running main. Before that I tested with another PR running your branch where I pushed an in-between version, but saw no inline comment. I /think/ the summary was updated, but the formatting looks a bit off, too

Here is what I think is happening, and am not sure if these changes introduce this issue. The newReviewPrHandler function in review.go stipulates the following:

	// if the user wants minder to request changes on a pull request, they need to
	// be different identities
	var failStatus *string
	if pr.AuthorId == cliUser.GetID() {
		failStatus = github.String("COMMENT")
		logger.Debug().Msg("author is the same as the authenticated user, can only comment")
	} else {
		failStatus = github.String("REQUEST_CHANGES")
		logger.Debug().Msg("author is different than the authenticated user, can request changes")
	}

So in the case of your PR, jhrozek is both the author and the de-facto reviewer. This response is therefore to be expected since the author (you) is triggering minder reviews -- which are of type COMMENT as opposed to REQUESTED_CHANGES. Since comment reviews cannot be dismissed this error is thrown (see MorrisonCole/pr-lint-action#155 (comment)).

I think this is pretty much what is happening. I'll try to poke either later today or tomorrow again to understand better what's causing the issue.

Also, are you sure you are using this branch version of minder? I don't see any tables being generated in the review comment for the PR you are testing on.

jhrozek
jhrozek previously requested changes Feb 2, 2024
Copy link
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

Thank you for your patience, I apologise it took me so long to get back to this review. I think the primary issue is the mixup of the Issues and PullReques APIs, otherwise the code looks quite good to me.

Some minor comments inline.

const (
reviewBodyMagicComment = "<!-- minder: pr-review-body -->"
statusBodyMagicComment = "<!-- minder: pr-status-body -->"
separatorComment = "<!--[SEP]-->"
Copy link
Contributor

Choose a reason for hiding this comment

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

yes, but the constant separatorComment is still unused, the "string constant is used verbatim in minderTemplateString`.

}

func (ra *reviewPrHandler) findPreviousStatusComment(ctx context.Context) error {
comments, err := ra.cli.ListComments(ctx, ra.pr.RepoOwner, ra.pr.RepoName, int(ra.pr.Number),
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I think this is the crux of the issues I was seeing. So the ra.cli.ListComments method wraps c.client.PullRequests.ListComments, its result is what the code iterates over and saves into ra.minderStatusReport. But, the comments are created with ra.cli.CreateComment which uses c.client.Issues.CreateComment - it's a different API. The Issues one is for what's usually referred to as comments, but the PullRequest API is for iterating over review comments, those that are submitted either inline or as the review summary.

I know this work has been going on for some time, I wonder if there's a reason those two got mixed up?

<th>Patch</th>
</tr>
</thead>
<tbody>
Copy link
Contributor

Choose a reason for hiding this comment

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

For reason I really don't understand the <tbody> tag is not rendered correctly for me, see this example.

I wonder if we need the thead and tbody tags at all, can't we get away with just tr?

Copy link
Contributor Author

@gregfurman gregfurman Feb 12, 2024

Choose a reason for hiding this comment

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

As far as I understand, using the Issues API is not actually an issue since every PR is technically an issue (according to the GitHub docs).

See https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#about-issue-and-pull-request-comments:

You can use the REST API to create and manage comments on issues and pull requests. Every pull request is an issue, but not every issue is a pull request. For this reason, "shared" actions for both features, like managing assignees, labels, and milestones, are provided within the Issues endpoints. To manage pull request review comments, see "Pull request review comments."

With that said, I'll change it to the PullRequest API for correctness.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In order to properly test this, the account making the comments (the author) cannot be the same as the account submitting a review (reviewer) because of the constraints outlined in #2171 (comment).

I'm also going to remove the section of the status comment that links the latest review -- since it's unnecessarily complicating things now that the status comment is required to be posted prior to the review comment.

}

if err := ra.dismissReview(ctx); err != nil {
return fmt.Errorf("could not dismiss previous review: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that previously error on dismiss review was not fatal, this was on purpose so that minder wouldn't error out trying to dismiss its own review running as the same use. I wonder if we could add this to body of dismissReview:

+       if ra.pr.AuthorId == ra.authorizedUser.GetID() {
+               ra.logger.Debug().Msg("author is the same as the authenticated user, can't dismiss")
+               return nil
+       }
+

this is pretty much the same as ignoring the error, except we don't actually attempt the call so we save a token..

@coveralls
Copy link

coveralls commented Feb 22, 2024

Coverage Status

coverage: 38.111% (+0.6%) from 37.465%
when pulling 2cf3dc8 on gregfurman:1862-single-review
into f846a56 on stacklok:main.

@jhrozek
Copy link
Contributor

jhrozek commented Feb 22, 2024

Hi,

I have two apologies - first for the delay, last week Stacklok met in person and not much development got done and so we're slow in PR reviews.

Second, re-reading this PR and our chats on Discord, we must have confused you and sent you down the path of implementing something that's too complex. So I pushed another patch atop yours (we squash commits so you'd still be attributed for this PR!) that I think simplifies the flow in the sense that the inline reviews are still retained, but Minder replies with inline comments only and the whole summary including the CVE table is kept in the single summary comment you introduced.

Would this approach work for you?

@gregfurman
Copy link
Contributor Author

Hi,

I have two apologies - first for the delay, last week Stacklok met in person and not much development got done and so we're slow in PR reviews.

Second, re-reading this PR and our chats on Discord, we must have confused you and sent you down the path of implementing something that's too complex. So I pushed another patch atop yours (we squash commits so you'd still be attributed for this PR!) that I think simplifies the flow in the sense that the inline reviews are still retained, but Minder replies with inline comments only and the whole summary including the CVE table is kept in the single summary comment you introduced.

Would this approach work for you?

Hi. No worries. Happy to go with the above approach and have these changes merged in 😄

evankanderson
evankanderson previously approved these changes Mar 5, 2024
TrackedDependencies []dependencyVulnerabilities
}

func (r *vulnSummaryReport) render() (string, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Not to block this, but it feels odd to assemble this from three templates, rather than one larger template that takes r.TrackedDependencies as the input, rather than looping outside the template over the tableVulnerabilitiesRows repeatedly.

(Also, the header template doesn't seem to have any template stuff in it, so it could just be treated like the footer.)

@@ -305,52 +256,93 @@ func (ra *reviewPrHandler) setStatus() {
ra.logger.Debug().Str("status", *ra.status).Msg("will set review status")
}

func (ra *reviewPrHandler) findPreviousReview(ctx context.Context) error {
reviews, err := ra.cli.ListReviews(ctx, ra.pr.RepoOwner, ra.pr.RepoName, int(ra.pr.Number), nil)
func (ra *reviewPrHandler) findPreviousStatusComment(ctx context.Context) error {
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice if this function didn't pass the found review as a struct field, but again, I'm not going to block this review on existing code styles.

return mci
}

func (ra *reviewPrHandler) submitReview(ctx context.Context, mci magicCommentInfo) (int64, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Future comment -- it would make sense to include a comment indicating what the returned int64 is. In this case, it appears to be the issue / PR number.

@evankanderson evankanderson dismissed jhrozek’s stale review March 5, 2024 22:05

Obsoleted by further changes.

@evankanderson
Copy link
Member

Thanks very much! Unfortunately, it looks like there are a couple of conflicts, so I can't merge. @jhrozek or @gregfurman if you could resolve the conflicts, I'm happy to merge this despite the small nits.

This commit builds atop the previous one and moves further so that the
message in the review reply (not to be confused with inline replies) are
empty and all the feedback is kept in the single summary comment.

Because we still need to dismiss the reviews, but now we don't have a
review text body where we used to keep the magic comment with metadata,
we keep it in the summary comment instead.

We also keep the review ID there because we can't be sure which is the
minder review now in case the same identity is used for a human identity
and minder.
@jhrozek
Copy link
Contributor

jhrozek commented Mar 6, 2024

Thanks very much! Unfortunately, it looks like there are a couple of conflicts, so I can't merge. @jhrozek or @gregfurman if you could resolve the conflicts, I'm happy to merge this despite the small nits.

Thank you for the review @evankanderson. If you don't mind I will address the comments you had as part of #1203 (which is getting more and more pressing as we have a fair bit of code duplication now between the reply machinery for trusty and OSV)

@evankanderson evankanderson merged commit 9d2df9e into mindersec:main Mar 6, 2024
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Minder to avoid commenting each time there's a change on a PR
5 participants