k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/robots/pr-labeler/main.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 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 17 // PR labeler provides a way to add a missing ok-to-test label on trusted PRs. 18 // 19 // The --token-path determines who interacts with github. 20 // By default PR labeler runs in dry mode, add --confirm to make it leave comments. 21 package main 22 23 import ( 24 "flag" 25 "log" 26 "net/url" 27 28 "k8s.io/apimachinery/pkg/util/sets" 29 "sigs.k8s.io/prow/pkg/config/secret" 30 "sigs.k8s.io/prow/pkg/flagutil" 31 "sigs.k8s.io/prow/pkg/github" 32 ) 33 34 const ( 35 needsOkToTest = "needs-ok-to-test" 36 okToTest = "ok-to-test" 37 ) 38 39 type client interface { 40 AddLabel(org, repo string, number int, label string) error 41 GetIssueLabels(org, repo string, number int) ([]github.Label, error) 42 GetPullRequests(org, repo string) ([]github.PullRequest, error) 43 ListCollaborators(org, repo string) ([]github.User, error) 44 ListOrgMembers(org, role string) ([]github.TeamMember, error) 45 } 46 47 type options struct { 48 confirm bool 49 endpoint flagutil.Strings 50 graphqlEndpoint string 51 org string 52 repo string 53 tokenPath string 54 trustCollaborators bool 55 } 56 57 func flagOptions() options { 58 o := options{ 59 endpoint: flagutil.NewStrings(github.DefaultAPIEndpoint), 60 } 61 flag.StringVar(&o.graphqlEndpoint, "graphql-endpoint", github.DefaultGraphQLEndpoint, "github graphql endpoint") 62 flag.BoolVar(&o.confirm, "confirm", false, "Mutate github if set") 63 flag.StringVar(&o.org, "org", "", "github org") 64 flag.StringVar(&o.repo, "repo", "", "github repo") 65 flag.StringVar(&o.tokenPath, "token-path", "", "Path to github token") 66 flag.BoolVar(&o.trustCollaborators, "trust-collaborators", false, "Also trust PRs from collaborators") 67 flag.Parse() 68 return o 69 } 70 71 func main() { 72 log.SetFlags(log.LstdFlags | log.Lshortfile) 73 o := flagOptions() 74 75 if o.org == "" { 76 log.Fatal("empty --org") 77 } 78 if o.repo == "" { 79 log.Fatal("empty --repo") 80 } 81 if o.tokenPath == "" { 82 log.Fatal("empty --token-path") 83 } 84 85 if err := secret.Add(o.tokenPath); err != nil { 86 log.Fatalf("Error starting secrets agent: %v", err) 87 } 88 89 var err error 90 for _, ep := range o.endpoint.Strings() { 91 _, err = url.ParseRequestURI(ep) 92 if err != nil { 93 log.Fatalf("Invalid --endpoint URL %q: %v.", ep, err) 94 } 95 } 96 97 _, err = url.Parse(o.graphqlEndpoint) 98 if err != nil { 99 log.Fatalf("Invalid --graphql-endpoint URL %q: %v.", o.graphqlEndpoint, err) 100 } 101 102 var c client 103 if o.confirm { 104 c, err = github.NewClient(secret.GetTokenGenerator(o.tokenPath), secret.Censor, o.graphqlEndpoint, o.endpoint.Strings()...) 105 } else { 106 c, err = github.NewDryRunClient(secret.GetTokenGenerator(o.tokenPath), secret.Censor, o.graphqlEndpoint, o.endpoint.Strings()...) 107 } 108 if err != nil { 109 log.Fatalf("failed to construgt GitHub client: %v", err) 110 } 111 112 // get all open PRs 113 prs, err := c.GetPullRequests(o.org, o.repo) 114 if err != nil { 115 log.Fatal(err) 116 } 117 118 // get the list of authors to skip once and use a set for lookups 119 skipAuthors := sets.NewString() 120 121 // skip org members 122 members, err := c.ListOrgMembers(o.org, "all") 123 if err != nil { 124 log.Fatal(err) 125 } 126 for _, member := range members { 127 skipAuthors.Insert(member.Login) 128 } 129 130 // eventually also skip collaborators 131 if o.trustCollaborators { 132 collaborators, err := c.ListCollaborators(o.org, o.repo) 133 if err != nil { 134 log.Fatal(err) 135 } 136 for _, collaborator := range collaborators { 137 skipAuthors.Insert(collaborator.Login) 138 } 139 } 140 141 for _, pr := range prs { 142 // skip PRs from these authors 143 if skipAuthors.Has(pr.User.Login) { 144 continue 145 } 146 // skip PRs with *ok-to-test labels 147 labels, err := c.GetIssueLabels(o.org, o.repo, pr.Number) 148 if err != nil { 149 log.Fatal(err) 150 } 151 if github.HasLabel(okToTest, labels) || github.HasLabel(needsOkToTest, labels) { 152 continue 153 } 154 // only add ok-to-test with --confirm 155 if !o.confirm { 156 log.Println("Use --confirm to add", okToTest, "to", pr.HTMLURL) 157 continue 158 } 159 if err := c.AddLabel(o.org, o.repo, pr.Number, okToTest); err != nil { 160 log.Fatal(err) 161 } 162 } 163 }