sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/pod-utils/downwardapi/jobspec.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 downwardapi
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"reflect"
    24  	"strconv"
    25  	"time"
    26  
    27  	"github.com/GoogleCloudPlatform/testgrid/metadata"
    28  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    29  	"sigs.k8s.io/prow/pkg/pod-utils/clone"
    30  )
    31  
    32  // JobSpec is the full downward API that we expose to
    33  // jobs that realize a ProwJob. We will provide this
    34  // data to jobs with environment variables in two ways:
    35  //   - the full spec, in serialized JSON in one variable
    36  //   - individual fields of the spec in their own variables
    37  type JobSpec struct {
    38  	Type      prowapi.ProwJobType `json:"type,omitempty"`
    39  	Job       string              `json:"job,omitempty"`
    40  	BuildID   string              `json:"buildid,omitempty"`
    41  	ProwJobID string              `json:"prowjobid,omitempty"`
    42  
    43  	// refs & extra_refs from the full spec
    44  	Refs      *prowapi.Refs  `json:"refs,omitempty"`
    45  	ExtraRefs []prowapi.Refs `json:"extra_refs,omitempty"`
    46  
    47  	DecorationConfig *prowapi.DecorationConfig `json:"decoration_config,omitempty"`
    48  
    49  	// we need to keep track of the agent until we
    50  	// migrate everyone away from using the $BUILD_NUMBER
    51  	// environment variable
    52  	agent prowapi.ProwJobAgent
    53  }
    54  
    55  // NewJobSpec converts a prowapi.ProwJobSpec invocation into a JobSpec
    56  func NewJobSpec(spec prowapi.ProwJobSpec, buildID, prowJobID string) JobSpec {
    57  	return JobSpec{
    58  		Type:             spec.Type,
    59  		Job:              spec.Job,
    60  		BuildID:          buildID,
    61  		ProwJobID:        prowJobID,
    62  		Refs:             spec.Refs,
    63  		ExtraRefs:        spec.ExtraRefs,
    64  		DecorationConfig: spec.DecorationConfig,
    65  		agent:            spec.Agent,
    66  	}
    67  }
    68  
    69  // ResolveSpecFromEnv will determine the Refs being
    70  // tested in by parsing Prow environment variable contents
    71  func ResolveSpecFromEnv() (*JobSpec, error) {
    72  	specEnv, ok := os.LookupEnv(JobSpecEnv)
    73  	if !ok {
    74  		return nil, fmt.Errorf("$%s unset", JobSpecEnv)
    75  	}
    76  
    77  	spec := &JobSpec{}
    78  	if err := json.Unmarshal([]byte(specEnv), spec); err != nil {
    79  		return nil, fmt.Errorf("malformed $%s: %w", JobSpecEnv, err)
    80  	}
    81  
    82  	return spec, nil
    83  }
    84  
    85  const (
    86  	// ci represents whether the current environment is a CI environment
    87  	CI = "CI"
    88  
    89  	// JobSpecEnv is the name that contains JobSpec marshaled into a string.
    90  	JobSpecEnv = "JOB_SPEC"
    91  
    92  	JobNameEnv   = "JOB_NAME"
    93  	JobTypeEnv   = "JOB_TYPE"
    94  	ProwJobIDEnv = "PROW_JOB_ID"
    95  
    96  	BuildIDEnv     = "BUILD_ID"
    97  	ProwBuildIDEnv = "BUILD_NUMBER" // Deprecated, will be removed in the future.
    98  
    99  	RepoOwnerEnv   = "REPO_OWNER"
   100  	RepoNameEnv    = "REPO_NAME"
   101  	PullBaseRefEnv = "PULL_BASE_REF"
   102  	PullBaseShaEnv = "PULL_BASE_SHA"
   103  	PullRefsEnv    = "PULL_REFS"
   104  	PullNumberEnv  = "PULL_NUMBER"
   105  	PullPullShaEnv = "PULL_PULL_SHA"
   106  	PullHeadRefEnv = "PULL_HEAD_REF"
   107  	PullTitleEnv   = "PULL_TITLE"
   108  )
   109  
   110  // EnvForSpec returns a mapping of environment variables
   111  // to their values that should be available for a job spec
   112  func EnvForSpec(spec JobSpec) (map[string]string, error) {
   113  	env := map[string]string{
   114  		CI:           "true",
   115  		JobNameEnv:   spec.Job,
   116  		BuildIDEnv:   spec.BuildID,
   117  		ProwJobIDEnv: spec.ProwJobID,
   118  		JobTypeEnv:   string(spec.Type),
   119  	}
   120  
   121  	// for backwards compatibility, we provide the build ID
   122  	// in both $BUILD_ID and $BUILD_NUMBER for Prow agents
   123  	// and in both $buildId and $BUILD_NUMBER for Jenkins
   124  	if spec.agent == prowapi.KubernetesAgent {
   125  		env[ProwBuildIDEnv] = spec.BuildID
   126  	}
   127  
   128  	raw, err := json.Marshal(spec)
   129  	if err != nil {
   130  		return env, fmt.Errorf("failed to marshal job spec: %w", err)
   131  	}
   132  	env[JobSpecEnv] = string(raw)
   133  
   134  	if spec.Type == prowapi.PeriodicJob {
   135  		return env, nil
   136  	}
   137  
   138  	env[RepoOwnerEnv] = spec.Refs.Org
   139  	env[RepoNameEnv] = spec.Refs.Repo
   140  	env[PullBaseRefEnv] = spec.Refs.BaseRef
   141  	env[PullBaseShaEnv] = spec.Refs.BaseSHA
   142  	env[PullRefsEnv] = spec.Refs.String()
   143  
   144  	if spec.Type == prowapi.PostsubmitJob || spec.Type == prowapi.BatchJob {
   145  		return env, nil
   146  	}
   147  
   148  	env[PullNumberEnv] = strconv.Itoa(spec.Refs.Pulls[0].Number)
   149  	env[PullPullShaEnv] = spec.Refs.Pulls[0].SHA
   150  	env[PullHeadRefEnv] = spec.Refs.Pulls[0].HeadRef
   151  	env[PullTitleEnv] = spec.Refs.Pulls[0].Title
   152  
   153  	return env, nil
   154  }
   155  
   156  // EnvForType returns the slice of environment variables to export for jobType
   157  func EnvForType(jobType prowapi.ProwJobType) []string {
   158  	baseEnv := []string{CI, JobNameEnv, JobSpecEnv, JobTypeEnv, ProwJobIDEnv, BuildIDEnv, ProwBuildIDEnv}
   159  	refsEnv := []string{RepoOwnerEnv, RepoNameEnv, PullBaseRefEnv, PullBaseShaEnv, PullRefsEnv}
   160  	pullEnv := []string{PullNumberEnv, PullPullShaEnv, PullHeadRefEnv, PullTitleEnv}
   161  
   162  	switch jobType {
   163  	case prowapi.PeriodicJob:
   164  		return baseEnv
   165  	case prowapi.PostsubmitJob, prowapi.BatchJob:
   166  		return append(baseEnv, refsEnv...)
   167  	case prowapi.PresubmitJob:
   168  		return append(append(baseEnv, refsEnv...), pullEnv...)
   169  	default:
   170  		return []string{}
   171  	}
   172  }
   173  
   174  // getRevisionFromRef returns a ref or sha from a refs object
   175  func getRevisionFromRef(refs *prowapi.Refs) string {
   176  	if refs == nil {
   177  		return ""
   178  	}
   179  	if len(refs.Pulls) > 0 {
   180  		return refs.Pulls[0].SHA
   181  	}
   182  
   183  	if refs.BaseSHA != "" {
   184  		return refs.BaseSHA
   185  	}
   186  
   187  	return refs.BaseRef
   188  }
   189  
   190  // GetRevisionFromSpec returns a main ref or sha from a spec object
   191  func GetRevisionFromSpec(jobSpec *JobSpec) string {
   192  	return GetRevisionFromRefs(jobSpec.Refs, jobSpec.ExtraRefs)
   193  }
   194  
   195  func GetRevisionFromRefs(refs *prowapi.Refs, extra []prowapi.Refs) string {
   196  	return getRevisionFromRef(mainRefs(refs, extra))
   197  }
   198  
   199  func mainRefs(refs *prowapi.Refs, extra []prowapi.Refs) *prowapi.Refs {
   200  	if refs != nil {
   201  		return refs
   202  	}
   203  	if len(extra) > 0 {
   204  		return &extra[0]
   205  	}
   206  	return nil
   207  }
   208  
   209  func PjToStarted(pj *prowapi.ProwJob, cloneRecords []clone.Record) metadata.Started {
   210  	return refsToStarted(pj.Spec.Refs, pj.Spec.ExtraRefs, cloneRecords, pj.Status.StartTime.Unix())
   211  }
   212  
   213  func SpecToStarted(spec *JobSpec, cloneRecords []clone.Record) metadata.Started {
   214  	return refsToStarted(spec.Refs, spec.ExtraRefs, cloneRecords, time.Now().Unix())
   215  }
   216  
   217  // refsToStarted translate refs into a Started struct
   218  // optionally overwrite RepoVersion with provided cloneRecords
   219  func refsToStarted(refs *prowapi.Refs, extraRefs []prowapi.Refs, cloneRecords []clone.Record, startTime int64) metadata.Started {
   220  	var version string
   221  
   222  	started := metadata.Started{
   223  		Timestamp: startTime,
   224  	}
   225  
   226  	if mainRefs := mainRefs(refs, extraRefs); mainRefs != nil {
   227  		version = shaForRefs(*mainRefs, cloneRecords)
   228  	}
   229  
   230  	if version == "" {
   231  		version = GetRevisionFromRefs(refs, extraRefs)
   232  	}
   233  
   234  	started.DeprecatedRepoVersion = version
   235  	started.RepoCommit = version
   236  
   237  	if refs != nil && len(refs.Pulls) > 0 {
   238  		started.Pull = strconv.Itoa(refs.Pulls[0].Number)
   239  	}
   240  
   241  	started.Repos = map[string]string{}
   242  
   243  	if refs != nil {
   244  		started.Repos[refs.Org+"/"+refs.Repo] = refs.String()
   245  	}
   246  	for _, ref := range extraRefs {
   247  		started.Repos[ref.Org+"/"+ref.Repo] = ref.String()
   248  	}
   249  
   250  	return started
   251  }
   252  
   253  // shaForRefs finds the resolved SHA after cloning and merging for the given refs
   254  func shaForRefs(refs prowapi.Refs, cloneRecords []clone.Record) string {
   255  	for _, record := range cloneRecords {
   256  		if reflect.DeepEqual(refs, record.Refs) {
   257  			return record.FinalSHA
   258  		}
   259  	}
   260  	return ""
   261  }
   262  
   263  // InCI returns true if the CI environment variable is not empty
   264  func InCI() bool {
   265  	return os.Getenv(CI) != ""
   266  }