github.com/jenkins-x/test-infra@v0.0.7/prow/config/config.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 config knows how to read and parse config.yaml.
    18  // It also implements an agent to read the secrets.
    19  package config
    20  
    21  import (
    22  	"bytes"
    23  	"errors"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net/url"
    27  	"os"
    28  	"path/filepath"
    29  	"regexp"
    30  	"strings"
    31  	"text/template"
    32  	"time"
    33  
    34  	"github.com/sirupsen/logrus"
    35  	"gopkg.in/robfig/cron.v2"
    36  	"k8s.io/api/core/v1"
    37  	"k8s.io/apimachinery/pkg/labels"
    38  	"k8s.io/apimachinery/pkg/util/sets"
    39  	"k8s.io/apimachinery/pkg/util/validation"
    40  	"sigs.k8s.io/yaml"
    41  
    42  	prowjobv1 "k8s.io/test-infra/prow/apis/prowjobs/v1"
    43  	"k8s.io/test-infra/prow/config/org"
    44  	"k8s.io/test-infra/prow/github"
    45  	"k8s.io/test-infra/prow/kube"
    46  	"k8s.io/test-infra/prow/pod-utils/decorate"
    47  	"k8s.io/test-infra/prow/pod-utils/downwardapi"
    48  )
    49  
    50  // Config is a read-only snapshot of the config.
    51  type Config struct {
    52  	JobConfig
    53  	ProwConfig
    54  }
    55  
    56  // JobConfig is config for all prow jobs
    57  type JobConfig struct {
    58  	// Presets apply to all job types.
    59  	Presets []Preset `json:"presets,omitempty"`
    60  	// Full repo name (such as "kubernetes/kubernetes") -> list of jobs.
    61  	Presubmits  map[string][]Presubmit  `json:"presubmits,omitempty"`
    62  	Postsubmits map[string][]Postsubmit `json:"postsubmits,omitempty"`
    63  
    64  	// Periodics are not associated with any repo.
    65  	Periodics []Periodic `json:"periodics,omitempty"`
    66  }
    67  
    68  // ProwConfig is config for all prow controllers
    69  type ProwConfig struct {
    70  	Tide             Tide                  `json:"tide,omitempty"`
    71  	Plank            Plank                 `json:"plank,omitempty"`
    72  	Sinker           Sinker                `json:"sinker,omitempty"`
    73  	Deck             Deck                  `json:"deck,omitempty"`
    74  	BranchProtection BranchProtection      `json:"branch-protection,omitempty"`
    75  	Orgs             map[string]org.Config `json:"orgs,omitempty"`
    76  	Gerrit           Gerrit                `json:"gerrit,omitempty"`
    77  	Build            Build                 `json:"build,omitempty"`
    78  
    79  	// TODO: Move this out of the main config.
    80  	JenkinsOperators []JenkinsOperator `json:"jenkins_operators,omitempty"`
    81  
    82  	// ProwJobNamespace is the namespace in the cluster that prow
    83  	// components will use for looking up ProwJobs. The namespace
    84  	// needs to exist and will not be created by prow.
    85  	// Defaults to "default".
    86  	ProwJobNamespace string `json:"prowjob_namespace,omitempty"`
    87  	// PodNamespace is the namespace in the cluster that prow
    88  	// components will use for looking up Pods owned by ProwJobs.
    89  	// The namespace needs to exist and will not be created by prow.
    90  	// Defaults to "default".
    91  	PodNamespace string `json:"pod_namespace,omitempty"`
    92  
    93  	// LogLevel enables dynamically updating the log level of the
    94  	// standard logger that is used by all prow components.
    95  	//
    96  	// Valid values:
    97  	//
    98  	// "debug", "info", "warn", "warning", "error", "fatal", "panic"
    99  	//
   100  	// Defaults to "info".
   101  	LogLevel string `json:"log_level,omitempty"`
   102  
   103  	// PushGateway is a prometheus push gateway.
   104  	PushGateway PushGateway `json:"push_gateway,omitempty"`
   105  
   106  	// OwnersDirBlacklist is used to configure which directories to ignore when
   107  	// searching for OWNERS{,_ALIAS} files in a repo.
   108  	OwnersDirBlacklist OwnersDirBlacklist `json:"owners_dir_blacklist,omitempty"`
   109  
   110  	// Pub/Sub Subscriptions that we want to listen to
   111  	PubSubSubscriptions PubsubSubscriptions `json:"pubsub_subscriptions,omitempty"`
   112  }
   113  
   114  // OwnersDirBlacklist is used to configure which directories to ignore when
   115  // searching for OWNERS{,_ALIAS} files in a repo.
   116  type OwnersDirBlacklist struct {
   117  	// Repos configures a directory blacklist per repo (or org)
   118  	Repos map[string][]string `json:"repos"`
   119  	// Default configures a default blacklist for repos (or orgs) not
   120  	// specifically configured
   121  	Default []string `json:"default"`
   122  }
   123  
   124  // PushGateway is a prometheus push gateway.
   125  type PushGateway struct {
   126  	// Endpoint is the location of the prometheus pushgateway
   127  	// where prow will push metrics to.
   128  	Endpoint string `json:"endpoint,omitempty"`
   129  	// IntervalString compiles into Interval at load time.
   130  	IntervalString string `json:"interval,omitempty"`
   131  	// Interval specifies how often prow will push metrics
   132  	// to the pushgateway. Defaults to 1m.
   133  	Interval time.Duration `json:"-"`
   134  }
   135  
   136  // Controller holds configuration applicable to all agent-specific
   137  // prow controllers.
   138  type Controller struct {
   139  	// JobURLTemplateString compiles into JobURLTemplate at load time.
   140  	JobURLTemplateString string `json:"job_url_template,omitempty"`
   141  	// JobURLTemplate is compiled at load time from JobURLTemplateString. It
   142  	// will be passed a kube.ProwJob and is used to set the URL for the
   143  	// "Details" link on GitHub as well as the link from deck.
   144  	JobURLTemplate *template.Template `json:"-"`
   145  
   146  	// ReportTemplateString compiles into ReportTemplate at load time.
   147  	ReportTemplateString string `json:"report_template,omitempty"`
   148  	// ReportTemplate is compiled at load time from ReportTemplateString. It
   149  	// will be passed a kube.ProwJob and can provide an optional blurb below
   150  	// the test failures comment.
   151  	ReportTemplate *template.Template `json:"-"`
   152  
   153  	// MaxConcurrency is the maximum number of tests running concurrently that
   154  	// will be allowed by the controller. 0 implies no limit.
   155  	MaxConcurrency int `json:"max_concurrency,omitempty"`
   156  
   157  	// MaxGoroutines is the maximum number of goroutines spawned inside the
   158  	// controller to handle tests. Defaults to 20. Needs to be a positive
   159  	// number.
   160  	MaxGoroutines int `json:"max_goroutines,omitempty"`
   161  
   162  	// AllowCancellations enables aborting presubmit jobs for commits that
   163  	// have been superseded by newer commits in Github pull requests.
   164  	AllowCancellations bool `json:"allow_cancellations,omitempty"`
   165  }
   166  
   167  // Plank is config for the plank controller.
   168  type Plank struct {
   169  	Controller `json:",inline"`
   170  	// PodPendingTimeoutString compiles into PodPendingTimeout at load time.
   171  	PodPendingTimeoutString string `json:"pod_pending_timeout,omitempty"`
   172  	// PodPendingTimeout is after how long the controller will perform a garbage
   173  	// collection on pending pods. Defaults to one day.
   174  	PodPendingTimeout time.Duration `json:"-"`
   175  	// DefaultDecorationConfig are defaults for shared fields for ProwJobs
   176  	// that request to have their PodSpecs decorated
   177  	DefaultDecorationConfig *kube.DecorationConfig `json:"default_decoration_config,omitempty"`
   178  	// JobURLPrefix is the host and path prefix under
   179  	// which job details will be viewable
   180  	JobURLPrefix string `json:"job_url_prefix,omitempty"`
   181  }
   182  
   183  // Gerrit is config for the gerrit controller.
   184  type Gerrit struct {
   185  	// TickInterval is how often we do a sync with binded gerrit instance
   186  	TickIntervalString string        `json:"tick_interval,omitempty"`
   187  	TickInterval       time.Duration `json:"-"`
   188  	// RateLimit defines how many changes to query per gerrit API call
   189  	// default is 5
   190  	RateLimit int `json:"ratelimit,omitempty"`
   191  }
   192  
   193  // JenkinsOperator is config for the jenkins-operator controller.
   194  type JenkinsOperator struct {
   195  	Controller `json:",inline"`
   196  	// LabelSelectorString compiles into LabelSelector at load time.
   197  	// If set, this option needs to match --label-selector used by
   198  	// the desired jenkins-operator. This option is considered
   199  	// invalid when provided with a single jenkins-operator config.
   200  	//
   201  	// For label selector syntax, see below:
   202  	// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
   203  	LabelSelectorString string `json:"label_selector,omitempty"`
   204  	// LabelSelector is used so different jenkins-operator replicas
   205  	// can use their own configuration.
   206  	LabelSelector labels.Selector `json:"-"`
   207  }
   208  
   209  // Sinker is config for the sinker controller.
   210  type Sinker struct {
   211  	// ResyncPeriodString compiles into ResyncPeriod at load time.
   212  	ResyncPeriodString string `json:"resync_period,omitempty"`
   213  	// ResyncPeriod is how often the controller will perform a garbage
   214  	// collection. Defaults to one hour.
   215  	ResyncPeriod time.Duration `json:"-"`
   216  	// MaxProwJobAgeString compiles into MaxProwJobAge at load time.
   217  	MaxProwJobAgeString string `json:"max_prowjob_age,omitempty"`
   218  	// MaxProwJobAge is how old a ProwJob can be before it is garbage-collected.
   219  	// Defaults to one week.
   220  	MaxProwJobAge time.Duration `json:"-"`
   221  	// MaxPodAgeString compiles into MaxPodAge at load time.
   222  	MaxPodAgeString string `json:"max_pod_age,omitempty"`
   223  	// MaxPodAge is how old a Pod can be before it is garbage-collected.
   224  	// Defaults to one day.
   225  	MaxPodAge time.Duration `json:"-"`
   226  }
   227  
   228  // Spyglass holds config for Spyglass
   229  type Spyglass struct {
   230  	// Viewers is a map of Regexp strings to viewer names that defines which sets
   231  	// of artifacts need to be consumed by which viewers. The keys are compiled
   232  	// and stored in RegexCache at load time.
   233  	Viewers map[string][]string `json:"viewers,omitempty"`
   234  	// RegexCache is a map of viewer regexp strings to their compiled equivalents.
   235  	RegexCache map[string]*regexp.Regexp `json:"-"`
   236  	// SizeLimit is the max size artifact in bytes that Spyglass will attempt to
   237  	// read in entirety. This will only affect viewers attempting to use
   238  	// artifact.ReadAll(). To exclude outlier artifacts, set this limit to
   239  	// expected file size + variance. To include all artifacts with high
   240  	// probability, use 2*maximum observed artifact size.
   241  	SizeLimit int64 `json:"size_limit,omitempty"`
   242  }
   243  
   244  // Deck holds config for deck.
   245  type Deck struct {
   246  	// Spyglass specifies which viewers will be used for which artifacts when viewing a job in Deck
   247  	Spyglass Spyglass `json:"spyglass,omitempty"`
   248  	// TideUpdatePeriodString compiles into TideUpdatePeriod at load time.
   249  	TideUpdatePeriodString string `json:"tide_update_period,omitempty"`
   250  	// TideUpdatePeriod specifies how often Deck will fetch status from Tide. Defaults to 10s.
   251  	TideUpdatePeriod time.Duration `json:"-"`
   252  	// HiddenRepos is a list of orgs and/or repos that should not be displayed by Deck.
   253  	HiddenRepos []string `json:"hidden_repos,omitempty"`
   254  	// ExternalAgentLogs ensures external agents can expose
   255  	// their logs in prow.
   256  	ExternalAgentLogs []ExternalAgentLog `json:"external_agent_logs,omitempty"`
   257  	// Branding of the frontend
   258  	Branding *Branding `json:"branding,omitempty"`
   259  }
   260  
   261  // ExternalAgentLog ensures an external agent like Jenkins can expose
   262  // its logs in prow.
   263  type ExternalAgentLog struct {
   264  	// Agent is an external prow agent that supports exposing
   265  	// logs via deck.
   266  	Agent string `json:"agent,omitempty"`
   267  	// SelectorString compiles into Selector at load time.
   268  	SelectorString string `json:"selector,omitempty"`
   269  	// Selector can be used in prow deployments where the workload has
   270  	// been sharded between controllers of the same agent. For more info
   271  	// see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
   272  	Selector labels.Selector `json:"-"`
   273  	// URLTemplateString compiles into URLTemplate at load time.
   274  	URLTemplateString string `json:"url_template,omitempty"`
   275  	// URLTemplate is compiled at load time from URLTemplateString. It
   276  	// will be passed a kube.ProwJob and the generated URL should provide
   277  	// logs for the ProwJob.
   278  	URLTemplate *template.Template `json:"-"`
   279  }
   280  
   281  // Branding holds branding configuration for deck.
   282  type Branding struct {
   283  	// Logo is the location of the logo that will be loaded in deck.
   284  	Logo string `json:"logo,omitempty"`
   285  	// Favicon is the location of the favicon that will be loaded in deck.
   286  	Favicon string `json:"favicon,omitempty"`
   287  	// BackgroundColor is the color of the background.
   288  	BackgroundColor string `json:"background_color,omitempty"`
   289  	// HeaderColor is the color of the header.
   290  	HeaderColor string `json:"header_color,omitempty"`
   291  }
   292  
   293  // Build is config for the knative build controller.
   294  type Build struct {
   295  	// AllowCancellations enables aborting presubmit jobs for commits that
   296  	// have been superseded by newer commits in Github pull requests.
   297  	AllowCancellations bool `json:"allow_cancellations,omitempty"`
   298  }
   299  
   300  // PubSubSubscriptions maps GCP projects to a list of Topics.
   301  type PubsubSubscriptions map[string][]string
   302  
   303  // Load loads and parses the config at path.
   304  func Load(prowConfig, jobConfig string) (c *Config, err error) {
   305  	// we never want config loading to take down the prow components
   306  	defer func() {
   307  		if r := recover(); r != nil {
   308  			c, err = nil, fmt.Errorf("panic loading config: %v", r)
   309  		}
   310  	}()
   311  	c, err = loadConfig(prowConfig, jobConfig)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	if err := c.finalizeJobConfig(); err != nil {
   316  		return nil, err
   317  	}
   318  	if err := c.validateComponentConfig(); err != nil {
   319  		return nil, err
   320  	}
   321  	if err := c.validateJobConfig(); err != nil {
   322  		return nil, err
   323  	}
   324  	return c, nil
   325  }
   326  
   327  // loadConfig loads one or multiple config files and returns a config object.
   328  func loadConfig(prowConfig, jobConfig string) (*Config, error) {
   329  	stat, err := os.Stat(prowConfig)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	if stat.IsDir() {
   335  		return nil, fmt.Errorf("prowConfig cannot be a dir - %s", prowConfig)
   336  	}
   337  
   338  	var nc Config
   339  	if err := yamlToConfig(prowConfig, &nc); err != nil {
   340  		return nil, err
   341  	}
   342  	if err := parseProwConfig(&nc); err != nil {
   343  		return nil, err
   344  	}
   345  
   346  	// TODO(krzyzacy): temporary allow empty jobconfig
   347  	//                 also temporary allow job config in prow config
   348  	if jobConfig == "" {
   349  		return &nc, nil
   350  	}
   351  
   352  	stat, err = os.Stat(jobConfig)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	if !stat.IsDir() {
   358  		// still support a single file
   359  		var jc JobConfig
   360  		if err := yamlToConfig(jobConfig, &jc); err != nil {
   361  			return nil, err
   362  		}
   363  		if err := nc.mergeJobConfig(jc); err != nil {
   364  			return nil, err
   365  		}
   366  		return &nc, nil
   367  	}
   368  
   369  	// we need to ensure all config files have unique basenames,
   370  	// since updateconfig plugin will use basename as a key in the configmap
   371  	uniqueBasenames := sets.String{}
   372  
   373  	err = filepath.Walk(jobConfig, func(path string, info os.FileInfo, err error) error {
   374  		if err != nil {
   375  			logrus.WithError(err).Errorf("walking path %q.", path)
   376  			// bad file should not stop us from parsing the directory
   377  			return nil
   378  		}
   379  
   380  		if strings.HasPrefix(info.Name(), "..") {
   381  			// kubernetes volumes also include files we
   382  			// should not look be looking into for keys
   383  			if info.IsDir() {
   384  				return filepath.SkipDir
   385  			}
   386  			return nil
   387  		}
   388  
   389  		if filepath.Ext(path) != ".yaml" && filepath.Ext(path) != ".yml" {
   390  			return nil
   391  		}
   392  
   393  		if info.IsDir() {
   394  			return nil
   395  		}
   396  
   397  		base := filepath.Base(path)
   398  		if uniqueBasenames.Has(base) {
   399  			return fmt.Errorf("duplicated basename is not allowed: %s", base)
   400  		}
   401  		uniqueBasenames.Insert(base)
   402  
   403  		var subConfig JobConfig
   404  		if err := yamlToConfig(path, &subConfig); err != nil {
   405  			return err
   406  		}
   407  		return nc.mergeJobConfig(subConfig)
   408  	})
   409  
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  
   414  	return &nc, nil
   415  }
   416  
   417  // LoadSecrets loads multiple paths of secrets and add them in a map.
   418  func LoadSecrets(paths []string) (map[string][]byte, error) {
   419  	secretsMap := make(map[string][]byte, len(paths))
   420  
   421  	for _, path := range paths {
   422  		secretValue, err := LoadSingleSecret(path)
   423  		if err != nil {
   424  			return nil, err
   425  		}
   426  		secretsMap[path] = secretValue
   427  	}
   428  	return secretsMap, nil
   429  }
   430  
   431  // LoadSingleSecret reads and returns the value of a single file.
   432  func LoadSingleSecret(path string) ([]byte, error) {
   433  	b, err := ioutil.ReadFile(path)
   434  	if err != nil {
   435  		return nil, fmt.Errorf("error reading %s: %v", path, err)
   436  	}
   437  	return bytes.TrimSpace(b), nil
   438  }
   439  
   440  // yamlToConfig converts a yaml file into a Config object
   441  func yamlToConfig(path string, nc interface{}) error {
   442  	b, err := ioutil.ReadFile(path)
   443  	if err != nil {
   444  		return fmt.Errorf("error reading %s: %v", path, err)
   445  	}
   446  	if err := yaml.Unmarshal(b, nc); err != nil {
   447  		return fmt.Errorf("error unmarshaling %s: %v", path, err)
   448  	}
   449  	var jc *JobConfig
   450  	switch v := nc.(type) {
   451  	case *JobConfig:
   452  		jc = v
   453  	case *Config:
   454  		jc = &v.JobConfig
   455  	}
   456  	for rep := range jc.Presubmits {
   457  		var fix func(*Presubmit)
   458  		fix = func(job *Presubmit) {
   459  			job.SourcePath = path
   460  			for i := range job.RunAfterSuccess {
   461  				fix(&job.RunAfterSuccess[i])
   462  			}
   463  		}
   464  		for i := range jc.Presubmits[rep] {
   465  			fix(&jc.Presubmits[rep][i])
   466  		}
   467  	}
   468  	for rep := range jc.Postsubmits {
   469  		var fix func(*Postsubmit)
   470  		fix = func(job *Postsubmit) {
   471  			job.SourcePath = path
   472  			for i := range job.RunAfterSuccess {
   473  				fix(&job.RunAfterSuccess[i])
   474  			}
   475  		}
   476  		for i := range jc.Postsubmits[rep] {
   477  			fix(&jc.Postsubmits[rep][i])
   478  		}
   479  	}
   480  
   481  	var fix func(*Periodic)
   482  	fix = func(job *Periodic) {
   483  		job.SourcePath = path
   484  		for i := range job.RunAfterSuccess {
   485  			fix(&job.RunAfterSuccess[i])
   486  		}
   487  	}
   488  	for i := range jc.Periodics {
   489  		fix(&jc.Periodics[i])
   490  	}
   491  	return nil
   492  }
   493  
   494  // mergeConfig merges two JobConfig together
   495  // It will try to merge:
   496  //	- Presubmits
   497  //	- Postsubmits
   498  // 	- Periodics
   499  //	- PodPresets
   500  func (c *Config) mergeJobConfig(jc JobConfig) error {
   501  	// Merge everything
   502  	// *** Presets ***
   503  	c.Presets = append(c.Presets, jc.Presets...)
   504  
   505  	// validate no duplicated presets
   506  	validLabels := map[string]string{}
   507  	for _, preset := range c.Presets {
   508  		for label, val := range preset.Labels {
   509  			if _, ok := validLabels[label]; ok {
   510  				return fmt.Errorf("duplicated preset label : %s", label)
   511  			}
   512  			validLabels[label] = val
   513  		}
   514  	}
   515  
   516  	// *** Periodics ***
   517  	c.Periodics = append(c.Periodics, jc.Periodics...)
   518  
   519  	// *** Presubmits ***
   520  	if c.Presubmits == nil {
   521  		c.Presubmits = make(map[string][]Presubmit)
   522  	}
   523  	for repo, jobs := range jc.Presubmits {
   524  		c.Presubmits[repo] = append(c.Presubmits[repo], jobs...)
   525  	}
   526  
   527  	// *** Postsubmits ***
   528  	if c.Postsubmits == nil {
   529  		c.Postsubmits = make(map[string][]Postsubmit)
   530  	}
   531  	for repo, jobs := range jc.Postsubmits {
   532  		c.Postsubmits[repo] = append(c.Postsubmits[repo], jobs...)
   533  	}
   534  
   535  	return nil
   536  }
   537  
   538  func setPresubmitDecorationDefaults(c *Config, ps *Presubmit) {
   539  	if ps.Decorate {
   540  		ps.DecorationConfig = ps.DecorationConfig.ApplyDefault(c.Plank.DefaultDecorationConfig)
   541  	}
   542  
   543  	for i := range ps.RunAfterSuccess {
   544  		setPresubmitDecorationDefaults(c, &ps.RunAfterSuccess[i])
   545  	}
   546  }
   547  
   548  func setPostsubmitDecorationDefaults(c *Config, ps *Postsubmit) {
   549  	if ps.Decorate {
   550  		ps.DecorationConfig = ps.DecorationConfig.ApplyDefault(c.Plank.DefaultDecorationConfig)
   551  	}
   552  
   553  	for i := range ps.RunAfterSuccess {
   554  		setPostsubmitDecorationDefaults(c, &ps.RunAfterSuccess[i])
   555  	}
   556  }
   557  
   558  func setPeriodicDecorationDefaults(c *Config, ps *Periodic) {
   559  	if ps.Decorate {
   560  		ps.DecorationConfig = ps.DecorationConfig.ApplyDefault(c.Plank.DefaultDecorationConfig)
   561  	}
   562  
   563  	for i := range ps.RunAfterSuccess {
   564  		setPeriodicDecorationDefaults(c, &ps.RunAfterSuccess[i])
   565  	}
   566  }
   567  
   568  // finalizeJobConfig mutates and fixes entries for jobspecs
   569  func (c *Config) finalizeJobConfig() error {
   570  	if c.decorationRequested() {
   571  		if c.Plank.DefaultDecorationConfig == nil {
   572  			return errors.New("no default decoration config provided for plank")
   573  		}
   574  		if c.Plank.DefaultDecorationConfig.UtilityImages == nil {
   575  			return errors.New("no default decoration image pull specs provided for plank")
   576  		}
   577  		if c.Plank.DefaultDecorationConfig.GCSConfiguration == nil {
   578  			return errors.New("no default GCS decoration config provided for plank")
   579  		}
   580  		if c.Plank.DefaultDecorationConfig.GCSCredentialsSecret == "" {
   581  			return errors.New("no default GCS credentials secret provided for plank")
   582  		}
   583  
   584  		for _, vs := range c.Presubmits {
   585  			for i := range vs {
   586  				setPresubmitDecorationDefaults(c, &vs[i])
   587  			}
   588  		}
   589  
   590  		for _, js := range c.Postsubmits {
   591  			for i := range js {
   592  				setPostsubmitDecorationDefaults(c, &js[i])
   593  			}
   594  		}
   595  
   596  		for i := range c.Periodics {
   597  			setPeriodicDecorationDefaults(c, &c.Periodics[i])
   598  		}
   599  	}
   600  
   601  	// Ensure that regexes are valid and set defaults.
   602  	for _, vs := range c.Presubmits {
   603  		c.defaultPresubmitFields(vs)
   604  		if err := SetPresubmitRegexes(vs); err != nil {
   605  			return fmt.Errorf("could not set regex: %v", err)
   606  		}
   607  	}
   608  	for _, js := range c.Postsubmits {
   609  		c.defaultPostsubmitFields(js)
   610  		if err := SetPostsubmitRegexes(js); err != nil {
   611  			return fmt.Errorf("could not set regex: %v", err)
   612  		}
   613  	}
   614  
   615  	c.defaultPeriodicFields(c.Periodics)
   616  
   617  	for _, v := range c.AllPresubmits(nil) {
   618  		if err := resolvePresets(v.Name, v.Labels, v.Spec, c.Presets); err != nil {
   619  			return err
   620  		}
   621  	}
   622  
   623  	for _, v := range c.AllPostsubmits(nil) {
   624  		if err := resolvePresets(v.Name, v.Labels, v.Spec, c.Presets); err != nil {
   625  			return err
   626  		}
   627  	}
   628  
   629  	for _, v := range c.AllPeriodics() {
   630  		if err := resolvePresets(v.Name, v.Labels, v.Spec, c.Presets); err != nil {
   631  			return err
   632  		}
   633  	}
   634  
   635  	return nil
   636  }
   637  
   638  // validateComponentConfig validates the infrastructure component configuration
   639  func (c *Config) validateComponentConfig() error {
   640  	if _, err := url.Parse(c.Plank.JobURLPrefix); c.Plank.JobURLPrefix != "" && err != nil {
   641  		return fmt.Errorf("plank declares an invalid job URL prefix %q: %v", c.Plank.JobURLPrefix, err)
   642  	}
   643  	return nil
   644  }
   645  
   646  var jobNameRegex = regexp.MustCompile(`^[A-Za-z0-9-._]+$`)
   647  
   648  func validateJobBase(v JobBase, jobType kube.ProwJobType, podNamespace string) error {
   649  	if !jobNameRegex.MatchString(v.Name) {
   650  		return fmt.Errorf("name: must match regex %q", jobNameRegex.String())
   651  	}
   652  	// Ensure max_concurrency is non-negative.
   653  	if v.MaxConcurrency < 0 {
   654  		return fmt.Errorf("max_concurrency: %d must be a non-negative number", v.MaxConcurrency)
   655  	}
   656  	if err := validateAgent(v, podNamespace); err != nil {
   657  		return err
   658  	}
   659  	if err := validatePodSpec(jobType, v.Spec); err != nil {
   660  		return err
   661  	}
   662  	if err := validateLabels(v.Labels); err != nil {
   663  		return err
   664  	}
   665  	if v.Spec == nil || len(v.Spec.Containers) == 0 {
   666  		return nil // knative-build and jenkins jobs have no spec
   667  	}
   668  	return validateDecoration(v.Spec.Containers[0], v.DecorationConfig)
   669  }
   670  
   671  // validateJobConfig validates if all the jobspecs/presets are valid
   672  // if you are mutating the jobs, please add it to finalizeJobConfig above
   673  func (c *Config) validateJobConfig() error {
   674  	type orgRepoJobName struct {
   675  		orgRepo, jobName string
   676  	}
   677  
   678  	// Validate presubmits.
   679  	// Checking that no duplicate job in prow config exists on the same org / repo / branch.
   680  	validPresubmits := map[orgRepoJobName][]Presubmit{}
   681  	for repo, jobs := range c.Presubmits {
   682  		for _, job := range listPresubmits(jobs) {
   683  			repoJobName := orgRepoJobName{repo, job.Name}
   684  			for _, existingJob := range validPresubmits[repoJobName] {
   685  				if existingJob.Brancher.Intersects(job.Brancher) {
   686  					return fmt.Errorf("duplicated presubmit job: %s", job.Name)
   687  				}
   688  			}
   689  			validPresubmits[repoJobName] = append(validPresubmits[repoJobName], job)
   690  		}
   691  	}
   692  
   693  	for _, v := range c.AllPresubmits(nil) {
   694  		if err := validateJobBase(v.JobBase, prowjobv1.PresubmitJob, c.PodNamespace); err != nil {
   695  			return fmt.Errorf("invalid presubmit job %s: %v", v.Name, err)
   696  		}
   697  		if err := validateTriggering(v); err != nil {
   698  			return err
   699  		}
   700  	}
   701  
   702  	// Validate postsubmits.
   703  	// Checking that no duplicate job in prow config exists on the same org / repo / branch.
   704  	validPostsubmits := map[orgRepoJobName][]Postsubmit{}
   705  	for repo, jobs := range c.Postsubmits {
   706  		for _, job := range listPostsubmits(jobs) {
   707  			repoJobName := orgRepoJobName{repo, job.Name}
   708  			for _, existingJob := range validPostsubmits[repoJobName] {
   709  				if existingJob.Brancher.Intersects(job.Brancher) {
   710  					return fmt.Errorf("duplicated postsubmit job: %s", job.Name)
   711  				}
   712  			}
   713  			validPostsubmits[repoJobName] = append(validPostsubmits[repoJobName], job)
   714  		}
   715  	}
   716  
   717  	for _, j := range c.AllPostsubmits(nil) {
   718  		if err := validateJobBase(j.JobBase, prowjobv1.PostsubmitJob, c.PodNamespace); err != nil {
   719  			return fmt.Errorf("invalid postsubmit job %s: %v", j.Name, err)
   720  		}
   721  	}
   722  
   723  	// validate no duplicated periodics
   724  	validPeriodics := sets.NewString()
   725  	// Ensure that the periodic durations are valid and specs exist.
   726  	for _, p := range c.AllPeriodics() {
   727  		if validPeriodics.Has(p.Name) {
   728  			return fmt.Errorf("duplicated periodic job : %s", p.Name)
   729  		}
   730  		validPeriodics.Insert(p.Name)
   731  		if err := validateJobBase(p.JobBase, prowjobv1.PeriodicJob, c.PodNamespace); err != nil {
   732  			return fmt.Errorf("invalid periodic job %s: %v", p.Name, err)
   733  		}
   734  	}
   735  	// Set the interval on the periodic jobs. It doesn't make sense to do this
   736  	// for child jobs.
   737  	for j, p := range c.Periodics {
   738  		if p.Cron != "" && p.Interval != "" {
   739  			return fmt.Errorf("cron and interval cannot be both set in periodic %s", p.Name)
   740  		} else if p.Cron == "" && p.Interval == "" {
   741  			return fmt.Errorf("cron and interval cannot be both empty in periodic %s", p.Name)
   742  		} else if p.Cron != "" {
   743  			if _, err := cron.Parse(p.Cron); err != nil {
   744  				return fmt.Errorf("invalid cron string %s in periodic %s: %v", p.Cron, p.Name, err)
   745  			}
   746  		} else {
   747  			d, err := time.ParseDuration(c.Periodics[j].Interval)
   748  			if err != nil {
   749  				return fmt.Errorf("cannot parse duration for %s: %v", c.Periodics[j].Name, err)
   750  			}
   751  			c.Periodics[j].interval = d
   752  		}
   753  	}
   754  
   755  	return nil
   756  }
   757  
   758  func parseProwConfig(c *Config) error {
   759  	if err := ValidateController(&c.Plank.Controller); err != nil {
   760  		return fmt.Errorf("validating plank config: %v", err)
   761  	}
   762  
   763  	if c.Plank.PodPendingTimeoutString == "" {
   764  		c.Plank.PodPendingTimeout = 24 * time.Hour
   765  	} else {
   766  		podPendingTimeout, err := time.ParseDuration(c.Plank.PodPendingTimeoutString)
   767  		if err != nil {
   768  			return fmt.Errorf("cannot parse duration for plank.pod_pending_timeout: %v", err)
   769  		}
   770  		c.Plank.PodPendingTimeout = podPendingTimeout
   771  	}
   772  
   773  	if c.Gerrit.TickIntervalString == "" {
   774  		c.Gerrit.TickInterval = time.Minute
   775  	} else {
   776  		tickInterval, err := time.ParseDuration(c.Gerrit.TickIntervalString)
   777  		if err != nil {
   778  			return fmt.Errorf("cannot parse duration for c.gerrit.tick_interval: %v", err)
   779  		}
   780  		c.Gerrit.TickInterval = tickInterval
   781  	}
   782  
   783  	if c.Gerrit.RateLimit == 0 {
   784  		c.Gerrit.RateLimit = 5
   785  	}
   786  
   787  	for i := range c.JenkinsOperators {
   788  		if err := ValidateController(&c.JenkinsOperators[i].Controller); err != nil {
   789  			return fmt.Errorf("validating jenkins_operators config: %v", err)
   790  		}
   791  		sel, err := labels.Parse(c.JenkinsOperators[i].LabelSelectorString)
   792  		if err != nil {
   793  			return fmt.Errorf("invalid jenkins_operators.label_selector option: %v", err)
   794  		}
   795  		c.JenkinsOperators[i].LabelSelector = sel
   796  		// TODO: Invalidate overlapping selectors more
   797  		if len(c.JenkinsOperators) > 1 && c.JenkinsOperators[i].LabelSelectorString == "" {
   798  			return errors.New("selector overlap: cannot use an empty label_selector with multiple selectors")
   799  		}
   800  		if len(c.JenkinsOperators) == 1 && c.JenkinsOperators[0].LabelSelectorString != "" {
   801  			return errors.New("label_selector is invalid when used for a single jenkins-operator")
   802  		}
   803  	}
   804  
   805  	for i, agentToTmpl := range c.Deck.ExternalAgentLogs {
   806  		urlTemplate, err := template.New(agentToTmpl.Agent).Parse(agentToTmpl.URLTemplateString)
   807  		if err != nil {
   808  			return fmt.Errorf("parsing template for agent %q: %v", agentToTmpl.Agent, err)
   809  		}
   810  		c.Deck.ExternalAgentLogs[i].URLTemplate = urlTemplate
   811  		// we need to validate selectors used by deck since these are not
   812  		// sent to the api server.
   813  		s, err := labels.Parse(c.Deck.ExternalAgentLogs[i].SelectorString)
   814  		if err != nil {
   815  			return fmt.Errorf("error parsing selector %q: %v", c.Deck.ExternalAgentLogs[i].SelectorString, err)
   816  		}
   817  		c.Deck.ExternalAgentLogs[i].Selector = s
   818  	}
   819  
   820  	if c.Deck.TideUpdatePeriodString == "" {
   821  		c.Deck.TideUpdatePeriod = time.Second * 10
   822  	} else {
   823  		period, err := time.ParseDuration(c.Deck.TideUpdatePeriodString)
   824  		if err != nil {
   825  			return fmt.Errorf("cannot parse duration for deck.tide_update_period: %v", err)
   826  		}
   827  		c.Deck.TideUpdatePeriod = period
   828  	}
   829  
   830  	if c.Deck.Spyglass.SizeLimit == 0 {
   831  		c.Deck.Spyglass.SizeLimit = 100e6
   832  	} else if c.Deck.Spyglass.SizeLimit <= 0 {
   833  		return fmt.Errorf("invalid value for deck.spyglass.size_limit, must be >=0")
   834  	}
   835  
   836  	c.Deck.Spyglass.RegexCache = make(map[string]*regexp.Regexp)
   837  	for k := range c.Deck.Spyglass.Viewers {
   838  		r, err := regexp.Compile(k)
   839  		if err != nil {
   840  			return fmt.Errorf("cannot compile regexp %s, err: %v", k, err)
   841  		}
   842  		c.Deck.Spyglass.RegexCache[k] = r
   843  	}
   844  
   845  	// Map old viewer names to the new ones for backwards compatibility.
   846  	// TODO(Katharine, #10274): remove this, eventually.
   847  	oldViewers := map[string]string{
   848  		"build-log-viewer": "buildlog",
   849  		"metadata-viewer":  "metadata",
   850  		"junit-viewer":     "junit",
   851  	}
   852  
   853  	for re, viewers := range c.Deck.Spyglass.Viewers {
   854  		for i, v := range viewers {
   855  			if rename, ok := oldViewers[v]; ok {
   856  				c.Deck.Spyglass.Viewers[re][i] = rename
   857  			}
   858  		}
   859  	}
   860  
   861  	if c.PushGateway.IntervalString == "" {
   862  		c.PushGateway.Interval = time.Minute
   863  	} else {
   864  		interval, err := time.ParseDuration(c.PushGateway.IntervalString)
   865  		if err != nil {
   866  			return fmt.Errorf("cannot parse duration for push_gateway.interval: %v", err)
   867  		}
   868  		c.PushGateway.Interval = interval
   869  	}
   870  
   871  	if c.Sinker.ResyncPeriodString == "" {
   872  		c.Sinker.ResyncPeriod = time.Hour
   873  	} else {
   874  		resyncPeriod, err := time.ParseDuration(c.Sinker.ResyncPeriodString)
   875  		if err != nil {
   876  			return fmt.Errorf("cannot parse duration for sinker.resync_period: %v", err)
   877  		}
   878  		c.Sinker.ResyncPeriod = resyncPeriod
   879  	}
   880  
   881  	if c.Sinker.MaxProwJobAgeString == "" {
   882  		c.Sinker.MaxProwJobAge = 7 * 24 * time.Hour
   883  	} else {
   884  		maxProwJobAge, err := time.ParseDuration(c.Sinker.MaxProwJobAgeString)
   885  		if err != nil {
   886  			return fmt.Errorf("cannot parse duration for max_prowjob_age: %v", err)
   887  		}
   888  		c.Sinker.MaxProwJobAge = maxProwJobAge
   889  	}
   890  
   891  	if c.Sinker.MaxPodAgeString == "" {
   892  		c.Sinker.MaxPodAge = 24 * time.Hour
   893  	} else {
   894  		maxPodAge, err := time.ParseDuration(c.Sinker.MaxPodAgeString)
   895  		if err != nil {
   896  			return fmt.Errorf("cannot parse duration for max_pod_age: %v", err)
   897  		}
   898  		c.Sinker.MaxPodAge = maxPodAge
   899  	}
   900  
   901  	if c.Tide.SyncPeriodString == "" {
   902  		c.Tide.SyncPeriod = time.Minute
   903  	} else {
   904  		period, err := time.ParseDuration(c.Tide.SyncPeriodString)
   905  		if err != nil {
   906  			return fmt.Errorf("cannot parse duration for tide.sync_period: %v", err)
   907  		}
   908  		c.Tide.SyncPeriod = period
   909  	}
   910  	if c.Tide.StatusUpdatePeriodString == "" {
   911  		c.Tide.StatusUpdatePeriod = c.Tide.SyncPeriod
   912  	} else {
   913  		period, err := time.ParseDuration(c.Tide.StatusUpdatePeriodString)
   914  		if err != nil {
   915  			return fmt.Errorf("cannot parse duration for tide.status_update_period: %v", err)
   916  		}
   917  		c.Tide.StatusUpdatePeriod = period
   918  	}
   919  
   920  	if c.Tide.MaxGoroutines == 0 {
   921  		c.Tide.MaxGoroutines = 20
   922  	}
   923  	if c.Tide.MaxGoroutines <= 0 {
   924  		return fmt.Errorf("tide has invalid max_goroutines (%d), it needs to be a positive number", c.Tide.MaxGoroutines)
   925  	}
   926  
   927  	for name, method := range c.Tide.MergeType {
   928  		if method != github.MergeMerge &&
   929  			method != github.MergeRebase &&
   930  			method != github.MergeSquash {
   931  			return fmt.Errorf("merge type %q for %s is not a valid type", method, name)
   932  		}
   933  	}
   934  
   935  	for i, tq := range c.Tide.Queries {
   936  		if err := tq.Validate(); err != nil {
   937  			return fmt.Errorf("tide query (index %d) is invalid: %v", i, err)
   938  		}
   939  	}
   940  
   941  	if c.ProwJobNamespace == "" {
   942  		c.ProwJobNamespace = "default"
   943  	}
   944  	if c.PodNamespace == "" {
   945  		c.PodNamespace = "default"
   946  	}
   947  
   948  	if c.LogLevel == "" {
   949  		c.LogLevel = "info"
   950  	}
   951  	lvl, err := logrus.ParseLevel(c.LogLevel)
   952  	if err != nil {
   953  		return err
   954  	}
   955  	logrus.SetLevel(lvl)
   956  
   957  	return nil
   958  }
   959  
   960  func (c *JobConfig) decorationRequested() bool {
   961  	for _, vs := range c.Presubmits {
   962  		for i := range vs {
   963  			if vs[i].Decorate {
   964  				return true
   965  			}
   966  		}
   967  	}
   968  
   969  	for _, js := range c.Postsubmits {
   970  		for i := range js {
   971  			if js[i].Decorate {
   972  				return true
   973  			}
   974  		}
   975  	}
   976  
   977  	for i := range c.Periodics {
   978  		if c.Periodics[i].Decorate {
   979  			return true
   980  		}
   981  	}
   982  
   983  	return false
   984  }
   985  
   986  func validateLabels(labels map[string]string) error {
   987  	for label, value := range labels {
   988  		for _, prowLabel := range decorate.Labels() {
   989  			if label == prowLabel {
   990  				return fmt.Errorf("label %s is reserved for decoration", label)
   991  			}
   992  		}
   993  		if errs := validation.IsQualifiedName(label); len(errs) != 0 {
   994  			return fmt.Errorf("invalid label %s: %v", label, errs)
   995  		}
   996  		if errs := validation.IsValidLabelValue(labels[label]); len(errs) != 0 {
   997  			return fmt.Errorf("label %s has invalid value %s: %v", label, value, errs)
   998  		}
   999  	}
  1000  	return nil
  1001  }
  1002  
  1003  func validateAgent(v JobBase, podNamespace string) error {
  1004  	k := string(prowjobv1.KubernetesAgent)
  1005  	b := string(prowjobv1.KnativeBuildAgent)
  1006  	j := string(prowjobv1.JenkinsAgent)
  1007  	agents := sets.NewString(k, b, j)
  1008  	agent := v.Agent
  1009  	switch {
  1010  	case !agents.Has(agent):
  1011  		return fmt.Errorf("agent must be one of %s (found %q)", strings.Join(agents.List(), ", "), agent)
  1012  	case v.Spec != nil && agent != k:
  1013  		return fmt.Errorf("job specs require agent: %s (found %q)", k, agent)
  1014  	case agent == k && v.Spec == nil:
  1015  		return errors.New("kubernetes jobs require a spec")
  1016  	case v.BuildSpec != nil && agent != b:
  1017  		return fmt.Errorf("job build_specs require agent: %s (found %q)", b, agent)
  1018  	case agent == b && v.BuildSpec == nil:
  1019  		return errors.New("knative-build jobs require a build_spec")
  1020  	case v.DecorationConfig != nil && agent != k && agent != b:
  1021  		// TODO(fejta): only source decoration supported...
  1022  		return fmt.Errorf("decoration requires agent: %s or %s (found %q)", k, b, agent)
  1023  	case v.ErrorOnEviction && agent != k:
  1024  		return fmt.Errorf("error_on_eviction only applies to agent: %s (found %q)", k, agent)
  1025  	case v.Namespace == nil || *v.Namespace == "":
  1026  		return fmt.Errorf("failed to default namespace")
  1027  	case *v.Namespace != podNamespace && agent != b:
  1028  		// TODO(fejta): update plank to allow this (depends on client change)
  1029  		return fmt.Errorf("namespace customization requires agent: %s (found %q)", b, agent)
  1030  	}
  1031  	return nil
  1032  }
  1033  
  1034  func validateDecoration(container v1.Container, config *kube.DecorationConfig) error {
  1035  	if config == nil {
  1036  		return nil
  1037  	}
  1038  
  1039  	if err := config.Validate(); err != nil {
  1040  		return fmt.Errorf("invalid decoration config: %v", err)
  1041  	}
  1042  	var args []string
  1043  	args = append(append(args, container.Command...), container.Args...)
  1044  	if len(args) == 0 || args[0] == "" {
  1045  		return errors.New("decorated job containers must specify command and/or args")
  1046  	}
  1047  	return nil
  1048  }
  1049  
  1050  func resolvePresets(name string, labels map[string]string, spec *v1.PodSpec, presets []Preset) error {
  1051  	for _, preset := range presets {
  1052  		if err := mergePreset(preset, labels, spec); err != nil {
  1053  			return fmt.Errorf("job %s failed to merge presets: %v", name, err)
  1054  		}
  1055  	}
  1056  
  1057  	return nil
  1058  }
  1059  
  1060  func validatePodSpec(jobType kube.ProwJobType, spec *v1.PodSpec) error {
  1061  	if spec == nil {
  1062  		return nil
  1063  	}
  1064  
  1065  	if len(spec.InitContainers) != 0 {
  1066  		return errors.New("pod spec may not use init containers")
  1067  	}
  1068  
  1069  	if n := len(spec.Containers); n != 1 {
  1070  		return fmt.Errorf("pod spec must specify exactly 1 container, found: %d", n)
  1071  	}
  1072  
  1073  	for _, env := range spec.Containers[0].Env {
  1074  		for _, prowEnv := range downwardapi.EnvForType(jobType) {
  1075  			if env.Name == prowEnv {
  1076  				// TODO(fejta): consider allowing this
  1077  				return fmt.Errorf("env %s is reserved", env.Name)
  1078  			}
  1079  		}
  1080  	}
  1081  
  1082  	for _, mount := range spec.Containers[0].VolumeMounts {
  1083  		for _, prowMount := range decorate.VolumeMounts() {
  1084  			if mount.Name == prowMount {
  1085  				return fmt.Errorf("volumeMount name %s is reserved for decoration", prowMount)
  1086  			}
  1087  		}
  1088  		for _, prowMountPath := range decorate.VolumeMountPaths() {
  1089  			if strings.HasPrefix(mount.MountPath, prowMountPath) || strings.HasPrefix(prowMountPath, mount.MountPath) {
  1090  				return fmt.Errorf("mount %s at %s conflicts with decoration mount at %s", mount.Name, mount.MountPath, prowMountPath)
  1091  			}
  1092  		}
  1093  	}
  1094  
  1095  	for _, volume := range spec.Volumes {
  1096  		for _, prowVolume := range decorate.VolumeMounts() {
  1097  			if volume.Name == prowVolume {
  1098  				return fmt.Errorf("volume %s is a reserved for decoration", volume.Name)
  1099  			}
  1100  		}
  1101  	}
  1102  
  1103  	return nil
  1104  }
  1105  
  1106  func validateTriggering(job Presubmit) error {
  1107  	if job.AlwaysRun && job.RunIfChanged != "" {
  1108  		return fmt.Errorf("job %s is set to always run but also declares run_if_changed targets, which are mutually exclusive", job.Name)
  1109  	}
  1110  
  1111  	if !job.SkipReport && job.Context == "" {
  1112  		return fmt.Errorf("job %s is set to report but has no context configured", job.Name)
  1113  	}
  1114  
  1115  	return nil
  1116  }
  1117  
  1118  // ValidateController validates the provided controller config.
  1119  func ValidateController(c *Controller) error {
  1120  	urlTmpl, err := template.New("JobURL").Parse(c.JobURLTemplateString)
  1121  	if err != nil {
  1122  		return fmt.Errorf("parsing template: %v", err)
  1123  	}
  1124  	c.JobURLTemplate = urlTmpl
  1125  
  1126  	reportTmpl, err := template.New("Report").Parse(c.ReportTemplateString)
  1127  	if err != nil {
  1128  		return fmt.Errorf("parsing template: %v", err)
  1129  	}
  1130  	c.ReportTemplate = reportTmpl
  1131  	if c.MaxConcurrency < 0 {
  1132  		return fmt.Errorf("controller has invalid max_concurrency (%d), it needs to be a non-negative number", c.MaxConcurrency)
  1133  	}
  1134  	if c.MaxGoroutines == 0 {
  1135  		c.MaxGoroutines = 20
  1136  	}
  1137  	if c.MaxGoroutines <= 0 {
  1138  		return fmt.Errorf("controller has invalid max_goroutines (%d), it needs to be a positive number", c.MaxGoroutines)
  1139  	}
  1140  	return nil
  1141  }
  1142  
  1143  // DefaultTriggerFor returns the default regexp string used to match comments
  1144  // that should trigger the job with this name.
  1145  func DefaultTriggerFor(name string) string {
  1146  	return fmt.Sprintf(`(?m)^/test( | .* )%s,?($|\s.*)`, name)
  1147  }
  1148  
  1149  // DefaultRerunCommandFor returns the default rerun command for the job with
  1150  // this name.
  1151  func DefaultRerunCommandFor(name string) string {
  1152  	return fmt.Sprintf("/test %s", name)
  1153  }
  1154  
  1155  // defaultJobBase configures common parameters, currently Agent and Namespace.
  1156  func (c *ProwConfig) defaultJobBase(base *JobBase) {
  1157  	if base.Agent == "" { // Use kubernetes by default
  1158  		base.Agent = string(kube.KubernetesAgent)
  1159  	}
  1160  	if base.Namespace == nil || *base.Namespace == "" {
  1161  		s := c.PodNamespace
  1162  		base.Namespace = &s
  1163  	}
  1164  	if base.Cluster == "" {
  1165  		base.Cluster = kube.DefaultClusterAlias
  1166  	}
  1167  }
  1168  
  1169  func (c *ProwConfig) defaultPresubmitFields(js []Presubmit) {
  1170  	for i := range js {
  1171  		c.defaultJobBase(&js[i].JobBase)
  1172  		if js[i].Context == "" {
  1173  			js[i].Context = js[i].Name
  1174  		}
  1175  		// Default the values of Trigger and RerunCommand if both fields are
  1176  		// specified. Otherwise let validation fail as both or neither should have
  1177  		// been specified.
  1178  		if js[i].Trigger == "" && js[i].RerunCommand == "" {
  1179  			js[i].Trigger = DefaultTriggerFor(js[i].Name)
  1180  			js[i].RerunCommand = DefaultRerunCommandFor(js[i].Name)
  1181  		}
  1182  		c.defaultPresubmitFields(js[i].RunAfterSuccess)
  1183  	}
  1184  }
  1185  
  1186  func (c *ProwConfig) defaultPostsubmitFields(js []Postsubmit) {
  1187  	for i := range js {
  1188  		c.defaultJobBase(&js[i].JobBase)
  1189  		c.defaultPostsubmitFields(js[i].RunAfterSuccess)
  1190  	}
  1191  }
  1192  
  1193  func (c *ProwConfig) defaultPeriodicFields(js []Periodic) {
  1194  	for i := range js {
  1195  		c.defaultJobBase(&js[i].JobBase)
  1196  		c.defaultPeriodicFields(js[i].RunAfterSuccess)
  1197  	}
  1198  }
  1199  
  1200  // SetPresubmitRegexes compiles and validates all the regular expressions for
  1201  // the provided presubmits.
  1202  func SetPresubmitRegexes(js []Presubmit) error {
  1203  	for i, j := range js {
  1204  		if re, err := regexp.Compile(j.Trigger); err == nil {
  1205  			js[i].re = re
  1206  		} else {
  1207  			return fmt.Errorf("could not compile trigger regex for %s: %v", j.Name, err)
  1208  		}
  1209  		if !js[i].re.MatchString(j.RerunCommand) {
  1210  			return fmt.Errorf("for job %s, rerun command \"%s\" does not match trigger \"%s\"", j.Name, j.RerunCommand, j.Trigger)
  1211  		}
  1212  		b, err := setBrancherRegexes(j.Brancher)
  1213  		if err != nil {
  1214  			return fmt.Errorf("could not set branch regexes for %s: %v", j.Name, err)
  1215  		}
  1216  		js[i].Brancher = b
  1217  
  1218  		c, err := setChangeRegexes(j.RegexpChangeMatcher)
  1219  		if err != nil {
  1220  			return fmt.Errorf("could not set change regexes for %s: %v", j.Name, err)
  1221  		}
  1222  		js[i].RegexpChangeMatcher = c
  1223  
  1224  		if err := SetPresubmitRegexes(j.RunAfterSuccess); err != nil {
  1225  			return err
  1226  		}
  1227  	}
  1228  	return nil
  1229  }
  1230  
  1231  // setBrancherRegexes compiles and validates all the regular expressions for
  1232  // the provided branch specifiers.
  1233  func setBrancherRegexes(br Brancher) (Brancher, error) {
  1234  	if len(br.Branches) > 0 {
  1235  		if re, err := regexp.Compile(strings.Join(br.Branches, `|`)); err == nil {
  1236  			br.re = re
  1237  		} else {
  1238  			return br, fmt.Errorf("could not compile positive branch regex: %v", err)
  1239  		}
  1240  	}
  1241  	if len(br.SkipBranches) > 0 {
  1242  		if re, err := regexp.Compile(strings.Join(br.SkipBranches, `|`)); err == nil {
  1243  			br.reSkip = re
  1244  		} else {
  1245  			return br, fmt.Errorf("could not compile negative branch regex: %v", err)
  1246  		}
  1247  	}
  1248  	return br, nil
  1249  }
  1250  
  1251  func setChangeRegexes(cm RegexpChangeMatcher) (RegexpChangeMatcher, error) {
  1252  	if cm.RunIfChanged != "" {
  1253  		re, err := regexp.Compile(cm.RunIfChanged)
  1254  		if err != nil {
  1255  			return cm, fmt.Errorf("could not compile run_if_changed regex: %v", err)
  1256  		}
  1257  		cm.reChanges = re
  1258  	}
  1259  	return cm, nil
  1260  }
  1261  
  1262  // SetPostsubmitRegexes compiles and validates all the regular expressions for
  1263  // the provided postsubmits.
  1264  func SetPostsubmitRegexes(ps []Postsubmit) error {
  1265  	for i, j := range ps {
  1266  		b, err := setBrancherRegexes(j.Brancher)
  1267  		if err != nil {
  1268  			return fmt.Errorf("could not set branch regexes for %s: %v", j.Name, err)
  1269  		}
  1270  		ps[i].Brancher = b
  1271  		c, err := setChangeRegexes(j.RegexpChangeMatcher)
  1272  		if err != nil {
  1273  			return fmt.Errorf("could not set change regexes for %s: %v", j.Name, err)
  1274  		}
  1275  		ps[i].RegexpChangeMatcher = c
  1276  		if err := SetPostsubmitRegexes(j.RunAfterSuccess); err != nil {
  1277  			return err
  1278  		}
  1279  	}
  1280  	return nil
  1281  }