github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/lifecycle/close.go (about) 1 /* 2 Copyright 2016 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 lifecycle 18 19 import ( 20 "fmt" 21 "regexp" 22 23 "github.com/sirupsen/logrus" 24 25 "k8s.io/test-infra/prow/github" 26 "k8s.io/test-infra/prow/plugins" 27 ) 28 29 var closeRe = regexp.MustCompile(`(?mi)^/close\s*$`) 30 31 type closeClient interface { 32 IsCollaborator(owner, repo, login string) (bool, error) 33 CreateComment(owner, repo string, number int, comment string) error 34 CloseIssue(owner, repo string, number int) error 35 ClosePR(owner, repo string, number int) error 36 GetIssueLabels(owner, repo string, number int) ([]github.Label, error) 37 } 38 39 func isActive(gc closeClient, org, repo string, number int) (bool, error) { 40 labels, err := gc.GetIssueLabels(org, repo, number) 41 if err != nil { 42 return true, fmt.Errorf("list issue labels error: %v", err) 43 } 44 for _, label := range []string{"lifecycle/stale", "lifecycle/rotten"} { 45 if github.HasLabel(label, labels) { 46 return false, nil 47 } 48 } 49 return true, nil 50 } 51 52 func handleClose(gc closeClient, log *logrus.Entry, e *github.GenericCommentEvent) error { 53 // Only consider open issues and new comments. 54 if e.IssueState != "open" || e.Action != github.GenericCommentActionCreated { 55 return nil 56 } 57 58 if !closeRe.MatchString(e.Body) { 59 return nil 60 } 61 62 org := e.Repo.Owner.Login 63 repo := e.Repo.Name 64 number := e.Number 65 commentAuthor := e.User.Login 66 67 isAuthor := e.IssueAuthor.Login == commentAuthor 68 69 isCollaborator, err := gc.IsCollaborator(org, repo, commentAuthor) 70 if err != nil { 71 log.WithError(err).Errorf("Failed IsCollaborator(%s, %s, %s)", org, repo, commentAuthor) 72 } 73 74 active, err := isActive(gc, org, repo, number) 75 if err != nil { 76 log.Infof("Cannot determine if issue is active: %v", err) 77 active = true // Fail active 78 } 79 80 // Only authors and collaborators are allowed to close active issues. 81 if !isAuthor && !isCollaborator && active { 82 response := "You can't close an active issue/PR unless you authored it or you are a collaborator." 83 log.Infof("Commenting \"%s\".", response) 84 return gc.CreateComment( 85 org, 86 repo, 87 number, 88 plugins.FormatResponseRaw(e.Body, e.HTMLURL, commentAuthor, response), 89 ) 90 } 91 92 // Add a comment after closing the PR or issue 93 // to leave an audit trail of who asked to close it. 94 if e.IsPR { 95 log.Info("Closing PR.") 96 if err := gc.ClosePR(org, repo, number); err != nil { 97 return fmt.Errorf("Error closing PR: %v", err) 98 } 99 response := plugins.FormatResponseRaw(e.Body, e.HTMLURL, commentAuthor, "Closed this PR.") 100 return gc.CreateComment(org, repo, number, response) 101 } 102 103 log.Info("Closing issue.") 104 if err := gc.CloseIssue(org, repo, number); err != nil { 105 return fmt.Errorf("Error closing issue: %v", err) 106 } 107 response := plugins.FormatResponseRaw(e.Body, e.HTMLURL, commentAuthor, "Closing this issue.") 108 return gc.CreateComment(org, repo, number, response) 109 }