Skip to content

Commit c51f96b

Browse files
authored
referee, prow, plugin: add plugin that holds PRs on too many retest comments (kubevirt#3251)
* referee, prow, plugin: initial prototype This commit provides a simple tool that counts the number of retest comments after the latest push to the given PR. Also it provides the server implementation for the prow external plugin. Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: add bazel build files Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: add files for publishing image Add Makefile, Containerfile and job definition for publishing the plugin image. Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: add service and deployment for k8s Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: enable in prow plugin config Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: test event handling Extract interfaces to be able to mock out graphql client and github client dependencies. Then create a couple of test cases to make sure that the botuser doesn't handle it's own comments. Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: ping team on hold comment Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: leave PR alone if hold placed In case a PR already has received a hold comment we don't handle it again. The intent is that the author and a team has already been pinged on the PR, and we leave it up to them to handle this now. Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: fix review issues Signed-off-by: Daniel Hiller <[email protected]> * referee, prow, plugin: add PR labels to evaluation We now also consider whether a previous hold was already issued before by the bot user, and if so, we do not place another hold again. The hold is not placed even if it is not present any more. This covers the case where the hold was placed once by the botuser and then was taken back by someone else. Note: updated robots/cmd/retests-to-merge to fetch PRs on itself. Signed-off-by: Daniel Hiller <[email protected]> * prow, plugin, referee: add unit tests for labels Signed-off-by: Daniel Hiller <[email protected]> * robots, retests-to-merge: add README, refine output Refines the output of the cli tool to sort the output starting with highest number of retest PRs first. Adds README and updates main README to contain entry for tool. Signed-off-by: Daniel Hiller <[email protected]> * prow, plugin, referee: update wording in comment Add references to k/kubevirt CONTRIBUTING and getting-started#testing. Signed-off-by: Daniel Hiller <[email protected]> --------- Signed-off-by: Daniel Hiller <[email protected]>
1 parent e3f3d6a commit c51f96b

29 files changed

+1757
-2
lines changed

Diff for: README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ depending on billing alerts. See [README](limiter/README.md)
5555

5656
* `robots/cmd/labels-checker`: Checks whether a PR has certain labels
5757

58+
* `robots/cmd/retests-to-merge`: Tool to check recent approved PRs for retest comments. See
59+
[README](robots/cmd/retests-to-merge/README.md)
60+
5861
* `robots/cmd/uploader`: Tool to mirror bazel dependencies on GCS. See
59-
[README](plugins/cmd/uploader/README.md)
62+
[README](robots/cmd/uploader/README.md)
6063

6164
## Contributing
6265

Diff for: external-plugins/referee/BUILD.bazel

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["main.go"],
6+
importpath = "kubevirt.io/project-infra/external-plugins/referee",
7+
visibility = ["//visibility:private"],
8+
deps = [
9+
"//external-plugins/referee/ghgraphql:go_default_library",
10+
"//external-plugins/referee/server:go_default_library",
11+
"@com_github_shurcool_githubv4//:go_default_library",
12+
"@com_github_sirupsen_logrus//:go_default_library",
13+
"@io_k8s_test_infra//pkg/flagutil:go_default_library",
14+
"@io_k8s_test_infra//prow/config/secret:go_default_library",
15+
"@io_k8s_test_infra//prow/flagutil:go_default_library",
16+
"@io_k8s_test_infra//prow/interrupts:go_default_library",
17+
"@io_k8s_test_infra//prow/pluginhelp/externalplugins:go_default_library",
18+
"@org_golang_x_oauth2//:go_default_library",
19+
],
20+
)
21+
22+
go_binary(
23+
name = "referee",
24+
embed = [":go_default_library"],
25+
visibility = ["//visibility:public"],
26+
)

