github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/milestonestatus/milestonestatus.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 milestonestatus implements the `/status` command which allows members of the milestone
    18  // maintainers team to specify a `status/*` label to be applied to an Issue or PR.
    19  package milestonestatus
    20  
    21  import (
    22  	"fmt"
    23  	"regexp"
    24  	"strings"
    25  
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"k8s.io/test-infra/prow/github"
    29  	"k8s.io/test-infra/prow/pluginhelp"
    30  	"k8s.io/test-infra/prow/plugins"
    31  )
    32  
    33  const pluginName = "milestonestatus"
    34  
    35  var (
    36  	statusRegex      = regexp.MustCompile(`(?m)^/status\s+(.+)$`)
    37  	mustBeSigLead    = "You must be a member of the [%s/%s](https://github.com/orgs/%s/teams/%s/members) github team to add status labels."
    38  	milestoneTeamMsg = "The milestone maintainers team is the Github team with ID: %d."
    39  	statusMap        = map[string]string{
    40  		"approved-for-milestone": "status/approved-for-milestone",
    41  		"in-progress":            "status/in-progress",
    42  		"in-review":              "status/in-review",
    43  	}
    44  )
    45  
    46  type githubClient interface {
    47  	CreateComment(owner, repo string, number int, comment string) error
    48  	AddLabel(owner, repo string, number int, label string) error
    49  	ListTeamMembers(id int, role string) ([]github.TeamMember, error)
    50  }
    51  
    52  func init() {
    53  	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
    54  }
    55  
    56  func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
    57  	pluginHelp := &pluginhelp.PluginHelp{
    58  		Description: "The milestonestatus plugin allows members of the milestone maintainers Github team to specify the 'status/*' label that should apply to a pull request.",
    59  		Config: func() map[string]string {
    60  			configMap := make(map[string]string)
    61  			for _, repo := range enabledRepos {
    62  				team, exists := config.RepoMilestone[repo]
    63  				if exists {
    64  					configMap[repo] = fmt.Sprintf(milestoneTeamMsg, team)
    65  				}
    66  			}
    67  			configMap[""] = fmt.Sprintf(milestoneTeamMsg, config.RepoMilestone[""])
    68  			return configMap
    69  		}(),
    70  	}
    71  	pluginHelp.AddCommand(pluginhelp.Command{
    72  		Usage:       "/status (approved-for-milestone|in-progress|in-review)",
    73  		Description: "Applies the 'status/' label to a PR.",
    74  		Featured:    false,
    75  		WhoCanUse:   "Members of the milestone maintainers Github team can use the '/status' command. This team is specified in the config by providing the Github team's ID.",
    76  		Examples:    []string{"/status approved-for-milestone", "/status in-progress", "/status in-review"},
    77  	})
    78  	return pluginHelp, nil
    79  }
    80  
    81  func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
    82  	return handle(pc.GitHubClient, pc.Logger, &e, pc.PluginConfig.RepoMilestone)
    83  }
    84  
    85  func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, repoMilestone map[string]plugins.Milestone) error {
    86  	if e.Action != github.GenericCommentActionCreated {
    87  		return nil
    88  	}
    89  
    90  	statusMatches := statusRegex.FindAllStringSubmatch(e.Body, -1)
    91  	if len(statusMatches) == 0 {
    92  		return nil
    93  	}
    94  
    95  	org := e.Repo.Owner.Login
    96  	repo := e.Repo.Name
    97  
    98  	milestone, exists := repoMilestone[fmt.Sprintf("%s/%s", org, repo)]
    99  	if !exists {
   100  		// fallback default
   101  		milestone = repoMilestone[""]
   102  	}
   103  
   104  	milestoneMaintainers, err := gc.ListTeamMembers(milestone.MaintainersID, github.RoleAll)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	found := false
   109  	for _, person := range milestoneMaintainers {
   110  		login := strings.ToLower(e.User.Login)
   111  		if strings.ToLower(person.Login) == login {
   112  			found = true
   113  			break
   114  		}
   115  	}
   116  	if !found {
   117  		// not in the milestone maintainers team
   118  		msg := fmt.Sprintf(mustBeSigLead, org, milestone.MaintainersTeam, org, milestone.MaintainersTeam)
   119  		return gc.CreateComment(org, repo, e.Number, msg)
   120  	}
   121  
   122  	for _, statusMatch := range statusMatches {
   123  		sLabel, validStatus := statusMap[strings.TrimSpace(statusMatch[1])]
   124  		if !validStatus {
   125  			continue
   126  		}
   127  		if err := gc.AddLabel(org, repo, e.Number, sLabel); err != nil {
   128  			log.WithError(err).Errorf("Error adding the label %q to %s/%s#%d.", sLabel, org, repo, e.Number)
   129  		}
   130  	}
   131  	return nil
   132  }