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  }