Diff for: external-plugins/referee/Makefile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
.PHONY: all build test format push
3+
all: format push
4+
bazelbin := bazelisk
5+
6+
build:
7+
$(bazelbin) build //external-plugins/referee/...
8+
9+
test:
10+
$(bazelbin) test //external-plugins/referee/...
11+
12+
format:
13+
gofmt -w .
14+
15+
push:
16+
podman build -f ../../images/referee/Containerfile -t quay.io/kubevirtci/referee:latest && podman push quay.io/kubevirtci/referee:latest

Diff for: external-plugins/referee/ghgraphql/BUILD.bazel

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = [
6+
"client.go",
7+
"labels.go",
8+
"pullrequests.go",
9+
"timeline.go",
10+
"types.go",
11+
],
12+
importpath = "kubevirt.io/project-infra/external-plugins/referee/ghgraphql",
13+
visibility = ["//visibility:public"],
14+
deps = [
15+
"@com_github_shurcool_githubv4//:go_default_library",
16+
"@com_github_sirupsen_logrus//:go_default_library",
17+
],
18+
)
19+
20+
go_test(
21+
name = "go_default_test",
22+
srcs = [
23+
"ghgraphql_suite_test.go",
24+
"labels_test.go",
25+
],
26+
deps = [
27+
":go_default_library",
28+
"@com_github_onsi_ginkgo_v2//:go_default_library",
29+
"@com_github_onsi_gomega//:go_default_library",
30+
],
31+
)

Diff for: external-plugins/referee/ghgraphql/client.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* This file is part of the KubeVirt project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* Copyright the KubeVirt Authors.
17+
*
18+
*/
19+
20+
package ghgraphql
21+
22+
import "github.com/shurcooL/githubv4"
23+
24+
type GitHubGraphQLClient interface {
25+
// FetchPRTimeLineForLastCommit returns specific events a PR has received
26+
// after the last commit or force push.
27+
FetchPRTimeLineForLastCommit(org string, repo string, prNumber int) (PRTimelineForLastCommit, error)
28+
29+
FetchPRLabels(org string, repo string, prNumber int) (PRLabels, error)
30+
31+
FetchOpenApprovedAndLGTMedPRs(org string, repo string) (PullRequests, error)
32+
}
33+
34+
type gitHubGraphQLClient struct {
35+
gitHubClient *githubv4.Client
36+
}
37+
38+
func NewClient(gitHubClient *githubv4.Client) GitHubGraphQLClient {
39+
return gitHubGraphQLClient{gitHubClient: gitHubClient}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* This file is part of the KubeVirt project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* Copyright the KubeVirt Authors.
17+
*
18+
*/
19+
20+
package ghgraphql_test
21+
22+
import (
23+
"testing"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
)
28+
29+
func TestGHGraphQL(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecs(t, "GHGraphQL Suite")
32+
}

Diff for: external-plugins/referee/ghgraphql/labels.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* This file is part of the KubeVirt project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* Copyright the KubeVirt Authors.
17+
*
18+
*/
19+
20+
package ghgraphql
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"github.com/shurcooL/githubv4"
26+
)
27+
28+
func (g gitHubGraphQLClient) FetchPRLabels(org string, repo string, prNumber int) (PRLabels, error) {
29+
labels, err := g.fetchLabelsForPR(org, repo, prNumber)
30+
if err != nil {
31+
return PRLabels{}, err
32+
}
33+
return NewPRLabels(labels), nil
34+
}
35+
36+
func (g gitHubGraphQLClient) fetchLabelsForPR(org string, repo string, prNumber int) ([]Label, error) {
37+
var query struct {
38+
Repository struct {
39+
PullRequest struct {
40+
Labels Labels `graphql:"labels (first: 100)"`
41+
} `graphql:"pullRequest(number: $prNumber)"`
42+
} `graphql:"repository(owner: $org, name: $repo)"`
43+
}
44+
variables := map[string]interface{}{
45+
"prNumber": githubv4.Int(prNumber),
46+
"org": githubv4.String(org),
47+
"repo": githubv4.String(repo),
48+
}
49+
50+
err := g.gitHubClient.Query(context.Background(), &query, variables)
51+
if err != nil {
52+
return []Label{}, fmt.Errorf("failed to use github query %+v with variables %v: %w", query, variables, err)
53+
}
54+
return query.Repository.PullRequest.Labels.Nodes, nil
55+
}
56+
57+
func NewPRLabels(labels []Label) PRLabels {
58+
prLabels := PRLabels{
59+
Labels: labels,
60+
}
61+
for _, label := range labels {
62+
if label.Name == "do-not-merge/hold" {
63+
prLabels.IsHoldPresent = true
64+
}
65+
}
66+
return prLabels
67+
}

