github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/config/jobs.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
    18  
    19  import (
    20  	"fmt"
    21  	"regexp"
    22  	"time"
    23  
    24  	"k8s.io/api/core/v1"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/test-infra/prow/kube"
    28  
    29  	buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
    30  )
    31  
    32  // Preset is intended to match the k8s' PodPreset feature, and may be removed
    33  // if that feature goes beta.
    34  type Preset struct {
    35  	Labels       map[string]string `json:"labels"`
    36  	Env          []v1.EnvVar       `json:"env"`
    37  	Volumes      []v1.Volume       `json:"volumes"`
    38  	VolumeMounts []v1.VolumeMount  `json:"volumeMounts"`
    39  }
    40  
    41  func mergePreset(preset Preset, labels map[string]string, pod *v1.PodSpec) error {
    42  	if pod == nil {
    43  		return nil
    44  	}
    45  	for l, v := range preset.Labels {
    46  		if v2, ok := labels[l]; !ok || v2 != v {
    47  			return nil
    48  		}
    49  	}
    50  	for _, e1 := range preset.Env {
    51  		for i := range pod.Containers {
    52  			for _, e2 := range pod.Containers[i].Env {
    53  				if e1.Name == e2.Name {
    54  					return fmt.Errorf("env var duplicated in pod spec: %s", e1.Name)
    55  				}
    56  			}
    57  			pod.Containers[i].Env = append(pod.Containers[i].Env, e1)
    58  		}
    59  	}
    60  	for _, v1 := range preset.Volumes {
    61  		for _, v2 := range pod.Volumes {
    62  			if v1.Name == v2.Name {
    63  				return fmt.Errorf("volume duplicated in pod spec: %s", v1.Name)
    64  			}
    65  		}
    66  		pod.Volumes = append(pod.Volumes, v1)
    67  	}
    68  	for _, vm1 := range preset.VolumeMounts {
    69  		for i := range pod.Containers {
    70  			for _, vm2 := range pod.Containers[i].VolumeMounts {
    71  				if vm1.Name == vm2.Name {
    72  					return fmt.Errorf("volume mount duplicated in pod spec: %s", vm1.Name)
    73  				}
    74  			}
    75  			pod.Containers[i].VolumeMounts = append(pod.Containers[i].VolumeMounts, vm1)
    76  		}
    77  	}
    78  	return nil
    79  }
    80  
    81  // Presubmit is the job-specific trigger info.
    82  type Presubmit struct {
    83  	// eg kubernetes-pull-build-test-e2e-gce
    84  	Name string `json:"name"`
    85  	// Labels are added in prowjobs created for this job.
    86  	Labels map[string]string `json:"labels"`
    87  	// Run for every PR, or only when a comment triggers it.
    88  	AlwaysRun bool `json:"always_run"`
    89  	// Run if the PR modifies a file that matches this regex.
    90  	RunIfChanged string `json:"run_if_changed"`
    91  	// Context line for GitHub status.
    92  	Context string `json:"context"`
    93  	// eg @k8s-bot e2e test this
    94  	Trigger string `json:"trigger"`
    95  	// Valid rerun command to give users. Must match Trigger.
    96  	RerunCommand string `json:"rerun_command"`
    97  	// Whether or not to skip commenting and setting status on GitHub.
    98  	SkipReport bool `json:"skip_report"`
    99  	// Maximum number of this job running concurrently, 0 implies no limit.
   100  	MaxConcurrency int `json:"max_concurrency"`
   101  	// Agent that will take care of running this job.
   102  	Agent string `json:"agent"`
   103  	// Cluster is the alias of the cluster to run this job in. (Default: kube.DefaultClusterAlias)
   104  	Cluster string `json:"cluster"`
   105  	// Kubernetes pod spec.
   106  	Spec *v1.PodSpec `json:"spec,omitempty"`
   107  	// knative build spec.
   108  	BuildSpec *buildv1alpha1.BuildSpec `json:"build_spec,omitempty"`
   109  	// Run these jobs after successfully running this one.
   110  	RunAfterSuccess []Presubmit `json:"run_after_success"`
   111  	// Consider job optional for branch protection.
   112  	Optional bool `json:"optional,omitempty"`
   113  
   114  	Brancher
   115  
   116  	UtilityConfig
   117  
   118  	// We'll set these when we load it.
   119  	re        *regexp.Regexp // from Trigger.
   120  	reChanges *regexp.Regexp // from RunIfChanged
   121  }
   122  
   123  // Postsubmit runs on push events.
   124  type Postsubmit struct {
   125  	Name string `json:"name"`
   126  	// Labels are added in prowjobs created for this job.
   127  	Labels map[string]string `json:"labels"`
   128  	// Agent that will take care of running this job.
   129  	Agent string `json:"agent"`
   130  	// Cluster is the alias of the cluster to run this job in. (Default: kube.DefaultClusterAlias)
   131  	Cluster string `json:"cluster"`
   132  	// Kubernetes pod spec.
   133  	Spec *v1.PodSpec `json:"spec,omitempty"`
   134  	// Maximum number of this job running concurrently, 0 implies no limit.
   135  	MaxConcurrency int `json:"max_concurrency"`
   136  
   137  	Brancher
   138  
   139  	UtilityConfig
   140  
   141  	// Run these jobs after successfully running this one.
   142  	RunAfterSuccess []Postsubmit `json:"run_after_success"`
   143  }
   144  
   145  // Periodic runs on a timer.
   146  type Periodic struct {
   147  	Name string `json:"name"`
   148  	// Labels are added in prowjobs created for this job.
   149  	Labels map[string]string `json:"labels"`
   150  	// Agent that will take care of running this job.
   151  	Agent string `json:"agent"`
   152  	// Cluster is the alias of the cluster to run this job in. (Default: kube.DefaultClusterAlias)
   153  	Cluster string `json:"cluster"`
   154  	// Kubernetes pod spec.
   155  	Spec *v1.PodSpec `json:"spec,omitempty"`
   156  	// (deprecated)Interval to wait between two runs of the job.
   157  	Interval string `json:"interval"`
   158  	// Cron representation of job trigger time
   159  	Cron string `json:"cron"`
   160  	// Tags for config entries
   161  	Tags []string `json:"tags,omitempty"`
   162  	// Run these jobs after successfully running this one.
   163  	RunAfterSuccess []Periodic `json:"run_after_success"`
   164  
   165  	UtilityConfig
   166  
   167  	interval time.Duration
   168  }
   169  
   170  // SetInterval updates interval, the frequency duration it runs.
   171  func (p *Periodic) SetInterval(d time.Duration) {
   172  	p.interval = d
   173  }
   174  
   175  // GetInterval returns interval, the frequency duration it runs.
   176  func (p *Periodic) GetInterval() time.Duration {
   177  	return p.interval
   178  }
   179  
   180  // Brancher is for shared code between jobs that only run against certain
   181  // branches. An empty brancher runs against all branches.
   182  type Brancher struct {
   183  	// Do not run against these branches. Default is no branches.
   184  	SkipBranches []string `json:"skip_branches"`
   185  	// Only run against these branches. Default is all branches.
   186  	Branches []string `json:"branches"`
   187  
   188  	// We'll set these when we load it.
   189  	re     *regexp.Regexp
   190  	reSkip *regexp.Regexp
   191  }
   192  
   193  // RunsAgainstAllBranch returns true if there are both branches and skip_branches are unset
   194  func (br Brancher) RunsAgainstAllBranch() bool {
   195  	return len(br.SkipBranches) == 0 && len(br.Branches) == 0
   196  }
   197  
   198  // RunsAgainstBranch returns true if the input branch matches, given the whitelist/blacklist.
   199  func (br Brancher) RunsAgainstBranch(branch string) bool {
   200  	if br.RunsAgainstAllBranch() {
   201  		return true
   202  	}
   203  
   204  	// Favor SkipBranches over Branches
   205  	if len(br.SkipBranches) != 0 && br.reSkip.MatchString(branch) {
   206  		return false
   207  	}
   208  	if len(br.Branches) == 0 || br.re.MatchString(branch) {
   209  		return true
   210  	}
   211  	return false
   212  }
   213  
   214  // Intersects checks if other Brancher would trigger for the same branch.
   215  func (br Brancher) Intersects(other Brancher) bool {
   216  	if br.RunsAgainstAllBranch() || other.RunsAgainstAllBranch() {
   217  		return true
   218  	}
   219  	if len(br.Branches) > 0 {
   220  		baseBranches := sets.NewString(br.Branches...)
   221  		if len(other.Branches) > 0 {
   222  			otherBranches := sets.NewString(other.Branches...)
   223  			if baseBranches.Intersection(otherBranches).Len() > 0 {
   224  				return true
   225  			}
   226  			return false
   227  		}
   228  		if !baseBranches.Intersection(sets.NewString(other.SkipBranches...)).Equal(baseBranches) {
   229  			return true
   230  		}
   231  		return false
   232  	}
   233  	if len(other.Branches) == 0 {
   234  		// There can only be one Brancher with skip_branches.
   235  		return true
   236  	}
   237  	return other.Intersects(br)
   238  }
   239  
   240  // RunsAgainstChanges returns true if any of the changed input paths match the run_if_changed regex.
   241  func (ps Presubmit) RunsAgainstChanges(changes []string) bool {
   242  	for _, change := range changes {
   243  		if ps.reChanges.MatchString(change) {
   244  			return true
   245  		}
   246  	}
   247  	return false
   248  }
   249  
   250  // TriggerMatches returns true if the comment body should trigger this presubmit.
   251  //
   252  // This is usually a /test foo string.
   253  func (ps Presubmit) TriggerMatches(body string) bool {
   254  	return ps.re.MatchString(body)
   255  }
   256  
   257  // ContextRequired checks whether a context is required from github points of view (required check).
   258  func (ps Presubmit) ContextRequired() bool {
   259  	if ps.Optional || ps.SkipReport {
   260  		return false
   261  	}
   262  	return true
   263  }
   264  
   265  // ChangedFilesProvider returns a slice of modified files.
   266  type ChangedFilesProvider func() ([]string, error)
   267  
   268  func matching(j Presubmit, body string, testAll bool) []Presubmit {
   269  	// When matching ignore whether the job runs for the branch or whether the job runs for the
   270  	// PR's changes. Even if the job doesn't run, it still matches the PR and may need to be marked
   271  	// as skipped on github.
   272  	var result []Presubmit
   273  	if (testAll && (j.AlwaysRun || j.RunIfChanged != "")) || j.TriggerMatches(body) {
   274  		result = append(result, j)
   275  	}
   276  	for _, child := range j.RunAfterSuccess {
   277  		result = append(result, matching(child, body, testAll)...)
   278  	}
   279  	return result
   280  }
   281  
   282  // MatchingPresubmits returns a slice of presubmits to trigger based on the repo and a comment text.
   283  func (c *JobConfig) MatchingPresubmits(fullRepoName, body string, testAll bool) []Presubmit {
   284  	var result []Presubmit
   285  	if jobs, ok := c.Presubmits[fullRepoName]; ok {
   286  		for _, job := range jobs {
   287  			result = append(result, matching(job, body, testAll)...)
   288  		}
   289  	}
   290  	return result
   291  }
   292  
   293  // UtilityConfig holds decoration metadata, such as how to clone and additional containers/etc
   294  type UtilityConfig struct {
   295  	// Decorate determines if we decorate the PodSpec or not
   296  	Decorate bool `json:"decorate,omitempty"`
   297  
   298  	// PathAlias is the location under <root-dir>/src
   299  	// where the repository under test is cloned. If this
   300  	// is not set, <root-dir>/src/github.com/org/repo will
   301  	// be used as the default.
   302  	PathAlias string `json:"path_alias,omitempty"`
   303  	// CloneURI is the URI that is used to clone the
   304  	// repository. If unset, will default to
   305  	// `https://github.com/org/repo.git`.
   306  	CloneURI string `json:"clone_uri,omitempty"`
   307  
   308  	// ExtraRefs are auxiliary repositories that
   309  	// need to be cloned, determined from config
   310  	ExtraRefs []*kube.Refs `json:"extra_refs,omitempty"`
   311  
   312  	// DecorationConfig holds configuration options for
   313  	// decorating PodSpecs that users provide
   314  	*kube.DecorationConfig
   315  }
   316  
   317  // RetestPresubmits returns all presubmits that should be run given a /retest command.
   318  // This is the set of all presubmits intersected with ((alwaysRun + runContexts) - skipContexts)
   319  func (c *JobConfig) RetestPresubmits(fullRepoName string, skipContexts, runContexts map[string]bool) []Presubmit {
   320  	var result []Presubmit
   321  	if jobs, ok := c.Presubmits[fullRepoName]; ok {
   322  		for _, job := range jobs {
   323  			if skipContexts[job.Context] {
   324  				continue
   325  			}
   326  			if job.AlwaysRun || job.RunIfChanged != "" || runContexts[job.Context] {
   327  				result = append(result, job)
   328  			}
   329  		}
   330  	}
   331  	return result
   332  }
   333  
   334  // GetPresubmit returns the presubmit job for the provided repo and job name.
   335  func (c *JobConfig) GetPresubmit(repo, jobName string) *Presubmit {
   336  	presubmits := c.AllPresubmits([]string{repo})
   337  	for i := range presubmits {
   338  		ps := presubmits[i]
   339  		if ps.Name == jobName {
   340  			return &ps
   341  		}
   342  	}
   343  	return nil
   344  }
   345  
   346  // SetPresubmits updates c.Presubmits to jobs, after compiling and validing their regexes.
   347  func (c *JobConfig) SetPresubmits(jobs map[string][]Presubmit) error {
   348  	nj := map[string][]Presubmit{}
   349  	for k, v := range jobs {
   350  		nj[k] = make([]Presubmit, len(v))
   351  		copy(nj[k], v)
   352  		if err := SetPresubmitRegexes(nj[k]); err != nil {
   353  			return err
   354  		}
   355  	}
   356  	c.Presubmits = nj
   357  	return nil
   358  }
   359  
   360  // listPresubmits list all the presubmit for a given repo including the run after success jobs.
   361  func listPresubmits(ps []Presubmit) []Presubmit {
   362  	var res []Presubmit
   363  	for _, p := range ps {
   364  		res = append(res, p)
   365  		res = append(res, listPresubmits(p.RunAfterSuccess)...)
   366  	}
   367  	return res
   368  }
   369  
   370  // AllPresubmits returns all prow presubmit jobs in repos.
   371  // if repos is empty, return all presubmits.
   372  func (c *JobConfig) AllPresubmits(repos []string) []Presubmit {
   373  	var res []Presubmit
   374  
   375  	for repo, v := range c.Presubmits {
   376  		if len(repos) == 0 {
   377  			res = append(res, listPresubmits(v)...)
   378  		} else {
   379  			for _, r := range repos {
   380  				if r == repo {
   381  					res = append(res, listPresubmits(v)...)
   382  					break
   383  				}
   384  			}
   385  		}
   386  	}
   387  
   388  	return res
   389  }
   390  
   391  // listPostsubmits list all the postsubmits for a given repo including the run after success jobs.
   392  func listPostsubmits(ps []Postsubmit) []Postsubmit {
   393  	var res []Postsubmit
   394  	for _, p := range ps {
   395  		res = append(res, p)
   396  		res = append(res, listPostsubmits(p.RunAfterSuccess)...)
   397  	}
   398  	return res
   399  }
   400  
   401  // AllPostsubmits returns all prow postsubmit jobs in repos.
   402  // if repos is empty, return all postsubmits.
   403  func (c *JobConfig) AllPostsubmits(repos []string) []Postsubmit {
   404  	var res []Postsubmit
   405  
   406  	for repo, v := range c.Postsubmits {
   407  		if len(repos) == 0 {
   408  			res = append(res, listPostsubmits(v)...)
   409  		} else {
   410  			for _, r := range repos {
   411  				if r == repo {
   412  					res = append(res, listPostsubmits(v)...)
   413  					break
   414  				}
   415  			}
   416  		}
   417  	}
   418  
   419  	return res
   420  }
   421  
   422  // AllPeriodics returns all prow periodic jobs.
   423  func (c *JobConfig) AllPeriodics() []Periodic {
   424  	var listPeriodic func(ps []Periodic) []Periodic
   425  	listPeriodic = func(ps []Periodic) []Periodic {
   426  		var res []Periodic
   427  		for _, p := range ps {
   428  			res = append(res, p)
   429  			res = append(res, listPeriodic(p.RunAfterSuccess)...)
   430  		}
   431  		return res
   432  	}
   433  
   434  	return listPeriodic(c.Periodics)
   435  }