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