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 }