github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/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 package config 19 20 import ( 21 "fmt" 22 "io/ioutil" 23 "regexp" 24 "text/template" 25 "time" 26 27 "github.com/ghodss/yaml" 28 "github.com/sirupsen/logrus" 29 30 "k8s.io/test-infra/prow/kube" 31 ) 32 33 // Config is a read-only snapshot of the config. 34 type Config struct { 35 // Full repo name (such as "kubernetes/kubernetes") -> list of jobs. 36 Presubmits map[string][]Presubmit `json:"presubmits,omitempty"` 37 Postsubmits map[string][]Postsubmit `json:"postsubmits,omitempty"` 38 39 // Periodics are not associated with any repo. 40 Periodics []Periodic `json:"periodics,omitempty"` 41 42 Tide Tide `json:"tide,omitempty"` 43 Plank Plank `json:"plank,omitempty"` 44 Sinker Sinker `json:"sinker,omitempty"` 45 46 // TODO: Move this out of the main config. 47 JenkinsOperator JenkinsOperator `json:"jenkins_operator,omitempty"` 48 49 // ProwJobNamespace is the namespace in the cluster that prow 50 // components will use for looking up ProwJobs. The namespace 51 // needs to exist and will not be created by prow. 52 // Defaults to "default". 53 ProwJobNamespace string `json:"prowjob_namespace,omitempty"` 54 // PodNamespace is the namespace in the cluster that prow 55 // components will use for looking up Pods owned by ProwJobs. 56 // The namespace needs to exist and will not be created by prow. 57 // Defaults to "default". 58 PodNamespace string `json:"pod_namespace,omitempty"` 59 60 // LogLevel enables dynamically updating the log level of the 61 // standard logger that is used by all prow components. 62 // 63 // Valid values: 64 // 65 // "debug", "info", "warn", "warning", "error", "fatal", "panic" 66 // 67 // Defaults to "info". 68 LogLevel string `json:"log_level,omitempty"` 69 70 // PushGateway is a prometheus push gateway. 71 PushGateway PushGateway `json:"push_gateway,omitempty"` 72 } 73 74 type PushGateway struct { 75 Endpoint string `json:"endpoint,omitempty"` 76 } 77 78 // Tide is config for the tide pool. 79 type Tide struct { 80 // These must be valid GitHub search queries. They should not overlap, 81 // which is to say two queries should never return the same PR. 82 Queries []string `json:"queries,omitempty"` 83 } 84 85 // Plank is config for the plank controller. 86 type Plank struct { 87 // JobURLTemplateString compiles into JobURLTemplate at load time. 88 JobURLTemplateString string `json:"job_url_template,omitempty"` 89 // JobURLTemplate is compiled at load time from JobURLTemplateString. It 90 // will be passed a kube.ProwJob and is used to set the URL for the 91 // "details" link on GitHub as well as the link from deck. 92 JobURLTemplate *template.Template `json:"-"` 93 94 // ReportTemplateString compiles into ReportTemplate at load time. 95 ReportTemplateString string `json:"report_template,omitempty"` 96 // ReportTemplate is compiled at load time from ReportTemplateString. It 97 // will be passed a kube.ProwJob and can provide an optional blurb below 98 // the test failures comment. 99 ReportTemplate *template.Template `json:"-"` 100 101 // MaxConcurrency is the maximum number of tests running concurrently that 102 // will be allowed by plank. 0 implies no limit. 103 MaxConcurrency int `json:"max_concurrency,omitempty"` 104 105 // AllowCancellations enables aborting presubmit jobs for commits that 106 // have been superseded by newer commits in Github pull requests. 107 AllowCancellations bool `json:"allow_cancellations"` 108 } 109 110 // JenkinsOperator is config for the jenkins-operator controller. 111 type JenkinsOperator struct { 112 // JobURLTemplateString compiles into JobURLTemplate at load time. 113 JobURLTemplateString string `json:"job_url_template,omitempty"` 114 // JobURLTemplate is compiled at load time from JobURLTemplateString. It 115 // will be passed a kube.ProwJob and is used to set the URL for the 116 // "details" link on GitHub as well as the link from deck. 117 JobURLTemplate *template.Template `json:"-"` 118 119 // ReportTemplateString compiles into ReportTemplate at load time. 120 ReportTemplateString string `json:"report_template,omitempty"` 121 // ReportTemplate is compiled at load time from ReportTemplateString. It 122 // will be passed a kube.ProwJob and can provide an optional blurb below 123 // the test failures comment. 124 ReportTemplate *template.Template `json:"-"` 125 126 // MaxConcurrency is the maximum number of tests running concurrently that 127 // will be allowed by jenkins-operator. 0 implies no limit. 128 MaxConcurrency int `json:"max_concurrency,omitempty"` 129 130 // AllowCancellations enables aborting presubmit jobs for commits that 131 // have been superseded by newer commits in Github pull requests. 132 AllowCancellations bool `json:"allow_cancellations"` 133 } 134 135 // Sinker is config for the sinker controller. 136 type Sinker struct { 137 // ResyncPeriodString compiles into ResyncPeriod at load time. 138 ResyncPeriodString string `json:"resync_period,omitempty"` 139 // ResyncPeriod is how often the controller will perform a garbage 140 // collection. Defaults to one hour. 141 ResyncPeriod time.Duration `json:"-"` 142 // MaxProwJobAgeString compiles into MaxProwJobAge at load time. 143 MaxProwJobAgeString string `json:"max_prowjob_age,omitempty"` 144 // MaxProwJobAge is how old a ProwJob can be before it is garbage-collected. 145 // Defaults to one week. 146 MaxProwJobAge time.Duration `json:"-"` 147 // MaxPodAgeString compiles into MaxPodAge at load time. 148 MaxPodAgeString string `json:"max_pod_age,omitempty"` 149 // MaxPodAge is how old a Pod can be before it is garbage-collected. 150 // Defaults to one day. 151 MaxPodAge time.Duration `json:"-"` 152 } 153 154 // Load loads and parses the config at path. 155 func Load(path string) (*Config, error) { 156 b, err := ioutil.ReadFile(path) 157 if err != nil { 158 return nil, fmt.Errorf("error reading %s: %v", path, err) 159 } 160 nc := &Config{} 161 if err := yaml.Unmarshal(b, nc); err != nil { 162 return nil, fmt.Errorf("error unmarshaling %s: %v", path, err) 163 } 164 if err := parseConfig(nc); err != nil { 165 return nil, err 166 } 167 return nc, nil 168 } 169 170 func parseConfig(c *Config) error { 171 // Ensure that presubmit regexes are valid. 172 for _, vs := range c.Presubmits { 173 if err := SetRegexes(vs); err != nil { 174 return fmt.Errorf("could not set regex: %v", err) 175 } 176 } 177 178 // Validate presubmits. 179 for _, v := range c.AllPresubmits(nil) { 180 name := v.Name 181 agent := v.Agent 182 // Ensure that k8s presubmits have a pod spec. 183 if agent == string(kube.KubernetesAgent) && v.Spec == nil { 184 return fmt.Errorf("job %s has no spec", name) 185 } 186 // Ensure agent is a known value. 187 if agent != string(kube.KubernetesAgent) && agent != string(kube.JenkinsAgent) { 188 return fmt.Errorf("job %s has invalid agent (%s), it needs to be one of the following: %s %s", 189 name, agent, kube.KubernetesAgent, kube.JenkinsAgent) 190 } 191 // Ensure max_concurrency is non-negative. 192 if v.MaxConcurrency < 0 { 193 return fmt.Errorf("job %s jas invalid max_concurrency (%d), it needs to be a non-negative number", name, v.MaxConcurrency) 194 } 195 } 196 197 // Validate postsubmits. 198 for _, j := range c.AllPostsubmits(nil) { 199 name := j.Name 200 agent := j.Agent 201 // Ensure that k8s postsubmits have a pod spec. 202 if agent == string(kube.KubernetesAgent) && j.Spec == nil { 203 return fmt.Errorf("job %s has no spec", name) 204 } 205 // Ensure agent is a known value. 206 if agent != string(kube.KubernetesAgent) && agent != string(kube.JenkinsAgent) { 207 return fmt.Errorf("job %s has invalid agent (%s), it needs to be one of the following: %s %s", 208 name, agent, kube.KubernetesAgent, kube.JenkinsAgent) 209 } 210 // Ensure max_concurrency is non-negative. 211 if j.MaxConcurrency < 0 { 212 return fmt.Errorf("job %s jas invalid max_concurrency (%d), it needs to be a non-negative number", name, j.MaxConcurrency) 213 } 214 } 215 216 // Ensure that the periodic durations are valid and specs exist. 217 for _, p := range c.AllPeriodics() { 218 name := p.Name 219 agent := p.Agent 220 if agent == string(kube.KubernetesAgent) && p.Spec == nil { 221 return fmt.Errorf("job %s has no spec", name) 222 } 223 if agent != string(kube.KubernetesAgent) && agent != string(kube.JenkinsAgent) { 224 return fmt.Errorf("job %s has invalid agent (%s), it needs to be one of the following: %s %s", 225 name, agent, kube.KubernetesAgent, kube.JenkinsAgent) 226 } 227 } 228 // Set the interval on the periodic jobs. It doesn't make sense to do this 229 // for child jobs. 230 for j := range c.Periodics { 231 d, err := time.ParseDuration(c.Periodics[j].Interval) 232 if err != nil { 233 return fmt.Errorf("cannot parse duration for %s: %v", c.Periodics[j].Name, err) 234 } 235 c.Periodics[j].interval = d 236 } 237 238 urlTmpl, err := template.New("JobURL").Parse(c.Plank.JobURLTemplateString) 239 if err != nil { 240 return fmt.Errorf("parsing template: %v", err) 241 } 242 c.Plank.JobURLTemplate = urlTmpl 243 244 reportTmpl, err := template.New("Report").Parse(c.Plank.ReportTemplateString) 245 if err != nil { 246 return fmt.Errorf("parsing template: %v", err) 247 } 248 c.Plank.ReportTemplate = reportTmpl 249 if c.Plank.MaxConcurrency < 0 { 250 return fmt.Errorf("plank has invalid max_concurrency (%d), it needs to be a non-negative number", c.Plank.MaxConcurrency) 251 } 252 253 jenkinsURLTmpl, err := template.New("JobURL").Parse(c.JenkinsOperator.JobURLTemplateString) 254 if err != nil { 255 return fmt.Errorf("parsing template: %v", err) 256 } 257 c.JenkinsOperator.JobURLTemplate = jenkinsURLTmpl 258 259 jenkinsReportTmpl, err := template.New("Report").Parse(c.JenkinsOperator.ReportTemplateString) 260 if err != nil { 261 return fmt.Errorf("parsing template: %v", err) 262 } 263 c.JenkinsOperator.ReportTemplate = jenkinsReportTmpl 264 if c.JenkinsOperator.MaxConcurrency < 0 { 265 return fmt.Errorf("jenkins-operator has invalid max_concurrency (%d), it needs to be a non-negative number", c.JenkinsOperator.MaxConcurrency) 266 } 267 268 if c.Sinker.ResyncPeriodString == "" { 269 c.Sinker.ResyncPeriod = time.Hour 270 } else { 271 resyncPeriod, err := time.ParseDuration(c.Sinker.ResyncPeriodString) 272 if err != nil { 273 return fmt.Errorf("cannot parse duration for resync_period: %v", err) 274 } 275 c.Sinker.ResyncPeriod = resyncPeriod 276 } 277 278 if c.Sinker.MaxProwJobAgeString == "" { 279 c.Sinker.MaxProwJobAge = 7 * 24 * time.Hour 280 } else { 281 maxProwJobAge, err := time.ParseDuration(c.Sinker.MaxProwJobAgeString) 282 if err != nil { 283 return fmt.Errorf("cannot parse duration for max_prowjob_age: %v", err) 284 } 285 c.Sinker.MaxProwJobAge = maxProwJobAge 286 } 287 288 if c.Sinker.MaxPodAgeString == "" { 289 c.Sinker.MaxPodAge = 24 * time.Hour 290 } else { 291 maxPodAge, err := time.ParseDuration(c.Sinker.MaxPodAgeString) 292 if err != nil { 293 return fmt.Errorf("cannot parse duration for max_pod_age: %v", err) 294 } 295 c.Sinker.MaxPodAge = maxPodAge 296 } 297 298 if c.ProwJobNamespace == "" { 299 c.ProwJobNamespace = "default" 300 } 301 if c.PodNamespace == "" { 302 c.PodNamespace = "default" 303 } 304 305 if c.LogLevel == "" { 306 c.LogLevel = "info" 307 } 308 lvl, err := logrus.ParseLevel(c.LogLevel) 309 if err != nil { 310 return err 311 } 312 logrus.SetLevel(lvl) 313 314 return nil 315 } 316 317 // SetRegexes compiles and validates all the regural expressions for 318 // the provided presubmits. 319 func SetRegexes(js []Presubmit) error { 320 for i, j := range js { 321 if re, err := regexp.Compile(j.Trigger); err == nil { 322 js[i].re = re 323 } else { 324 return fmt.Errorf("could not compile trigger regex for %s: %v", j.Name, err) 325 } 326 if !js[i].re.MatchString(j.RerunCommand) { 327 return fmt.Errorf("for job %s, rerun command \"%s\" does not match trigger \"%s\"", j.Name, j.RerunCommand, j.Trigger) 328 } 329 if err := SetRegexes(j.RunAfterSuccess); err != nil { 330 return err 331 } 332 if j.RunIfChanged != "" { 333 re, err := regexp.Compile(j.RunIfChanged) 334 if err != nil { 335 return fmt.Errorf("could not compile changes regex for %s: %v", j.Name, err) 336 } 337 js[i].reChanges = re 338 } 339 } 340 return nil 341 }