sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/commentpruner/commentpruner.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 // Package commentpruner facilitates efficiently deleting bot comments as a reaction to webhook events. 18 package commentpruner 19 20 import ( 21 "sync" 22 23 "github.com/sirupsen/logrus" 24 25 "sigs.k8s.io/prow/pkg/github" 26 ) 27 28 type githubClient interface { 29 BotUserChecker() (func(candidate string) bool, error) 30 ListIssueComments(org, repo string, number int) ([]github.IssueComment, error) 31 DeleteComment(org, repo string, id int) error 32 } 33 34 // EventClient is a struct that provides bot comment deletion for an event related to an issue. 35 // A single client instance should be created for each event and shared by all consumers of the event. 36 // The client fetches the comments only once and filters that list repeatedly to find bot comments to 37 // delete. This avoids using lots of API tokens when fetching comments for each handler that wants 38 // to delete comments. (An HTTP cache only partially helps with this because deletions modify the 39 // list of comments so the next call requires GH to send the resource again.) 40 type EventClient struct { 41 org string 42 repo string 43 number int 44 45 ghc githubClient 46 log *logrus.Entry 47 48 once sync.Once 49 lock sync.Mutex 50 comments []github.IssueComment 51 } 52 53 // NewEventClient creates an EventClient struct. This should be used once per webhook event. 54 func NewEventClient(ghc githubClient, log *logrus.Entry, org, repo string, number int) *EventClient { 55 return &EventClient{ 56 org: org, 57 repo: repo, 58 number: number, 59 60 ghc: ghc, 61 log: log, 62 } 63 } 64 65 // PruneComments fetches issue comments if they have not yet been fetched for this webhook event 66 // and then deletes any bot comments indicated by the func 'shouldPrune'. 67 func (c *EventClient) PruneComments(shouldPrune func(github.IssueComment) bool) { 68 c.once.Do(func() { 69 botUserChecker, err := c.ghc.BotUserChecker() 70 if err != nil { 71 c.log.WithError(err).Error("failed to get the bot's name. Pruning will consider all comments.") 72 } 73 comments, err := c.ghc.ListIssueComments(c.org, c.repo, c.number) 74 if err != nil { 75 c.log.WithError(err).Errorf("failed to list comments for %s/%s#%d", c.org, c.repo, c.number) 76 } 77 if botUserChecker != nil { 78 for _, comment := range comments { 79 if botUserChecker(comment.User.Login) { 80 c.comments = append(c.comments, comment) 81 } 82 } 83 } 84 }) 85 86 c.lock.Lock() 87 defer c.lock.Unlock() 88 89 var remaining []github.IssueComment 90 for _, comment := range c.comments { 91 removed := false 92 if shouldPrune(comment) { 93 if err := c.ghc.DeleteComment(c.org, c.repo, comment.ID); err != nil { 94 c.log.WithError(err).Errorf("failed to delete stale comment with ID '%d'", comment.ID) 95 } else { 96 removed = true 97 } 98 } 99 if !removed { 100 remaining = append(remaining, comment) 101 } 102 } 103 c.comments = remaining 104 }