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 }