github.com/abayer/test-infra@v0.0.5/mungegithub/mungers/path_label.go (about)

     1  /*
     2  Copyright 2015 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 mungers
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"os"
    23  	"regexp"
    24  	"strings"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/test-infra/mungegithub/features"
    28  	"k8s.io/test-infra/mungegithub/github"
    29  	"k8s.io/test-infra/mungegithub/options"
    30  
    31  	"github.com/golang/glog"
    32  )
    33  
    34  var (
    35  	_ = fmt.Print
    36  )
    37  
    38  const (
    39  	jenkinsBotName = "k8s-bot"
    40  )
    41  
    42  type labelMap struct {
    43  	regexp *regexp.Regexp
    44  	label  string
    45  }
    46  
    47  // PathLabelMunger will add labels to PRs based on what files it modified.
    48  // The mapping of files to labels if provided in a file in --path-label-config
    49  type PathLabelMunger struct {
    50  	pathLabelFile string
    51  	labelMap      []labelMap
    52  	allLabels     sets.String
    53  }
    54  
    55  func init() {
    56  	RegisterMungerOrDie(&PathLabelMunger{})
    57  }
    58  
    59  // Name is the name usable in --pr-mungers
    60  func (p *PathLabelMunger) Name() string { return "path-label" }
    61  
    62  // RequiredFeatures is a slice of 'features' that must be provided
    63  func (p *PathLabelMunger) RequiredFeatures() []string { return []string{} }
    64  
    65  // Initialize will initialize the munger
    66  func (p *PathLabelMunger) Initialize(config *github.Config, features *features.Features) error {
    67  	allLabels := sets.NewString()
    68  	out := []labelMap{}
    69  	file := p.pathLabelFile
    70  	if len(file) == 0 {
    71  		glog.Infof("No 'path-label-config' option supplied, applying no labels.")
    72  		return nil
    73  	}
    74  	fp, err := os.Open(file)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	defer fp.Close()
    79  	scanner := bufio.NewScanner(fp)
    80  	for scanner.Scan() {
    81  		line := scanner.Text()
    82  		if strings.HasPrefix(line, "#") {
    83  			continue
    84  		}
    85  		if line == "" {
    86  			continue
    87  		}
    88  		fields := strings.Fields(line)
    89  		if len(fields) != 2 {
    90  			glog.Errorf("Invalid line in path based label munger config %s: %q", file, line)
    91  			continue
    92  		}
    93  		r, err := regexp.Compile(fields[0])
    94  		if err != nil {
    95  			glog.Errorf("Invalid regexp in label munger config %s: %q", file, fields[0])
    96  			continue
    97  		}
    98  
    99  		label := fields[1]
   100  		lm := labelMap{
   101  			regexp: r,
   102  			label:  label,
   103  		}
   104  		out = append(out, lm)
   105  		allLabels.Insert(label)
   106  	}
   107  	p.allLabels = allLabels
   108  	p.labelMap = out
   109  	return scanner.Err()
   110  }
   111  
   112  // EachLoop is called at the start of every munge loop
   113  func (p *PathLabelMunger) EachLoop() error { return nil }
   114  
   115  // RegisterOptions registers options for this munger; returns any that require a restart when changed.
   116  func (p *PathLabelMunger) RegisterOptions(opts *options.Options) sets.String {
   117  	opts.RegisterString(&p.pathLabelFile, "path-label-config", "", "file containing the pathname to label mappings")
   118  	opts.RegisterUpdateCallback(func(changed sets.String) error {
   119  		if changed.Has("path-label-config") {
   120  			return p.Initialize(nil, nil) // Initialize doesn't use config or features.
   121  		}
   122  		return nil
   123  	})
   124  	return nil
   125  }
   126  
   127  // Munge is the workhorse the will actually make updates to the PR
   128  func (p *PathLabelMunger) Munge(obj *github.MungeObject) {
   129  	if !obj.IsPR() {
   130  		return
   131  	}
   132  
   133  	files, ok := obj.ListFiles()
   134  	if !ok {
   135  		return
   136  	}
   137  
   138  	needsLabels := sets.NewString()
   139  	for _, f := range files {
   140  		for _, lm := range p.labelMap {
   141  			if lm.regexp.MatchString(*f.Filename) {
   142  				needsLabels.Insert(lm.label)
   143  			}
   144  		}
   145  	}
   146  
   147  	SyncLabels(p.allLabels, needsLabels, obj)
   148  }
   149  
   150  // SyncLabels properly syncs a set of labels. 'allLabels' must be a superset of
   151  // 'desiredLabels'; to disable removing labels, set them to be the same set.
   152  // Multiple mungers must somehow coordinate on which labels the bot ought to
   153  // apply, otherwise the bot will fight with itself.
   154  //
   155  // TODO: fix error handling.
   156  func SyncLabels(allLabels, desiredLabels sets.String, obj *github.MungeObject) error {
   157  	hasLabels := obj.LabelSet().Intersection(allLabels)
   158  
   159  	missingLabels := desiredLabels.Difference(hasLabels)
   160  	if missingLabels.Len() != 0 {
   161  		obj.AddLabels(missingLabels.List())
   162  	}
   163  
   164  	extraLabels := hasLabels.Difference(desiredLabels)
   165  	for _, label := range extraLabels.List() {
   166  		creator, ok := obj.LabelCreator(label)
   167  		if ok && obj.IsRobot(creator) {
   168  			obj.RemoveLabel(label)
   169  		}
   170  	}
   171  	return nil
   172  }