From 98200444e82e5802c39a230479065b0e2791c68d Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 19 Sep 2018 16:25:08 +0200 Subject: [PATCH] Read directly from prow config files instead of our own Prow must have jobs defined for some integrations (like Deck) to properly show the jobs created here. The release-controller will directly reference the prow jobs. --- cmd/release-controller/main.go | 9 +++-- cmd/release-controller/sync.go | 36 +++++++++++++---- pkg/prow/apiv1/agent.go | 38 ++++++++++------- pkg/prow/apiv1/types.go | 31 +++++++++++++- test/testdata/jobs.yaml | 74 +++++++++++++--------------------- test/testdata/prow.yaml | 16 ++++++++ 6 files changed, 132 insertions(+), 72 deletions(-) create mode 100644 test/testdata/prow.yaml diff --git a/cmd/release-controller/main.go b/cmd/release-controller/main.go index e784bb186..4c27d5e9b 100644 --- a/cmd/release-controller/main.go +++ b/cmd/release-controller/main.go @@ -36,7 +36,9 @@ type options struct { ReleaseNamespace string JobNamespace string ProwNamespace string - ProwConfigPath string + + ProwConfigPath string + JobConfigPath string } func main() { @@ -56,7 +58,8 @@ func main() { flag.StringVar(&opt.JobNamespace, "job-namespace", opt.JobNamespace, "The namespace to execute jobs and hold temporary objects.") flag.StringVar(&opt.ReleaseNamespace, "release-namespace", opt.ReleaseNamespace, "The namespace where the releases will be published to.") flag.StringVar(&opt.ProwNamespace, "prow-namespace", opt.ProwNamespace, "The namespace where the Prow jobs will be created (defaults to --job-namespace).") - flag.StringVar(&opt.ProwConfigPath, "prow-config", opt.ProwConfigPath, "A config file containing the jobs to run against releases.") + flag.StringVar(&opt.ProwConfigPath, "prow-config", opt.ProwConfigPath, "A config file containing the prow configuration.") + flag.StringVar(&opt.JobConfigPath, "job-config", opt.JobConfigPath, "A config file containing the jobs to run against releases.") flag.AddGoFlag(original.Lookup("v")) if err := cmd.Execute(); err != nil { @@ -112,7 +115,7 @@ func (o *options) Run() error { configAgent := &prowapiv1.Agent{} if len(o.ProwConfigPath) > 0 { - if err := configAgent.Start(o.ProwConfigPath); err != nil { + if err := configAgent.Start(o.ProwConfigPath, o.JobConfigPath); err != nil { return err } } diff --git a/cmd/release-controller/sync.go b/cmd/release-controller/sync.go index 99c3132af..b988617a0 100644 --- a/cmd/release-controller/sync.go +++ b/cmd/release-controller/sync.go @@ -144,13 +144,14 @@ func (c *Controller) ensureProwJobForReleaseTag(release *Release, name, jobName c.eventRecorder.Event(release.Source, corev1.EventTypeWarning, "ProwJobInvalid", err.Error()) return nil, terminalError{err} } - prowSpec, ok := hasProwJob(config, jobName) + periodicConfig, ok := hasProwJob(config, jobName) if !ok { err := fmt.Errorf("the prow job %s is not valid: no job with that name", jobName) c.eventRecorder.Eventf(release.Source, corev1.EventTypeWarning, "ProwJobInvalid", err.Error()) return nil, terminalError{err} } - spec := prowSpec.DeepCopy() + + spec := prowSpecForPeriodicConfig(periodicConfig, config.Plank.DefaultDecorationConfig) mirror, _ := c.imageStreamLister.ImageStreams(c.jobNamespace).Get(releaseTag.Name) if err := addReleaseEnvToProwJobSpec(spec, release, mirror, releaseTag); err != nil { return nil, err @@ -738,7 +739,7 @@ func addReleaseEnvToProwJobSpec(spec *prowapiv1.ProwJobSpec, release *Release, m for j := range c.Env { switch name := c.Env[j].Name; { case name == "RELEASE_IMAGE_LATEST": - c.Env[j].Value = release.Target.Status.PublicDockerImageRepository + releaseTag.Name + c.Env[j].Value = release.Target.Status.PublicDockerImageRepository + ":" + releaseTag.Name case name == "IMAGE_FORMAT": if mirror == nil { return fmt.Errorf("unable to determine IMAGE_FORMAT for prow job %s", spec.Job) @@ -760,10 +761,10 @@ func addReleaseEnvToProwJobSpec(spec *prowapiv1.ProwJobSpec, release *Release, m return nil } -func hasProwJob(config *prowapiv1.Config, name string) (*prowapiv1.ProwJobSpec, bool) { - for i := range config.Jobs { - if config.Jobs[i].Job == name { - return &config.Jobs[i], true +func hasProwJob(config *prowapiv1.Config, name string) (*prowapiv1.PeriodicConfig, bool) { + for i := range config.Periodics { + if config.Periodics[i].Name == name { + return &config.Periodics[i], true } } return nil, false @@ -792,3 +793,24 @@ func toJSONString(data interface{}) string { } return string(out) } + +func prowSpecForPeriodicConfig(config *prowapiv1.PeriodicConfig, decorationConfig *prowapiv1.DecorationConfig) *prowapiv1.ProwJobSpec { + spec := &prowapiv1.ProwJobSpec{ + Type: prowapiv1.PeriodicJob, + Job: config.Name, + Agent: prowapiv1.KubernetesAgent, + + Refs: &prowapiv1.Refs{}, + + PodSpec: config.Spec.DeepCopy(), + } + + if decorationConfig != nil { + spec.DecorationConfig = decorationConfig.DeepCopy() + } else { + spec.DecorationConfig = &prowapiv1.DecorationConfig{} + } + spec.DecorationConfig.SkipCloning = true + + return spec +} diff --git a/pkg/prow/apiv1/agent.go b/pkg/prow/apiv1/agent.go index fac9cabec..32411de4b 100644 --- a/pkg/prow/apiv1/agent.go +++ b/pkg/prow/apiv1/agent.go @@ -28,18 +28,18 @@ import ( "github.com/golang/glog" ) -type Config struct { - Jobs []ProwJobSpec `json:"jobs"` -} - -func Load(path string) (*Config, error) { - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } +// Load reads the decoration_config from prowConfigPath and periodics from +// the provided job config, or returns an error. +func Load(prowConfigPath, jobConfigPath string) (*Config, error) { c := &Config{} - if err := yaml.Unmarshal(data, c); err != nil { - return nil, err + for _, path := range []string{prowConfigPath, jobConfigPath} { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + if err := yaml.Unmarshal(data, c); err != nil { + return nil, err + } } return c, nil } @@ -54,8 +54,8 @@ type Agent struct { // Start will begin polling the config file at the path. If the first load // fails, Start with return the error and abort. Future load failures will log // the failure message but continue attempting to load. -func (ca *Agent) Start(jobConfig string) error { - c, err := Load(jobConfig) +func (ca *Agent) Start(prowConfig, jobConfig string) error { + c, err := Load(prowConfig, jobConfig) if err != nil { return err } @@ -78,13 +78,23 @@ func (ca *Agent) Start(jobConfig string) error { recentModTime := prowStat.ModTime() + jobConfigStat, err := os.Stat(jobConfig) + if err != nil { + glog.Errorf("Error loading job config: %v", err) + continue + } + + if jobConfigStat.ModTime().After(recentModTime) { + recentModTime = jobConfigStat.ModTime() + } + if !recentModTime.After(lastModTime) { skips++ continue // file hasn't been modified } lastModTime = recentModTime } - if c, err := Load(jobConfig); err != nil { + if c, err := Load(prowConfig, jobConfig); err != nil { glog.Errorf("Error loading config: %v", err) } else { skips = 0 diff --git a/pkg/prow/apiv1/types.go b/pkg/prow/apiv1/types.go index 37344c5b6..930fdb9f9 100644 --- a/pkg/prow/apiv1/types.go +++ b/pkg/prow/apiv1/types.go @@ -27,6 +27,35 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// Config loads a subset of the Prow config definition. +type Config struct { + // Plank is the default plank configuration. + Plank PlankConfig `json:"plank"` + + // Periodics uses the definitions of periodic jobs to invoke + // special actions. Only a subset of fields is supported. + Periodics []PeriodicConfig `json:"periodics"` +} + +type PlankConfig struct { + // DecorationConfig holds configuration options for + // decorating PodSpecs that users provide. + DefaultDecorationConfig *DecorationConfig `json:"default_decoration_config,omitempty"` +} + +// PeriodicConfig is a subset of the configuration of a periodic job. +type PeriodicConfig struct { + Name string `json:"name"` + // Labels are added in prowjobs created for this job. + Labels map[string]string `json:"labels,omitempty"` + // Agent that will take care of running this job. + Agent string `json:"agent"` + // Kubernetes pod spec. + Spec *corev1.PodSpec `json:"spec,omitempty"` + // Tags for config entries + Tags []string `json:"tags,omitempty"` +} + // ProwJobType specifies how the job is triggered. type ProwJobType string @@ -131,8 +160,6 @@ type ProwJobSpec struct { // a Kubernetes agent PodSpec *corev1.PodSpec `json:"pod_spec,omitempty"` - Decorate bool `json:"decorate,omitempty"` - // DecorationConfig holds configuration options for // decorating PodSpecs that users provide DecorationConfig *DecorationConfig `json:"decoration_config,omitempty"` diff --git a/test/testdata/jobs.yaml b/test/testdata/jobs.yaml index f708d65bf..9d59294aa 100644 --- a/test/testdata/jobs.yaml +++ b/test/testdata/jobs.yaml @@ -1,56 +1,38 @@ -jobs: -- job: release-openshift-origin-installer-e2e-aws-master - type: periodic +periodics: +- name: release-openshift-origin-installer-e2e-aws-master agent: kubernetes - cluster: default - refs: {} - decorate: true - decoration_config: - gcs_configuration: - bucket: origin-ci-test - default_org: openshift - default_repo: origin - path_strategy: single - gcs_credentials_secret: gcs-publisher-credentials - grace_period: 15000000000 - skip_cloning: true - timeout: 14400000000000 - utility_images: - clonerefs: clonerefs:latest - entrypoint: entrypoint:latest - initupload: initupload:latest - sidecar: sidecar:latest - pod_spec: + # TODO: set arbitarily high so as not to be run without our parameters, + # add a new job type to test-infra for this use case or allow periodics to + # be set to disabled. + cron: "@yearly" + spec: containers: - name: echo image: centos:7 + # These environment parameters are replaced by the release-controller dynamically + env: + - name: RELEASE_IMAGE_LATEST + value: registry.svc.ci.openshift.org/openshift/release:v4.0 + - name: IMAGE_FORMAT + value: registry.svc.ci.openshift.org/openshift/origin-v4.0:${component} + - name: RPM_REPO + value: https://rpms.svc.ci.openshift.org/openshift-origin-v4.0/ command: - - echo - - "SUCCESS" -- job: release-openshift-origin-installer-e2e-gcp-master - type: periodic + - env +- name: release-openshift-origin-installer-e2e-gcp-master agent: kubernetes - cluster: default - refs: {} - decorate: true - decoration_config: - gcs_configuration: - bucket: origin-ci-test - default_org: openshift - default_repo: origin - path_strategy: single - gcs_credentials_secret: gcs-publisher-credentials - grace_period: 15000000000 - skip_cloning: true - timeout: 14400000000000 - utility_images: - clonerefs: clonerefs:latest - entrypoint: entrypoint:latest - initupload: initupload:latest - sidecar: sidecar:latest - pod_spec: + cron: "@yearly" + spec: containers: - name: echo image: centos:7 + # These environment parameters are replaced by the release-controller dynamically + env: + - name: RELEASE_IMAGE_LATEST + value: registry.svc.ci.openshift.org/openshift/release:v4.0 + - name: IMAGE_FORMAT + value: registry.svc.ci.openshift.org/openshift/origin-v4.0:${component} + - name: RPM_REPO + value: https://rpms.svc.ci.openshift.org/openshift-origin-v4.0/ command: - - /bin/false + - env diff --git a/test/testdata/prow.yaml b/test/testdata/prow.yaml new file mode 100644 index 000000000..136fe8209 --- /dev/null +++ b/test/testdata/prow.yaml @@ -0,0 +1,16 @@ +plank: + default_decoration_config: + gcs_configuration: + bucket: origin-ci-test + default_org: openshift + default_repo: origin + path_strategy: single + gcs_credentials_secret: gcs-publisher-credentials + grace_period: 15000000000 + skip_cloning: true + timeout: 14400000000000 + utility_images: + clonerefs: clonerefs:latest + entrypoint: entrypoint:latest + initupload: initupload:latest + sidecar: sidecar:latest