github.com/abayer/test-infra@v0.0.5/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 CreateComment(owner, repo string, number int, comment string) error 33 CloseIssue(owner, repo string, number int) error 34 ClosePR(owner, repo string, number int) error 35 IsMember(owner, login string) (bool, error) 36 AssignIssue(owner, repo string, number int, assignees []string) error 37 GetIssueLabels(owner, repo string, number int) ([]github.Label, error) 38 } 39 40 func isActive(gc closeClient, org, repo string, number int) (bool, error) { 41 labels, err := gc.GetIssueLabels(org, repo, number) 42 if err != nil { 43 return true, fmt.Errorf("list issue labels error: %v", err) 44 } 45 for _, label := range []string{"lifecycle/stale", "lifecycle/rotten"} { 46 if github.HasLabel(label, labels) { 47 return false, nil 48 } 49 } 50 return true, nil 51 } 52 53 func handleClose(gc closeClient, log *logrus.Entry, e *github.GenericCommentEvent) error { 54 // Only consider open issues and new comments. 55 if e.IssueState != "open" || e.Action != github.GenericCommentActionCreated { 56 return nil 57 } 58 59 if !closeRe.MatchString(e.Body) { 60 return nil 61 } 62 63 org := e.Repo.Owner.Login 64 repo := e.Repo.Name 65 number := e.Number 66 commentAuthor := e.User.Login 67 68 // Allow assignees and authors to close issues. 69 isAssignee := false 70 for _, assignee := range e.Assignees { 71 if commentAuthor == assignee.Login { 72 isAssignee = true 73 break 74 } 75 } 76 isAuthor := e.IssueAuthor.Login == commentAuthor 77 78 if !isAssignee && !isAuthor { 79 active, err := isActive(gc, org, repo, number) 80 if err != nil { 81 log.Infof("Cannot determine if issue is active: %v", err) 82 active = true // Fail active 83 } 84 85 if active { 86 // Try to assign the issue to the comment author 87 log.Infof("Assign to %s", commentAuthor) 88 if err := gc.AssignIssue(org, repo, number, []string{commentAuthor}); err != nil { 89 msg := "Assigning you to the issue failed." 90 if ok, merr := gc.IsMember(org, commentAuthor); merr == nil && !ok { 91 msg = "Can only assign issues to org members and/or repo collaborators." 92 } else if merr != nil { 93 log.WithError(merr).Errorf("Failed IsMember(%s, %s)", org, commentAuthor) 94 } else { 95 log.WithError(err).Errorf("Failed AssignIssue(%s, %s, %d, %s)", org, repo, number, commentAuthor) 96 } 97 resp := fmt.Sprintf("you can't close an active issue unless you authored it or you are assigned to it, %s.", msg) 98 log.Infof("Commenting \"%s\".", resp) 99 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, commentAuthor, resp)) 100 } 101 } 102 } 103 104 if e.IsPR { 105 log.Info("Closing PR.") 106 return gc.ClosePR(org, repo, number) 107 } 108 109 log.Info("Closing issue.") 110 return gc.CloseIssue(org, repo, number) 111 }