github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/milestoneapplier/milestoneapplier.go (about)

     1  /*
     2  Copyright 2019 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 milestoneapplier implements the plugin to automatically apply
    18  // the configured milestone after a PR is merged.
    19  package milestoneapplier
    20  
    21  import (
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/sirupsen/logrus"
    26  
    27  	"sigs.k8s.io/prow/pkg/config"
    28  	"sigs.k8s.io/prow/pkg/github"
    29  	"sigs.k8s.io/prow/pkg/pluginhelp"
    30  	"sigs.k8s.io/prow/pkg/plugins"
    31  	"sigs.k8s.io/prow/pkg/plugins/milestone"
    32  )
    33  
    34  const pluginName = "milestoneapplier"
    35  
    36  type githubClient interface {
    37  	SetMilestone(org, repo string, issueNum, milestoneNum int) error
    38  	ListMilestones(org, repo string) ([]github.Milestone, error)
    39  }
    40  
    41  func init() {
    42  	plugins.RegisterPullRequestHandler(pluginName, handlePullRequest, helpProvider)
    43  }
    44  
    45  func helpProvider(config *plugins.Configuration, enabledRepos []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
    46  	configInfo := map[string]string{}
    47  	for _, repo := range enabledRepos {
    48  		var branchesToMilestone []string
    49  		for branch, milestone := range config.MilestoneApplier[repo.String()] {
    50  			branchesToMilestone = append(branchesToMilestone, fmt.Sprintf("- `%s`: `%s`", branch, milestone))
    51  		}
    52  		configInfo[repo.String()] = fmt.Sprintf("The configured branches and milestones for this repo are:\n%s", strings.Join(branchesToMilestone, "\n"))
    53  	}
    54  
    55  	// The {WhoCanUse, Usage, Examples} fields are omitted because this plugin is not triggered with commands.
    56  	yamlSnippet, err := plugins.CommentMap.GenYaml(&plugins.Configuration{
    57  		MilestoneApplier: map[string]plugins.BranchToMilestone{
    58  			"kubernetes/kubernetes": {
    59  				"release-1.19": "v1.19",
    60  				"release-1.18": "v1.18",
    61  			},
    62  		},
    63  	})
    64  	if err != nil {
    65  		logrus.WithError(err).Warnf("cannot generate comments for %s plugin", pluginName)
    66  	}
    67  	return &pluginhelp.PluginHelp{
    68  		Description: "The milestoneapplier plugin automatically applies the configured milestone for the base branch after a PR is merged. If a PR targets a non-default branch, it also adds the milestone when the PR is opened.",
    69  		Config:      configInfo,
    70  		Snippet:     yamlSnippet,
    71  	}, nil
    72  }
    73  
    74  func handlePullRequest(pc plugins.Agent, pre github.PullRequestEvent) error {
    75  	org := pre.PullRequest.Base.Repo.Owner.Login
    76  	repo := pre.PullRequest.Base.Repo.Name
    77  	baseBranch := pre.PullRequest.Base.Ref
    78  
    79  	// if there are no branch to milestone mappings for this repo, return early
    80  	branchToMilestone, ok := pc.PluginConfig.MilestoneApplier[fmt.Sprintf("%s/%s", org, repo)]
    81  	if !ok {
    82  		return nil
    83  	}
    84  	// if the repo does not define milestones for this branch, return early
    85  	milestone, ok := branchToMilestone[baseBranch]
    86  	if !ok {
    87  		return nil
    88  	}
    89  
    90  	return handle(pc.GitHubClient, pc.Logger, milestone, pre)
    91  }
    92  
    93  func handle(gc githubClient, log *logrus.Entry, configuredMilestone string, pre github.PullRequestEvent) error {
    94  	pr := pre.PullRequest
    95  
    96  	// if the current milestone is equal to the configured milestone, return early
    97  	if pr.Milestone != nil && pr.Milestone.Title == configuredMilestone {
    98  		return nil
    99  	}
   100  
   101  	// if a PR targets a non-default branch, apply milestone when opened and on merge
   102  	// if a PR targets the default branch, apply the milestone only on merge
   103  	merged := pre.Action == github.PullRequestActionClosed && pr.Merged
   104  	if pr.Base.Repo.DefaultBranch != pr.Base.Ref {
   105  		if !merged && pre.Action != github.PullRequestActionOpened {
   106  			return nil
   107  		}
   108  	} else if !merged {
   109  		return nil
   110  	}
   111  
   112  	number := pre.Number
   113  	org := pr.Base.Repo.Owner.Login
   114  	repo := pr.Base.Repo.Name
   115  
   116  	milestones, err := gc.ListMilestones(org, repo)
   117  	if err != nil {
   118  		log.WithError(err).Errorf("Error listing the milestones in the %s/%s repo", org, repo)
   119  		return err
   120  	}
   121  
   122  	milestoneMap := milestone.BuildMilestoneMap(milestones)
   123  	configuredMilestoneNumber, ok := milestoneMap[configuredMilestone]
   124  	if !ok {
   125  		return fmt.Errorf("The configured milestone %s for %s branch does not exist in the %s/%s repo", configuredMilestone, pr.Base.Ref, org, repo)
   126  	}
   127  
   128  	if err := gc.SetMilestone(org, repo, number, configuredMilestoneNumber); err != nil {
   129  		log.WithError(err).Errorf("Error adding the milestone %s to %s/%s#%d.", configuredMilestone, org, repo, number)
   130  		return err
   131  	}
   132  
   133  	return nil
   134  }