Diff for: external-plugins/referee/ghgraphql/labels_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* This file is part of the KubeVirt project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* Copyright the KubeVirt Authors.
17+
*
18+
*/
19+
20+
package ghgraphql_test
21+
22+
import (
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
"kubevirt.io/project-infra/external-plugins/referee/ghgraphql"
26+
)
27+
28+
var _ = Describe("Labels", func() {
29+
DescribeTable("NewLabels", func(labels []ghgraphql.Label, expected ghgraphql.PRLabels) {
30+
Expect(ghgraphql.NewPRLabels(labels)).To(BeEquivalentTo(expected))
31+
},
32+
Entry("no labels don't set IsHoldPresent", nil, ghgraphql.PRLabels{
33+
IsHoldPresent: false,
34+
}),
35+
Entry("some labels don't set IsHoldPresent", []ghgraphql.Label{
36+
{Name: "test"},
37+
}, ghgraphql.PRLabels{
38+
IsHoldPresent: false,
39+
Labels: []ghgraphql.Label{
40+
{Name: "test"},
41+
},
42+
}),
43+
Entry("exactly one label sets IsHoldPresent", []ghgraphql.Label{
44+
{Name: "ok-to-test"},
45+
{Name: "do-not-merge/hold"},
46+
{Name: "needs-rebase"},
47+
}, ghgraphql.PRLabels{
48+
IsHoldPresent: true,
49+
Labels: []ghgraphql.Label{
50+
{Name: "ok-to-test"},
51+
{Name: "do-not-merge/hold"},
52+
{Name: "needs-rebase"},
53+
},
54+
}),
55+
)
56+
})

Diff for: external-plugins/referee/ghgraphql/pullrequests.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* This file is part of the KubeVirt project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* Copyright the KubeVirt Authors.
17+
*
18+
*/
19+
20+
package ghgraphql
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"github.com/shurcooL/githubv4"
26+
)
27+
28+
func (g gitHubGraphQLClient) FetchOpenApprovedAndLGTMedPRs(org string, repo string) (PullRequests, error) {
29+
labels, err := g.fetchPRs(org, repo)
30+
if err != nil {
31+
return PullRequests{}, err
32+
}
33+
pullRequests := PullRequests{
34+
PRs: labels,
35+
}
36+
return pullRequests, nil
37+
}
38+
39+
func (g gitHubGraphQLClient) fetchPRs(org string, repo string) ([]PullRequest, error) {
40+
var query struct {
41+
Repository struct {
42+
PullRequests struct {
43+
Nodes []PullRequest
44+
} `graphql:"pullRequests(first: 100, baseRefName: \"main\", labels: [\"lgtm\", \"approved\"], states: [OPEN])"`
45+
} `graphql:"repository(owner: $org, name: $repo)"`
46+
}
47+
variables := map[string]interface{}{
48+
"org": githubv4.String(org),
49+
"repo": githubv4.String(repo),
50+
}
51+
52+
err := g.gitHubClient.Query(context.Background(), &query, variables)
53+
if err != nil {
54+
return []PullRequest{}, fmt.Errorf("failed to use github query %+v with variables %v: %w", query, variables, err)
55+
}
56+
return query.Repository.PullRequests.Nodes, nil
57+
}

0 commit comments

Comments
 (0)