github.com/abayer/test-infra@v0.0.5/mungegithub/mungegithub.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 main
    18  
    19  import (
    20  	"flag"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"time"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	utilflag "k8s.io/apiserver/pkg/util/flag"
    28  	"k8s.io/test-infra/mungegithub/features"
    29  	github_util "k8s.io/test-infra/mungegithub/github"
    30  	"k8s.io/test-infra/mungegithub/mungeopts"
    31  	"k8s.io/test-infra/mungegithub/mungers"
    32  	"k8s.io/test-infra/mungegithub/options"
    33  	"k8s.io/test-infra/mungegithub/reports"
    34  
    35  	"github.com/golang/glog"
    36  	"github.com/spf13/cobra"
    37  )
    38  
    39  type mungeConfig struct {
    40  	github_util.Config
    41  	*options.Options
    42  	features.Features
    43  
    44  	path string
    45  
    46  	once           bool
    47  	prMungers      []string
    48  	issueReports   []string
    49  	period         time.Duration
    50  	webhookKeyFile string
    51  }
    52  
    53  func registerOptions(config *mungeConfig) {
    54  	config.RegisterBool(&config.once, "once", false, "If true, run one loop and exit")
    55  	config.RegisterStringSlice(&config.prMungers, "pr-mungers", []string{}, "A list of pull request mungers to run")
    56  	config.RegisterStringSlice(&config.issueReports, "issue-reports", []string{}, "A list of issue reports to run. If set, will run the reports and exit.")
    57  	config.RegisterDuration(&config.period, "period", 10*time.Minute, "The period for running mungers")
    58  	config.RegisterString(&config.webhookKeyFile, "github-key-file", "", "Github secret key for webhooks")
    59  
    60  	// options that necessitate a full restart when they change.
    61  	immutables := sets.NewString("once", "pr-mungers", "issue-reports", "github-key-file")
    62  	// Register config update callback. This goes before registration of other sources to ensure:
    63  	// 1) If an immutable option is changed, we fail before calling other update callbacks.
    64  	// 2) The updated config is printed before anything else when a new config is found.
    65  	config.RegisterUpdateCallback(func(changed sets.String) error {
    66  		if common := immutables.Intersection(changed); len(common) > 0 {
    67  			return fmt.Errorf("option key(s) %q was updated necessitating a restart", common.List())
    68  		}
    69  
    70  		glog.Infof(
    71  			"ConfigMap '%s' was updated.\nOptions changed: %q\n%s",
    72  			config.path,
    73  			changed.List(),
    74  			config.CurrentValues(),
    75  		)
    76  		return nil
    77  	})
    78  
    79  	// Register options from other sources.
    80  	// github.Config
    81  	immutables = immutables.Union(config.Config.RegisterOptions(config.Options))
    82  	// Features (per feature opts)
    83  	immutables = immutables.Union(config.Features.RegisterOptions(config.Options))
    84  	// Reports (per report opts)
    85  	immutables = immutables.Union(reports.RegisterOptions(config.Options))
    86  	// MungeOpts (opts shared by mungers or features)
    87  	immutables = immutables.Union(mungeopts.RegisterOptions(config.Options))
    88  	// Mungers (per munger opts)
    89  	immutables = immutables.Union(mungers.RegisterOptions(config.Options))
    90  }
    91  
    92  func doMungers(config *mungeConfig) error {
    93  	for {
    94  		nextRunStartTime := time.Now().Add(config.period)
    95  		glog.Infof("Running mungers")
    96  		config.NextExpectedUpdate(nextRunStartTime)
    97  
    98  		config.Features.EachLoop()
    99  		mungers.EachLoop()
   100  
   101  		if err := config.ForEachIssueDo(mungers.MungeIssue); err != nil {
   102  			glog.Errorf("Error munging PRs: %v", err)
   103  		}
   104  
   105  		config.ResetAPICount()
   106  		if config.once {
   107  			break
   108  		}
   109  		if nextRunStartTime.After(time.Now()) {
   110  			sleepDuration := nextRunStartTime.Sub(time.Now())
   111  			glog.Infof("Sleeping for %v\n", sleepDuration)
   112  			time.Sleep(sleepDuration)
   113  		} else {
   114  			glog.Infof("Not sleeping as we took more than %v to complete one loop\n", config.period)
   115  		}
   116  
   117  		if config.path == "" {
   118  			continue
   119  		}
   120  		if _, err := config.Load(config.path); err != nil {
   121  			if _, ok := err.(*options.UpdateCallbackError); ok {
   122  				return err // Fatal
   123  			}
   124  			// Non-fatal since the config has previously been loaded successfully.
   125  			glog.Errorf("Error reloading config (ignored): %v", err)
   126  		}
   127  	}
   128  	return nil
   129  }
   130  
   131  func main() {
   132  	config := &mungeConfig{}
   133  	root := &cobra.Command{
   134  		Use:   filepath.Base(os.Args[0]),
   135  		Short: "A program to add labels, check tests, and generally mess with outstanding PRs",
   136  		RunE: func(_ *cobra.Command, _ []string) error {
   137  			optFlagsSpecified := config.Options.FlagsSpecified()
   138  			if config.path != "" && len(optFlagsSpecified) > 0 {
   139  				glog.Fatalf("Error: --config-path flag cannot be used with option flags. Option flag(s) %v were specified.", optFlagsSpecified.List())
   140  			}
   141  			if config.path != "" {
   142  				glog.Infof("Loading config from file '%s'.\n", config.path)
   143  				if _, err := config.Load(config.path); err != nil {
   144  					glog.Fatalf("Error loading options: %v", err)
   145  				}
   146  			} else {
   147  				glog.Info("Loading config from flags.\n")
   148  				config.PopulateFromFlags()
   149  			}
   150  
   151  			glog.Info(config.CurrentValues())
   152  			if err := config.PreExecute(); err != nil {
   153  				return err
   154  			}
   155  			if len(config.issueReports) > 0 {
   156  				return reports.RunReports(&config.Config, config.issueReports...)
   157  			}
   158  			if len(config.prMungers) == 0 {
   159  				glog.Fatalf("must include at least one --pr-mungers")
   160  			}
   161  			if err := mungers.RegisterMungers(config.prMungers); err != nil {
   162  				glog.Fatalf("unable to find requested mungers: %v", err)
   163  			}
   164  			// Include the server feature if webhooks are enabled.
   165  			requestedFeatures := sets.NewString(mungers.RequestedFeatures()...)
   166  			if config.webhookKeyFile != "" {
   167  				requestedFeatures = requestedFeatures.Union(sets.NewString(features.ServerFeatureName))
   168  			}
   169  			if err := config.Features.Initialize(&config.Config, requestedFeatures.List()); err != nil {
   170  				return err
   171  			}
   172  			if err := mungers.InitializeMungers(&config.Config, &config.Features); err != nil {
   173  				glog.Fatalf("unable to initialize mungers: %v", err)
   174  			}
   175  			if config.webhookKeyFile != "" {
   176  				config.HookHandler = github_util.NewWebHookAndListen(config.webhookKeyFile, config.Features.Server)
   177  			}
   178  			return doMungers(config)
   179  		},
   180  	}
   181  
   182  	config.Options = options.New()
   183  	registerOptions(config)
   184  	config.Options.ToFlags()
   185  
   186  	root.SetGlobalNormalizationFunc(utilflag.WordSepNormalizeFunc)
   187  
   188  	// Command line flags.
   189  	flag.BoolVar(&config.DryRun, "dry-run", true, "If true, don't actually merge anything")
   190  	flag.StringVar(&config.path, "config-path", "", "File path to yaml config map containing the values to use for options.")
   191  	root.PersistentFlags().AddGoFlagSet(flag.CommandLine)
   192  
   193  	if err := root.Execute(); err != nil {
   194  		glog.Fatalf("%v\n", err)
   195  	}
   196  }