-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Eddie Zaneski <eddiezane@gmail.com>
- Loading branch information
Showing
4 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package transferissue | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
|
||
"github.com/sirupsen/logrus" | ||
|
||
"k8s.io/test-infra/prow/config" | ||
"k8s.io/test-infra/prow/github" | ||
"k8s.io/test-infra/prow/pluginhelp" | ||
"k8s.io/test-infra/prow/plugins" | ||
) | ||
|
||
const pluginName = "transfer-issue" | ||
|
||
var ( | ||
transferRe = regexp.MustCompile(`(?mi)^/transfer(?:-issue)?\s*(.*)$`) | ||
) | ||
|
||
type githubClient interface { | ||
GetRepo(org, name string) (github.FullRepo, error) | ||
CreateComment(org, repo string, number int, comment string) error | ||
IsCollaborator(org, repo, user string) (bool, error) | ||
TransferIssue(org, dstRepoNodeID string, issueNodeID string) (*github.TransferIssueMutation, error) | ||
} | ||
|
||
func init() { | ||
plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider) | ||
} | ||
|
||
func helpProvider(_ *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { | ||
pluginHelp := &pluginhelp.PluginHelp{ | ||
Description: "The transfer-issue plugin transfers a GitHub issue from one repo to another in the same organization.", | ||
} | ||
pluginHelp.AddCommand(pluginhelp.Command{ | ||
Usage: "/transfer[-issue] <destination repo in same org>", | ||
Description: "Transfers an issue to a different repo in the same org.", | ||
Featured: true, | ||
WhoCanUse: "Org members.", | ||
Examples: []string{"/transfer-issue kubectl", "/transfer test-infra"}, | ||
}) | ||
return pluginHelp, nil | ||
} | ||
|
||
func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error { | ||
return handleTransfer(pc.GitHubClient, pc.Logger, e) | ||
} | ||
|
||
func handleTransfer(gc githubClient, log *logrus.Entry, e github.GenericCommentEvent) error { | ||
org := e.Repo.Owner.Login | ||
srcRepoName := e.Repo.Name | ||
srcRepoPair := org + "/" + srcRepoName | ||
user := e.User.Login | ||
|
||
if e.IsPR || e.Action != github.GenericCommentActionCreated { | ||
return nil | ||
} | ||
matches := transferRe.FindAllStringSubmatch(e.Body, -1) | ||
if len(matches) == 0 { | ||
return nil | ||
} | ||
if len(matches) != 1 || len(matches[0]) != 2 { | ||
log.Warnf("invalid usage: %v", matches) | ||
return gc.CreateComment( | ||
org, srcRepoName, e.Number, | ||
plugins.FormatResponseRaw(e.Body, e.HTMLURL, user, "/transfer-issue must only be used once and with a single destination repo."), | ||
) | ||
} | ||
|
||
dstRepoName := matches[0][1] | ||
dstRepoPair := org + "/" + dstRepoName | ||
|
||
dstRepo, err := gc.GetRepo(org, dstRepoName) | ||
if err != nil { | ||
log.WithError(err).Errorf("could not fetch destination repo: %s", dstRepoPair) | ||
// TODO: Might want to add another GetRepo type call that checks if a repo exists vs a bad request | ||
return gc.CreateComment( | ||
org, srcRepoName, e.Number, | ||
plugins.FormatResponseRaw(e.Body, e.HTMLURL, user, fmt.Sprintf("Something went wrong or the destination repo %s does not exist.", dstRepoPair)), | ||
) | ||
} | ||
|
||
isCollaboratorSrc, err := gc.IsCollaborator(org, srcRepoName, user) | ||
if err != nil { | ||
log.WithError(err).Errorf("could not fetch if user: %s is collaborator of source repo: %s", user, srcRepoPair) | ||
return err | ||
} | ||
isCollaboratorDst, err := gc.IsCollaborator(org, dstRepoName, user) | ||
if err != nil { | ||
log.WithError(err).Errorf("could not fetch if user: %s is collaborator of destination repo: %s", user, dstRepoPair) | ||
return err | ||
} | ||
if !(isCollaboratorSrc && isCollaboratorDst) { | ||
log.Warnf("user %s is not a collaborator of %s and/or %s", user, srcRepoPair, dstRepoPair) | ||
return gc.CreateComment( | ||
org, srcRepoName, e.Number, | ||
plugins.FormatResponseRaw(e.Body, e.HTMLURL, user, "User must be a collaborator of source and destination repos."), | ||
) | ||
} | ||
|
||
m, err := gc.TransferIssue(org, dstRepo.NodeID, e.NodeID) | ||
if err != nil { | ||
log.WithError(err).Errorf("could not transfer issue: %d from: %s to: %s", e.Number, srcRepoPair, dstRepoPair) | ||
return err | ||
} | ||
log.Infof("%s transferred issue %s/%d to %v", user, srcRepoPair, e.Number, m.TransferIssue.Issue.URL) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package transferissue | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/sirupsen/logrus" | ||
|
||
"k8s.io/test-infra/prow/github" | ||
"k8s.io/test-infra/prow/github/fakegithub" | ||
) | ||
|
||
const issuerNum = 1 | ||
|
||
func Test_handleTransfer(t *testing.T) { | ||
ts := []struct { | ||
name string | ||
event github.GenericCommentEvent | ||
expectError bool | ||
errorMessage string | ||
comment string | ||
fcFunc func(client *fakegithub.FakeClient) | ||
}{ | ||
{ | ||
name: "is a pr", | ||
event: github.GenericCommentEvent{IsPR: true}, | ||
}, | ||
{ | ||
name: "is not comment added", | ||
event: github.GenericCommentEvent{Action: github.GenericCommentActionDeleted}, | ||
}, | ||
{ | ||
name: "multiple matches", | ||
event: github.GenericCommentEvent{ | ||
Action: github.GenericCommentActionCreated, | ||
Body: `/transfer-issue kubectl | ||
/transfer test-infra`, | ||
HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum), | ||
Number: issuerNum, | ||
Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, | ||
User: github.User{Login: "user"}, | ||
}, | ||
comment: "single destination", | ||
}, | ||
{ | ||
name: "no destination", | ||
event: github.GenericCommentEvent{ | ||
Action: github.GenericCommentActionCreated, | ||
Body: "/transfer", | ||
HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum), | ||
Number: issuerNum, | ||
Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, | ||
User: github.User{Login: "user"}, | ||
}, | ||
comment: "single destination", | ||
}, | ||
{ | ||
name: "dest repo does not exist", | ||
event: github.GenericCommentEvent{ | ||
Action: github.GenericCommentActionCreated, | ||
Body: "/transfer-issue fake", | ||
HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum), | ||
Number: issuerNum, | ||
Repo: github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"}, | ||
User: github.User{Login: "user"}, | ||
}, | ||
comment: "does not exist", | ||
fcFunc: func(fc *fakegithub.FakeClient) { | ||
fc.GetRepoError = errors.New("stub") | ||
}, | ||
}, | ||
{ | ||
name: "not collaborator", | ||
event: github.GenericCommentEvent{ | ||
Action: github.GenericCommentActionCreated, | ||
Body: "/transfer-issue test-infra", | ||
HTMLURL: fmt.Sprintf("https://github.com/kubernetes/fake/issues/%d", issuerNum), | ||
Number: issuerNum, | ||
Repo: github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"}, | ||
User: github.User{Login: "user"}, | ||
}, | ||
comment: "must be a collaborator", | ||
fcFunc: func(fc *fakegithub.FakeClient) { | ||
fc.Collaborators = []string{} | ||
}, | ||
}, | ||
{ | ||
name: "happy path", | ||
event: github.GenericCommentEvent{ | ||
Action: github.GenericCommentActionCreated, | ||
Body: "/transfer-issue test-infra", | ||
Number: issuerNum, | ||
Repo: github.Repo{Owner: github.User{Login: "kubernetes"}, Name: "kubectl"}, | ||
User: github.User{Login: "user"}, | ||
}, | ||
fcFunc: func(fc *fakegithub.FakeClient) { | ||
fc.Collaborators = []string{"user"} | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range ts { | ||
t.Run(tc.name, func(t *testing.T) { | ||
fc := fakegithub.NewFakeClient() | ||
if tc.fcFunc != nil { | ||
tc.fcFunc(fc) | ||
} | ||
log := logrus.WithField("plugin", pluginName) | ||
err := handleTransfer(fc, log, tc.event) | ||
if err != nil { | ||
if !tc.expectError { | ||
t.Errorf("unexpected error: %v", err) | ||
return | ||
} | ||
if m := err.Error(); !strings.Contains(m, tc.errorMessage) { | ||
t.Errorf("expected error to contain: %s got: %v", tc.errorMessage, m) | ||
return | ||
} | ||
} | ||
if err == nil && tc.expectError { | ||
t.Error("expected error but did not produce") | ||
return | ||
} | ||
if len(tc.comment) != 0 { | ||
if c, ok := fc.IssueComments[tc.event.Number]; ok { | ||
if !strings.Contains(c[0].Body, tc.comment) { | ||
t.Errorf("expected comment to contain: %s got: %s", tc.comment, c[0].Body) | ||
} | ||
} | ||
} | ||
if len(tc.comment) == 0 && len(fc.IssueComments[issuerNum]) != 0 { | ||
t.Errorf("unexpected comment: %v", fc.IssueComments[issuerNum]) | ||
} | ||
}) | ||
} | ||
} |