github.com/abayer/test-infra@v0.0.5/prow/pjutil/pjutil.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 pjutil contains helpers for working with ProwJobs. 18 package pjutil 19 20 import ( 21 "path/filepath" 22 "strconv" 23 24 "github.com/satori/go.uuid" 25 "github.com/sirupsen/logrus" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/validation" 28 buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1" 29 30 "k8s.io/test-infra/prow/config" 31 "k8s.io/test-infra/prow/github" 32 "k8s.io/test-infra/prow/kube" 33 "k8s.io/api/core/v1" 34 "fmt" 35 ) 36 37 const ( 38 jobNameLabel = "prow.k8s.io/job" 39 jobTypeLabel = "prow.k8s.io/type" 40 orgLabel = "prow.k8s.io/refs.org" 41 repoLabel = "prow.k8s.io/refs.repo" 42 pullLabel = "prow.k8s.io/refs.pull" 43 44 45 // JobSpecEnv is the name that contains JobSpec marshaled into a string. 46 JobSpecEnv = "JOB_SPEC" 47 48 jobNameEnv = "JOB_NAME" 49 jobTypeEnv = "JOB_TYPE" 50 prowJobIDEnv = "PROW_JOB_ID" 51 52 buildIDEnv = "BUILD_ID" 53 prowBuildIDEnv = "BUILD_NUMBER" // Deprecated, will be removed in the future. 54 jenkinsBuildIDEnv = "buildId" // Deprecated, will be removed in the future. 55 56 repoOwnerEnv = "REPO_OWNER" 57 repoNameEnv = "REPO_NAME" 58 pullBaseRefEnv = "PULL_BASE_REF" 59 pullBaseShaEnv = "PULL_BASE_SHA" 60 pullRefsEnv = "PULL_REFS" 61 pullNumberEnv = "PULL_NUMBER" 62 pullPullShaEnv = "PULL_PULL_SHA" 63 cloneURI = "CLONE_URI" 64 65 // todo lets come up with better const names 66 jmbrBranchName = "BRANCH_NAME" 67 jmbrSourceURL = "SOURCE_URL" 68 ) 69 // NewProwJob initializes a ProwJob out of a ProwJobSpec. 70 func NewProwJob(spec kube.ProwJobSpec, labels map[string]string) kube.ProwJob { 71 allLabels := map[string]string{ 72 jobNameLabel: spec.Job, 73 jobTypeLabel: string(spec.Type), 74 } 75 if spec.Type != kube.PeriodicJob { 76 allLabels[orgLabel] = spec.Refs.Org 77 allLabels[repoLabel] = spec.Refs.Repo 78 if len(spec.Refs.Pulls) > 0 { 79 allLabels[pullLabel] = strconv.Itoa(spec.Refs.Pulls[0].Number) 80 } 81 } 82 for key, value := range labels { 83 allLabels[key] = value 84 } 85 86 // let's validate labels 87 for key, value := range allLabels { 88 if errs := validation.IsValidLabelValue(value); len(errs) > 0 { 89 // try to use basename of a path, if path contains invalid // 90 base := filepath.Base(value) 91 if errs := validation.IsValidLabelValue(base); len(errs) == 0 { 92 allLabels[key] = base 93 continue 94 } 95 delete(allLabels, key) 96 logrus.Warnf("Removing invalid label: key - %s, value - %s, error: %s", key, value, errs) 97 } 98 } 99 100 return kube.ProwJob{ 101 TypeMeta: metav1.TypeMeta{ 102 APIVersion: "prow.k8s.io/v1", 103 Kind: "ProwJob", 104 }, 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: uuid.NewV1().String(), 107 Labels: allLabels, 108 }, 109 Spec: spec, 110 Status: kube.ProwJobStatus{ 111 StartTime: metav1.Now(), 112 State: kube.TriggeredState, 113 }, 114 } 115 } 116 117 func NewPresubmit(pr github.PullRequest, baseSHA string, job config.Presubmit, eventGUID string) kube.ProwJob { 118 org := pr.Base.Repo.Owner.Login 119 repo := pr.Base.Repo.Name 120 number := pr.Number 121 kr := kube.Refs{ 122 Org: org, 123 Repo: repo, 124 BaseRef: pr.Base.Ref, 125 BaseSHA: baseSHA, 126 Pulls: []kube.Pull{ 127 { 128 Number: number, 129 Author: pr.User.Login, 130 SHA: pr.Head.SHA, 131 }, 132 }, 133 } 134 labels := make(map[string]string) 135 for k, v := range job.Labels { 136 labels[k] = v 137 } 138 labels[github.EventGUID] = eventGUID 139 return NewProwJob(PresubmitSpec(job, kr), labels) 140 } 141 142 // PresubmitSpec initializes a ProwJobSpec for a given presubmit job. 143 func PresubmitSpec(p config.Presubmit, refs kube.Refs) kube.ProwJobSpec { 144 refs.PathAlias = p.PathAlias 145 refs.CloneURI = p.CloneURI 146 147 pjs := kube.ProwJobSpec{ 148 Type: kube.PresubmitJob, 149 Job: p.Name, 150 Refs: &refs, 151 ExtraRefs: p.ExtraRefs, 152 153 Report: !p.SkipReport, 154 Context: p.Context, 155 RerunCommand: p.RerunCommand, 156 MaxConcurrency: p.MaxConcurrency, 157 158 DecorationConfig: p.DecorationConfig, 159 } 160 pjs.Agent = kube.ProwJobAgent(p.Agent) 161 if pjs.Agent == kube.KubernetesAgent { 162 pjs.PodSpec = p.Spec 163 pjs.Cluster = p.Cluster 164 if pjs.Cluster == "" { 165 pjs.Cluster = kube.DefaultClusterAlias 166 } 167 } 168 if pjs.Agent == kube.BuildAgent { 169 170 pjs.BuildSpec = p.BuildSpec 171 pjs.Cluster = p.Cluster 172 173 if pjs.Cluster == "" { 174 pjs.Cluster = kube.DefaultClusterAlias 175 } 176 interpolateEnvVars(&pjs, refs) 177 } 178 for _, nextP := range p.RunAfterSuccess { 179 pjs.RunAfterSuccess = append(pjs.RunAfterSuccess, PresubmitSpec(nextP, refs)) 180 } 181 return pjs 182 } 183 184 func interpolateEnvVars(pjs *kube.ProwJobSpec, refs kube.Refs) { 185 //todo lets clean this up 186 sourceURL := fmt.Sprintf("https://github.com/%s/%s.git",refs.Org,refs.Repo) 187 sourceSpec := buildv1alpha1.SourceSpec{Git: &buildv1alpha1.GitSourceSpec{Url:sourceURL}} 188 189 // todo taken from downwardapi.JobSpec, lets clean up the duplication 190 env := map[string]string{ 191 jobNameEnv: pjs.Job, 192 // TODO: figure out how to reliably get this even after pod restarts, we want the number to increase so maybe we 193 // TODO: need to think about using an external resource, kubernetes / git / some other to work out the next build # 194 //jmbrBuildNumber: "987654321", 195 //buildIDEnv: buildID, 196 //prowJobIDEnv: spec.ProwJobID, 197 jobTypeEnv: string(pjs.Type), 198 } 199 200 branchName := "" 201 if len(refs.Pulls) == 1 { 202 branchName = "PR-" + strconv.Itoa(refs.Pulls[0].Number) 203 } 204 205 // enrich with jenkins multi branch plugin env vars 206 env[jmbrBranchName] = branchName 207 env[jmbrSourceURL] = sourceURL 208 209 env[repoOwnerEnv] = refs.Org 210 env[repoNameEnv] = refs.Repo 211 env[pullBaseRefEnv] = refs.BaseRef 212 env[pullBaseShaEnv] = refs.BaseSHA 213 env[pullRefsEnv] = refs.String() 214 env[cloneURI] = refs.CloneURI 215 216 env[pullNumberEnv] = strconv.Itoa(refs.Pulls[0].Number) 217 env[pullPullShaEnv] = refs.Pulls[0].SHA 218 219 pjs.BuildSpec.Source = &sourceSpec 220 pjs.BuildSpec.Source.Git.Revision = refs.Pulls[0].SHA 221 222 for i, step := range pjs.BuildSpec.Steps { 223 if len(step.Env) == 0{ 224 step.Env = []v1.EnvVar{} 225 } 226 for k, v := range env { 227 e := v1.EnvVar{ 228 Name: k, 229 Value: v, 230 } 231 pjs.BuildSpec.Steps[i].Env = append(pjs.BuildSpec.Steps[i].Env, e) 232 } 233 } 234 } 235 236 // PostsubmitSpec initializes a ProwJobSpec for a given postsubmit job. 237 func PostsubmitSpec(p config.Postsubmit, refs kube.Refs) kube.ProwJobSpec { 238 refs.PathAlias = p.PathAlias 239 refs.CloneURI = p.CloneURI 240 pjs := kube.ProwJobSpec{ 241 Type: kube.PostsubmitJob, 242 Job: p.Name, 243 Refs: &refs, 244 ExtraRefs: p.ExtraRefs, 245 246 MaxConcurrency: p.MaxConcurrency, 247 248 DecorationConfig: p.DecorationConfig, 249 } 250 pjs.Agent = kube.ProwJobAgent(p.Agent) 251 if pjs.Agent == kube.KubernetesAgent { 252 pjs.PodSpec = p.Spec 253 pjs.Cluster = p.Cluster 254 if pjs.Cluster == "" { 255 pjs.Cluster = kube.DefaultClusterAlias 256 } 257 } 258 for _, nextP := range p.RunAfterSuccess { 259 pjs.RunAfterSuccess = append(pjs.RunAfterSuccess, PostsubmitSpec(nextP, refs)) 260 } 261 return pjs 262 } 263 264 // PeriodicSpec initializes a ProwJobSpec for a given periodic job. 265 func PeriodicSpec(p config.Periodic) kube.ProwJobSpec { 266 pjs := kube.ProwJobSpec{ 267 Type: kube.PeriodicJob, 268 Job: p.Name, 269 ExtraRefs: p.ExtraRefs, 270 271 DecorationConfig: p.DecorationConfig, 272 } 273 pjs.Agent = kube.ProwJobAgent(p.Agent) 274 if pjs.Agent == kube.KubernetesAgent { 275 pjs.PodSpec = p.Spec 276 pjs.Cluster = p.Cluster 277 if pjs.Cluster == "" { 278 pjs.Cluster = kube.DefaultClusterAlias 279 } 280 } 281 for _, nextP := range p.RunAfterSuccess { 282 pjs.RunAfterSuccess = append(pjs.RunAfterSuccess, PeriodicSpec(nextP)) 283 } 284 return pjs 285 } 286 287 // BatchSpec initializes a ProwJobSpec for a given batch job and ref spec. 288 func BatchSpec(p config.Presubmit, refs kube.Refs) kube.ProwJobSpec { 289 refs.PathAlias = p.PathAlias 290 refs.CloneURI = p.CloneURI 291 pjs := kube.ProwJobSpec{ 292 Type: kube.BatchJob, 293 Job: p.Name, 294 Refs: &refs, 295 ExtraRefs: p.ExtraRefs, 296 Context: p.Context, 297 298 DecorationConfig: p.DecorationConfig, 299 } 300 pjs.Agent = kube.ProwJobAgent(p.Agent) 301 if pjs.Agent == kube.KubernetesAgent { 302 pjs.PodSpec = p.Spec 303 pjs.Cluster = p.Cluster 304 if pjs.Cluster == "" { 305 pjs.Cluster = kube.DefaultClusterAlias 306 } 307 } 308 if pjs.Agent == kube.BuildAgent { 309 pjs.BuildSpec = p.BuildSpec 310 pjs.Cluster = p.Cluster 311 if pjs.Cluster == "" { 312 pjs.Cluster = kube.DefaultClusterAlias 313 } 314 315 sourceSpec := buildv1alpha1.SourceSpec{Git: &buildv1alpha1.GitSourceSpec{Url:"https://github.com/" + refs.Org + "/" + refs.Repo + ".git"}} 316 317 318 pjs.BuildSpec.Source = &sourceSpec 319 320 //pjs.BuildSpec.Source.Git.Url = "https://github.com/" + refs.Org + "/" + refs.Repo + ".git" 321 pjs.BuildSpec.Source.Git.Revision = refs.Pulls[0].SHA 322 } 323 for _, nextP := range p.RunAfterSuccess { 324 pjs.RunAfterSuccess = append(pjs.RunAfterSuccess, BatchSpec(nextP, refs)) 325 } 326 return pjs 327 } 328 329 // PartitionActive separates the provided prowjobs into pending and triggered 330 // and returns them inside channels so that they can be consumed in parallel 331 // by different goroutines. Complete prowjobs are filtered out. Controller 332 // loops need to handle pending jobs first so they can conform to maximum 333 // concurrency requirements that different jobs may have. 334 func PartitionActive(pjs []kube.ProwJob) (pending, triggered chan kube.ProwJob) { 335 // Size channels correctly. 336 pendingCount, triggeredCount := 0, 0 337 for _, pj := range pjs { 338 switch pj.Status.State { 339 case kube.PendingState: 340 pendingCount++ 341 case kube.TriggeredState: 342 triggeredCount++ 343 } 344 } 345 pending = make(chan kube.ProwJob, pendingCount) 346 triggered = make(chan kube.ProwJob, triggeredCount) 347 348 // Partition the jobs into the two separate channels. 349 for _, pj := range pjs { 350 switch pj.Status.State { 351 case kube.PendingState: 352 pending <- pj 353 case kube.TriggeredState: 354 triggered <- pj 355 } 356 } 357 close(pending) 358 close(triggered) 359 return pending, triggered 360 } 361 362 // GetLatestProwJobs filters through the provided prowjobs and returns 363 // a map of jobType jobs to their latest prowjobs. 364 func GetLatestProwJobs(pjs []kube.ProwJob, jobType kube.ProwJobType) map[string]kube.ProwJob { 365 latestJobs := make(map[string]kube.ProwJob) 366 for _, j := range pjs { 367 if j.Spec.Type != jobType { 368 continue 369 } 370 name := j.Spec.Job 371 if j.Status.StartTime.After(latestJobs[name].Status.StartTime.Time) { 372 latestJobs[name] = j 373 } 374 } 375 return latestJobs 376 } 377 378 // ProwJobFields extracts logrus fields from a prowjob useful for logging. 379 func ProwJobFields(pj *kube.ProwJob) logrus.Fields { 380 fields := make(logrus.Fields) 381 fields["name"] = pj.ObjectMeta.Name 382 fields["job"] = pj.Spec.Job 383 fields["type"] = pj.Spec.Type 384 if len(pj.ObjectMeta.Labels[github.EventGUID]) > 0 { 385 fields[github.EventGUID] = pj.ObjectMeta.Labels[github.EventGUID] 386 } 387 if pj.Spec.Refs != nil && len(pj.Spec.Refs.Pulls) == 1 { 388 fields[github.PrLogField] = pj.Spec.Refs.Pulls[0].Number 389 fields[github.RepoLogField] = pj.Spec.Refs.Repo 390 fields[github.OrgLogField] = pj.Spec.Refs.Org 391 } 392 return fields 393 }