config.go 40 KB

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