sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/apis/prowjobs/v1/types.go (about) 1 /* 2 Copyright 2018 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 v1 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "mime" 24 "net/url" 25 "strings" 26 "time" 27 28 pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/api/resource" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 33 prowgithub "sigs.k8s.io/prow/pkg/github" 34 ) 35 36 // ProwJobType specifies how the job is triggered. 37 type ProwJobType string 38 39 // Various job types. 40 const ( 41 // PresubmitJob means it runs on unmerged PRs. 42 PresubmitJob ProwJobType = "presubmit" 43 // PostsubmitJob means it runs on each new commit. 44 PostsubmitJob ProwJobType = "postsubmit" 45 // Periodic job means it runs on a time-basis, unrelated to git changes. 46 PeriodicJob ProwJobType = "periodic" 47 // BatchJob tests multiple unmerged PRs at the same time. 48 BatchJob ProwJobType = "batch" 49 ) 50 51 // ProwJobState specifies whether the job is running 52 type ProwJobState string 53 54 // Various job states. 55 const ( 56 // SchedulingState means the job has been created and it is waiting to be scheduled. 57 SchedulingState ProwJobState = "scheduling" 58 // TriggeredState means the job has been scheduled but it is not running yet. 59 TriggeredState ProwJobState = "triggered" 60 // PendingState means the job is currently running and we are waiting for it to finish. 61 PendingState ProwJobState = "pending" 62 // SuccessState means the job completed without error (exit 0) 63 SuccessState ProwJobState = "success" 64 // FailureState means the job completed with errors (exit non-zero) 65 FailureState ProwJobState = "failure" 66 // AbortedState means prow killed the job early (new commit pushed, perhaps). 67 AbortedState ProwJobState = "aborted" 68 // ErrorState means the job could not schedule (bad config, perhaps). 69 ErrorState ProwJobState = "error" 70 ) 71 72 // GetAllProwJobStates returns all possible job states. 73 func GetAllProwJobStates() []ProwJobState { 74 return []ProwJobState{ 75 TriggeredState, 76 PendingState, 77 SuccessState, 78 FailureState, 79 AbortedState, 80 ErrorState} 81 } 82 83 // ProwJobAgent specifies the controller (such as plank or jenkins-agent) that runs the job. 84 type ProwJobAgent string 85 86 const ( 87 // KubernetesAgent means prow will create a pod to run this job. 88 KubernetesAgent ProwJobAgent = "kubernetes" 89 // JenkinsAgent means prow will schedule the job on jenkins. 90 JenkinsAgent ProwJobAgent = "jenkins" 91 // TektonAgent means prow will schedule the job via a tekton PipelineRun CRD resource. 92 TektonAgent = "tekton-pipeline" 93 ) 94 95 const ( 96 // DefaultClusterAlias specifies the default cluster key to schedule jobs. 97 DefaultClusterAlias = "default" 98 ) 99 100 const ( 101 // StartedStatusFile is the JSON file that stores information about the build 102 // at the start of the build. See testgrid/metadata/job.go for more details. 103 StartedStatusFile = "started.json" 104 105 // FinishedStatusFile is the JSON file that stores information about the build 106 // after its completion. See testgrid/metadata/job.go for more details. 107 FinishedStatusFile = "finished.json" 108 109 // ProwJobFile is the JSON file that stores the prowjob information. 110 ProwJobFile = "prowjob.json" 111 112 // CloneRecordFile is the JSON file that stores clone records of a prowjob. 113 CloneRecordFile = "clone-records.json" 114 ) 115 116 // +genclient 117 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 118 119 // ProwJob contains the spec as well as runtime metadata. 120 // +kubebuilder:printcolumn:name="Job",type=string,JSONPath=`.spec.job`,description="The name of the job being run" 121 // +kubebuilder:printcolumn:name="BuildId",type=string,JSONPath=`.status.build_id`,description="The ID of the job being run." 122 // +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`,description="The type of job being run." 123 // +kubebuilder:printcolumn:name="Org",type=string,JSONPath=`.spec.refs.org`,description="The org for which the job is running." 124 // +kubebuilder:printcolumn:name="Repo",type=string,JSONPath=`.spec.refs.repo`,description="The repo for which the job is running." 125 // +kubebuilder:printcolumn:name="Pulls",type=string,JSONPath=`.spec.refs.pulls[*].number`,description="The pulls for which the job is running." 126 // +kubebuilder:printcolumn:name="StartTime",type=date,JSONPath=`.status.startTime`,description="When the job started running." 127 // +kubebuilder:printcolumn:name="CompletionTime",type=date,JSONPath=`.status.completionTime`,description="When the job finished running." 128 // +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state`,description="The state of the job." 129 type ProwJob struct { 130 metav1.TypeMeta `json:",inline"` 131 metav1.ObjectMeta `json:"metadata,omitempty"` 132 133 Spec ProwJobSpec `json:"spec,omitempty"` 134 Status ProwJobStatus `json:"status,omitempty"` 135 } 136 137 // ProwJobSpec configures the details of the prow job. 138 // 139 // Details include the podspec, code to clone, the cluster it runs 140 // any child jobs, concurrency limitations, etc. 141 type ProwJobSpec struct { 142 // Type is the type of job and informs how 143 // the jobs is triggered 144 // +kubebuilder:validation:Enum=presubmit;postsubmit;periodic;batch 145 // +kubebuilder:validation:Required 146 Type ProwJobType `json:"type,omitempty"` 147 // Agent determines which controller fulfills 148 // this specific ProwJobSpec and runs the job 149 Agent ProwJobAgent `json:"agent,omitempty"` 150 // Cluster is which Kubernetes cluster is used 151 // to run the job, only applicable for that 152 // specific agent 153 Cluster string `json:"cluster,omitempty"` 154 // Namespace defines where to create pods/resources. 155 Namespace string `json:"namespace,omitempty"` 156 // Job is the name of the job 157 // +kubebuilder:validation:Required 158 Job string `json:"job,omitempty"` 159 // Refs is the code under test, determined at 160 // runtime by Prow itself 161 Refs *Refs `json:"refs,omitempty"` 162 // ExtraRefs are auxiliary repositories that 163 // need to be cloned, determined from config 164 ExtraRefs []Refs `json:"extra_refs,omitempty"` 165 // Report determines if the result of this job should 166 // be reported (e.g. status on GitHub, message in Slack, etc.) 167 Report bool `json:"report,omitempty"` 168 // Context is the name of the status context used to 169 // report back to GitHub 170 Context string `json:"context,omitempty"` 171 // RerunCommand is the command a user would write to 172 // trigger this job on their pull request 173 RerunCommand string `json:"rerun_command,omitempty"` 174 // MaxConcurrency restricts the total number of instances 175 // of this job that can run in parallel at once. This is 176 // a separate mechanism to JobQueueName and the lowest max 177 // concurrency is selected from these two. 178 // +kubebuilder:validation:Minimum=0 179 MaxConcurrency int `json:"max_concurrency,omitempty"` 180 // ErrorOnEviction indicates that the ProwJob should be completed and given 181 // the ErrorState status if the pod that is executing the job is evicted. 182 // If this field is unspecified or false, a new pod will be created to replace 183 // the evicted one. 184 ErrorOnEviction bool `json:"error_on_eviction,omitempty"` 185 186 // PodSpec provides the basis for running the test under 187 // a Kubernetes agent 188 PodSpec *corev1.PodSpec `json:"pod_spec,omitempty"` 189 190 // JenkinsSpec holds configuration specific to Jenkins jobs 191 JenkinsSpec *JenkinsSpec `json:"jenkins_spec,omitempty"` 192 193 // PipelineRunSpec provides the basis for running the test as 194 // a pipeline-crd resource 195 // https://github.com/tektoncd/pipeline 196 PipelineRunSpec *pipelinev1beta1.PipelineRunSpec `json:"pipeline_run_spec,omitempty"` 197 198 // TektonPipelineRunSpec provides the basis for running the test as 199 // a pipeline-crd resource 200 // https://github.com/tektoncd/pipeline 201 TektonPipelineRunSpec *TektonPipelineRunSpec `json:"tekton_pipeline_run_spec,omitempty"` 202 203 // DecorationConfig holds configuration options for 204 // decorating PodSpecs that users provide 205 DecorationConfig *DecorationConfig `json:"decoration_config,omitempty"` 206 207 // ReporterConfig holds reporter-specific configuration 208 ReporterConfig *ReporterConfig `json:"reporter_config,omitempty"` 209 210 // RerunAuthConfig holds information about which users can rerun the job 211 RerunAuthConfig *RerunAuthConfig `json:"rerun_auth_config,omitempty"` 212 213 // Hidden specifies if the Job is considered hidden. 214 // Hidden jobs are only shown by deck instances that have the 215 // `--hiddenOnly=true` or `--show-hidden=true` flag set. 216 // Presubmits and Postsubmits can also be set to hidden by 217 // adding their repository in Decks `hidden_repo` setting. 218 Hidden bool `json:"hidden,omitempty"` 219 220 // ProwJobDefault holds configuration options provided as defaults 221 // in the Prow config 222 ProwJobDefault *ProwJobDefault `json:"prowjob_defaults,omitempty"` 223 224 // JobQueueName is an optional field with name of a queue defining 225 // max concurrency. When several jobs from the same queue try to run 226 // at the same time, the number of them that is actually started is 227 // limited by JobQueueCapacities (part of Plank's config). If 228 // this field is left undefined inifinite concurrency is assumed. 229 // This behaviour may be superseded by MaxConcurrency field, if it 230 // is set to a constraining value. 231 JobQueueName string `json:"job_queue_name,omitempty"` 232 } 233 234 func (pjs ProwJobSpec) HasPipelineRunSpec() bool { 235 if pjs.TektonPipelineRunSpec != nil && pjs.TektonPipelineRunSpec.V1Beta1 != nil { 236 return true 237 } 238 if pjs.PipelineRunSpec != nil { 239 return true 240 } 241 return false 242 } 243 244 func (pjs ProwJobSpec) GetPipelineRunSpec() (*pipelinev1beta1.PipelineRunSpec, error) { 245 var found *pipelinev1beta1.PipelineRunSpec 246 if pjs.TektonPipelineRunSpec != nil { 247 found = pjs.TektonPipelineRunSpec.V1Beta1 248 } 249 if found == nil && pjs.PipelineRunSpec != nil { 250 found = pjs.PipelineRunSpec 251 } 252 if found == nil { 253 return nil, errors.New("pipeline run spec not found") 254 } 255 return found, nil 256 } 257 258 type GitHubTeamSlug struct { 259 Slug string `json:"slug"` 260 Org string `json:"org"` 261 } 262 263 type RerunAuthConfig struct { 264 // If AllowAnyone is set to true, any user can rerun the job 265 AllowAnyone bool `json:"allow_anyone,omitempty"` 266 // GitHubTeams contains IDs of GitHub teams of users who can rerun the job 267 // If you know the name of a team and the org it belongs to, 268 // you can look up its ID using this command, where the team slug is the hyphenated name: 269 // curl -H "Authorization: token <token>" "https://api.github.com/orgs/<org-name>/teams/<team slug>" 270 // or, to list all teams in a given org, use 271 // curl -H "Authorization: token <token>" "https://api.github.com/orgs/<org-name>/teams" 272 GitHubTeamIDs []int `json:"github_team_ids,omitempty"` 273 // GitHubTeamSlugs contains slugs and orgs of teams of users who can rerun the job 274 GitHubTeamSlugs []GitHubTeamSlug `json:"github_team_slugs,omitempty"` 275 // GitHubUsers contains names of individual users who can rerun the job 276 GitHubUsers []string `json:"github_users,omitempty"` 277 // GitHubOrgs contains names of GitHub organizations whose members can rerun the job 278 GitHubOrgs []string `json:"github_orgs,omitempty"` 279 } 280 281 // IsSpecifiedUser returns true if AllowAnyone is set to true or if the given user is 282 // specified as a permitted GitHubUser 283 func (rac *RerunAuthConfig) IsAuthorized(org, user string, cli prowgithub.RerunClient) (bool, error) { 284 if rac == nil { 285 return false, nil 286 } 287 if rac.AllowAnyone { 288 return true, nil 289 } 290 for _, u := range rac.GitHubUsers { 291 if prowgithub.NormLogin(u) == prowgithub.NormLogin(user) { 292 return true, nil 293 } 294 } 295 // if there is no client, no token was provided, so we cannot access the teams 296 if cli == nil { 297 return false, nil 298 } 299 for _, gho := range rac.GitHubOrgs { 300 isOrgMember, err := cli.IsMember(gho, user) 301 if err != nil { 302 return false, fmt.Errorf("GitHub failed to fetch members of org %v: %w", gho, err) 303 } 304 if isOrgMember { 305 return true, nil 306 } 307 } 308 for _, ght := range rac.GitHubTeamIDs { 309 member, err := cli.TeamHasMember(org, ght, user) 310 if err != nil { 311 return false, fmt.Errorf("GitHub failed to fetch members of team %v, verify that you have the correct team number and access token: %w", ght, err) 312 } 313 if member { 314 return true, nil 315 } 316 } 317 for _, ghts := range rac.GitHubTeamSlugs { 318 member, err := cli.TeamBySlugHasMember(ghts.Org, ghts.Slug, user) 319 if err != nil { 320 return false, fmt.Errorf("GitHub failed to check if team with slug %s has member %s: %w", ghts.Slug, user, err) 321 } 322 if member { 323 return true, nil 324 } 325 } 326 return false, nil 327 } 328 329 // Validate validates the RerunAuthConfig fields. 330 func (rac *RerunAuthConfig) Validate() error { 331 if rac == nil { 332 return nil 333 } 334 335 hasAllowList := len(rac.GitHubUsers) > 0 || len(rac.GitHubTeamIDs) > 0 || len(rac.GitHubTeamSlugs) > 0 || len(rac.GitHubOrgs) > 0 336 337 // If an allowlist is specified, the user probably does not intend for anyone to be able to rerun any job. 338 if rac.AllowAnyone && hasAllowList { 339 return errors.New("allow anyone is set to true and permitted users or groups are specified") 340 } 341 342 return nil 343 } 344 345 // IsAllowAnyone checks if anyone can rerun the job. 346 func (rac *RerunAuthConfig) IsAllowAnyone() bool { 347 if rac == nil { 348 return false 349 } 350 351 return rac.AllowAnyone 352 } 353 354 type ReporterConfig struct { 355 Slack *SlackReporterConfig `json:"slack,omitempty"` 356 } 357 358 type SlackReporterConfig struct { 359 Host string `json:"host,omitempty"` 360 Channel string `json:"channel,omitempty"` 361 JobStatesToReport []ProwJobState `json:"job_states_to_report,omitempty"` 362 ReportTemplate string `json:"report_template,omitempty"` 363 // Report is derived from JobStatesToReport, it's used for differentiating 364 // nil from empty slice, as yaml roundtrip by design can't tell the 365 // difference when omitempty is supplied. 366 // See https://github.com/kubernetes/test-infra/pull/24168 for details 367 // Priority-wise, it goes by following order: 368 // - `report: true/false`` in job config 369 // - `JobStatesToReport: <anything including empty slice>` in job config 370 // - `report: true/false`` in global config 371 // - `JobStatesToReport:` in global config 372 Report *bool `json:"report,omitempty"` 373 } 374 375 // ApplyDefault is called by jobConfig.ApplyDefault(globalConfig) 376 func (src *SlackReporterConfig) ApplyDefault(def *SlackReporterConfig) *SlackReporterConfig { 377 if src == nil && def == nil { 378 return nil 379 } 380 var merged SlackReporterConfig 381 if src != nil { 382 merged = *src.DeepCopy() 383 } else { 384 merged = *def.DeepCopy() 385 } 386 if src == nil || def == nil { 387 return &merged 388 } 389 390 if merged.Channel == "" { 391 merged.Channel = def.Channel 392 } 393 if merged.Host == "" { 394 merged.Host = def.Host 395 } 396 // Note: `job_states_to_report: []` also results in JobStatesToReport == nil 397 if merged.JobStatesToReport == nil { 398 merged.JobStatesToReport = def.JobStatesToReport 399 } 400 if merged.ReportTemplate == "" { 401 merged.ReportTemplate = def.ReportTemplate 402 } 403 if merged.Report == nil { 404 merged.Report = def.Report 405 } 406 return &merged 407 } 408 409 // Duration is a wrapper around time.Duration that parses times in either 410 // 'integer number of nanoseconds' or 'duration string' formats and serializes 411 // to 'duration string' format. 412 // +kubebuilder:validation:Type=string 413 type Duration struct { 414 time.Duration 415 } 416 417 func (d *Duration) UnmarshalJSON(b []byte) error { 418 if err := json.Unmarshal(b, &d.Duration); err == nil { 419 // b was an integer number of nanoseconds. 420 return nil 421 } 422 // b was not an integer. Assume that it is a duration string. 423 424 var str string 425 err := json.Unmarshal(b, &str) 426 if err != nil { 427 return err 428 } 429 430 pd, err := time.ParseDuration(str) 431 if err != nil { 432 return err 433 } 434 d.Duration = pd 435 return nil 436 } 437 438 func (d *Duration) MarshalJSON() ([]byte, error) { 439 return json.Marshal(d.Duration.String()) 440 } 441 442 // ProwJobDefault is used for Prowjob fields we want to set as defaults 443 // in Prow config 444 type ProwJobDefault struct { 445 ResultStoreConfig *ResultStoreConfig `json:"resultstore_config,omitempty"` 446 TenantID string `json:"tenant_id,omitempty"` 447 } 448 449 // ResultStoreConfig specifies parameters for uploading results to 450 // the ResultStore service. 451 type ResultStoreConfig struct { 452 // ProjectID specifies the ResultStore InvocationAttributes.ProjectID, used 453 // for various quota and GUI access control purposes. 454 // In practice, it is generally the same as the Google Cloud Project ID or 455 // number of the job's GCS storage bucket. 456 // Required to upload results to ResultStore. 457 ProjectID string `json:"project_id,omitempty"` 458 } 459 460 // DecorationConfig specifies how to augment pods. 461 // 462 // This is primarily used to provide automatic integration with gubernator 463 // and testgrid. 464 type DecorationConfig struct { 465 // Timeout is how long the pod utilities will wait 466 // before aborting a job with SIGINT. 467 Timeout *Duration `json:"timeout,omitempty"` 468 // GracePeriod is how long the pod utilities will wait 469 // after sending SIGINT to send SIGKILL when aborting 470 // a job. Only applicable if decorating the PodSpec. 471 GracePeriod *Duration `json:"grace_period,omitempty"` 472 473 // UtilityImages holds pull specs for utility container 474 // images used to decorate a PodSpec. 475 UtilityImages *UtilityImages `json:"utility_images,omitempty"` 476 // Resources holds resource requests and limits for utility 477 // containers used to decorate a PodSpec. 478 Resources *Resources `json:"resources,omitempty"` 479 // GCSConfiguration holds options for pushing logs and 480 // artifacts to GCS from a job. 481 GCSConfiguration *GCSConfiguration `json:"gcs_configuration,omitempty"` 482 // GCSCredentialsSecret is the name of the Kubernetes secret 483 // that holds GCS push credentials. 484 GCSCredentialsSecret *string `json:"gcs_credentials_secret,omitempty"` 485 // S3CredentialsSecret is the name of the Kubernetes secret 486 // that holds blob storage push credentials. 487 S3CredentialsSecret *string `json:"s3_credentials_secret,omitempty"` 488 // DefaultServiceAccountName is the name of the Kubernetes service account 489 // that should be used by the pod if one is not specified in the podspec. 490 DefaultServiceAccountName *string `json:"default_service_account_name,omitempty"` 491 // SSHKeySecrets are the names of Kubernetes secrets that contain 492 // SSK keys which should be used during the cloning process. 493 SSHKeySecrets []string `json:"ssh_key_secrets,omitempty"` 494 // SSHHostFingerprints are the fingerprints of known SSH hosts 495 // that the cloning process can trust. 496 // Create with ssh-keyscan [-t rsa] host 497 SSHHostFingerprints []string `json:"ssh_host_fingerprints,omitempty"` 498 // BloblessFetch tells Prow to avoid fetching objects when cloning using 499 // the --filter=blob:none flag. 500 BloblessFetch *bool `json:"blobless_fetch,omitempty"` 501 // SkipCloning determines if we should clone source code in the 502 // initcontainers for jobs that specify refs 503 SkipCloning *bool `json:"skip_cloning,omitempty"` 504 // CookieFileSecret is the name of a kubernetes secret that contains 505 // a git http.cookiefile, which should be used during the cloning process. 506 CookiefileSecret *string `json:"cookiefile_secret,omitempty"` 507 // OauthTokenSecret is a Kubernetes secret that contains the OAuth token, 508 // which is going to be used for fetching a private repository. 509 OauthTokenSecret *OauthTokenSecret `json:"oauth_token_secret,omitempty"` 510 // GitHubAPIEndpoints are the endpoints of GitHub APIs. 511 GitHubAPIEndpoints []string `json:"github_api_endpoints,omitempty"` 512 // GitHubAppID is the ID of GitHub App, which is going to be used for fetching a private 513 // repository. 514 GitHubAppID string `json:"github_app_id,omitempty"` 515 // GitHubAppPrivateKeySecret is a Kubernetes secret that contains the GitHub App private key, 516 // which is going to be used for fetching a private repository. 517 GitHubAppPrivateKeySecret *GitHubAppPrivateKeySecret `json:"github_app_private_key_secret,omitempty"` 518 519 // CensorSecrets enables censoring output logs and artifacts. 520 CensorSecrets *bool `json:"censor_secrets,omitempty"` 521 522 // CensoringOptions exposes options for censoring output logs and artifacts. 523 CensoringOptions *CensoringOptions `json:"censoring_options,omitempty"` 524 525 // UploadIgnoresInterrupts causes sidecar to ignore interrupts for the upload process in 526 // hope that the test process exits cleanly before starting an upload. 527 UploadIgnoresInterrupts *bool `json:"upload_ignores_interrupts,omitempty"` 528 529 // SetLimitEqualsMemoryRequest sets memory limit equal to request. 530 SetLimitEqualsMemoryRequest *bool `json:"set_limit_equals_memory_request,omitempty"` 531 // DefaultMemoryRequest is the default requested memory on a test container. 532 // If SetLimitEqualsMemoryRequest is also true then the Limit will also be 533 // set the same as this request. Could be overridden by memory request 534 // defined explicitly on prowjob. 535 DefaultMemoryRequest *resource.Quantity `json:"default_memory_request,omitempty"` 536 537 // PodPendingTimeout defines how long the controller will wait to perform garbage 538 // collection on pending pods. Specific for OrgRepo or Cluster. If not set, it has a fallback inside plank field. 539 PodPendingTimeout *metav1.Duration `json:"pod_pending_timeout,omitempty"` 540 // PodRunningTimeout defines how long the controller will wait to abort a prowjob pod 541 // stuck in running state. Specific for OrgRepo or Cluster. If not set, it has a fallback inside plank field. 542 PodRunningTimeout *metav1.Duration `json:"pod_running_timeout,omitempty"` 543 // PodUnscheduledTimeout defines how long the controller will wait to abort a prowjob 544 // stuck in an unscheduled state. Specific for OrgRepo or Cluster. If not set, it has a fallback inside plank field. 545 PodUnscheduledTimeout *metav1.Duration `json:"pod_unscheduled_timeout,omitempty"` 546 547 // RunAsUser defines UID for process in all containers running in a Pod. 548 // This field will not override the existing ProwJob's PodSecurityContext. 549 // Equivalent to PodSecurityContext's RunAsUser 550 RunAsUser *int64 `json:"run_as_user,omitempty"` 551 // RunAsGroup defines GID of process in all containers running in a Pod. 552 // This field will not override the existing ProwJob's PodSecurityContext. 553 // Equivalent to PodSecurityContext's RunAsGroup 554 RunAsGroup *int64 `json:"run_as_group,omitempty"` 555 // FsGroup defines special supplemental group ID used in all containers in a Pod. 556 // This allows to change the ownership of particular volumes by kubelet. 557 // This field will not override the existing ProwJob's PodSecurityContext. 558 // Equivalent to PodSecurityContext's FsGroup 559 FsGroup *int64 `json:"fs_group,omitempty"` 560 } 561 562 type CensoringOptions struct { 563 // CensoringConcurrency is the maximum number of goroutines that should be censoring 564 // artifacts and logs at any time. If unset, defaults to 10. 565 CensoringConcurrency *int64 `json:"censoring_concurrency,omitempty"` 566 // CensoringBufferSize is the size in bytes of the buffer allocated for every file 567 // being censored. We want to keep as little of the file in memory as possible in 568 // order for censoring to be reasonably performant in space. However, to guarantee 569 // that we censor every instance of every secret, our buffer size must be at least 570 // two times larger than the largest secret we are about to censor. While that size 571 // is the smallest possible buffer we could use, if the secrets being censored are 572 // small, censoring will not be performant as the number of I/O actions per file 573 // would increase. If unset, defaults to 10MiB. 574 CensoringBufferSize *int `json:"censoring_buffer_size,omitempty"` 575 576 // IncludeDirectories are directories which should have their content censored. If 577 // present, only content in these directories will be censored. Entries in this list 578 // are relative to $ARTIFACTS and are parsed with the go-zglob library, allowing for 579 // globbed matches. 580 IncludeDirectories []string `json:"include_directories,omitempty"` 581 582 // ExcludeDirectories are directories which should not have their content censored. If 583 // present, content in these directories will not be censored even if the directory also 584 // matches a glob in IncludeDirectories. Entries in this list are relative to $ARTIFACTS, 585 // and are parsed with the go-zglob library, allowing for globbed matches. 586 ExcludeDirectories []string `json:"exclude_directories,omitempty"` 587 } 588 589 // ApplyDefault applies the defaults for CensoringOptions decorations. If a field has a zero value, 590 // it replaces that with the value set in def. 591 func (g *CensoringOptions) ApplyDefault(def *CensoringOptions) *CensoringOptions { 592 if g == nil && def == nil { 593 return nil 594 } 595 var merged CensoringOptions 596 if g != nil { 597 merged = *g.DeepCopy() 598 } else { 599 merged = *def.DeepCopy() 600 } 601 if g == nil || def == nil { 602 return &merged 603 } 604 605 if merged.CensoringConcurrency == nil { 606 merged.CensoringConcurrency = def.CensoringConcurrency 607 } 608 609 if merged.CensoringBufferSize == nil { 610 merged.CensoringBufferSize = def.CensoringBufferSize 611 } 612 613 if merged.IncludeDirectories == nil { 614 merged.IncludeDirectories = def.IncludeDirectories 615 } 616 617 if merged.ExcludeDirectories == nil { 618 merged.ExcludeDirectories = def.ExcludeDirectories 619 } 620 return &merged 621 } 622 623 // Resources holds resource requests and limits for 624 // containers used to decorate a PodSpec 625 type Resources struct { 626 CloneRefs *corev1.ResourceRequirements `json:"clonerefs,omitempty"` 627 InitUpload *corev1.ResourceRequirements `json:"initupload,omitempty"` 628 PlaceEntrypoint *corev1.ResourceRequirements `json:"place_entrypoint,omitempty"` 629 Sidecar *corev1.ResourceRequirements `json:"sidecar,omitempty"` 630 } 631 632 // ApplyDefault applies the defaults for the resource decorations. If a field has a zero value, 633 // it replaces that with the value set in def. 634 func (u *Resources) ApplyDefault(def *Resources) *Resources { 635 if u == nil { 636 return def 637 } else if def == nil { 638 return u 639 } 640 641 merged := *u 642 if merged.CloneRefs == nil { 643 merged.CloneRefs = def.CloneRefs 644 } 645 if merged.InitUpload == nil { 646 merged.InitUpload = def.InitUpload 647 } 648 if merged.PlaceEntrypoint == nil { 649 merged.PlaceEntrypoint = def.PlaceEntrypoint 650 } 651 if merged.Sidecar == nil { 652 merged.Sidecar = def.Sidecar 653 } 654 return &merged 655 } 656 657 // OauthTokenSecret holds the information of the oauth token's secret name and key. 658 type OauthTokenSecret struct { 659 // Name is the name of a kubernetes secret. 660 Name string `json:"name,omitempty"` 661 // Key is the key of the corresponding kubernetes secret that 662 // holds the value of the OAuth token. 663 Key string `json:"key,omitempty"` 664 } 665 666 // GitHubAppPrivateKeySecret holds the information of the GitHub App private key's secret name and key. 667 type GitHubAppPrivateKeySecret struct { 668 // Name is the name of a kubernetes secret. 669 Name string `json:"name,omitempty"` 670 // Key is the key of the corresponding kubernetes secret that 671 // holds the value of the GitHub App private key. 672 Key string `json:"key,omitempty"` 673 } 674 675 func (d *ProwJobDefault) ApplyDefault(def *ProwJobDefault) *ProwJobDefault { 676 if d == nil && def == nil { 677 return nil 678 } 679 var merged ProwJobDefault 680 if d != nil { 681 merged = *d.DeepCopy() 682 } else { 683 merged = *def.DeepCopy() 684 } 685 if d == nil || def == nil { 686 return &merged 687 } 688 if merged.ResultStoreConfig == nil { 689 merged.ResultStoreConfig = def.ResultStoreConfig 690 } 691 if merged.TenantID == "" { 692 merged.TenantID = def.TenantID 693 } 694 695 return &merged 696 } 697 698 // ApplyDefault applies the defaults for the ProwJob decoration. If a field has a zero value, it 699 // replaces that with the value set in def. 700 func (d *DecorationConfig) ApplyDefault(def *DecorationConfig) *DecorationConfig { 701 if d == nil && def == nil { 702 return nil 703 } 704 var merged DecorationConfig 705 if d != nil { 706 merged = *d.DeepCopy() 707 } else { 708 merged = *def.DeepCopy() 709 } 710 if d == nil || def == nil { 711 return &merged 712 } 713 merged.UtilityImages = merged.UtilityImages.ApplyDefault(def.UtilityImages) 714 merged.Resources = merged.Resources.ApplyDefault(def.Resources) 715 merged.GCSConfiguration = merged.GCSConfiguration.ApplyDefault(def.GCSConfiguration) 716 merged.CensoringOptions = merged.CensoringOptions.ApplyDefault(def.CensoringOptions) 717 718 if merged.Timeout == nil { 719 merged.Timeout = def.Timeout 720 } 721 if merged.GracePeriod == nil { 722 merged.GracePeriod = def.GracePeriod 723 } 724 if merged.GCSCredentialsSecret == nil { 725 merged.GCSCredentialsSecret = def.GCSCredentialsSecret 726 } 727 if merged.S3CredentialsSecret == nil { 728 merged.S3CredentialsSecret = def.S3CredentialsSecret 729 } 730 if merged.DefaultServiceAccountName == nil { 731 merged.DefaultServiceAccountName = def.DefaultServiceAccountName 732 } 733 if len(merged.SSHKeySecrets) == 0 { 734 merged.SSHKeySecrets = def.SSHKeySecrets 735 } 736 if len(merged.SSHHostFingerprints) == 0 { 737 merged.SSHHostFingerprints = def.SSHHostFingerprints 738 } 739 if merged.SkipCloning == nil { 740 merged.SkipCloning = def.SkipCloning 741 } 742 if merged.CookiefileSecret == nil { 743 merged.CookiefileSecret = def.CookiefileSecret 744 } 745 if merged.OauthTokenSecret == nil { 746 merged.OauthTokenSecret = def.OauthTokenSecret 747 } 748 if len(merged.GitHubAPIEndpoints) == 0 { 749 merged.GitHubAPIEndpoints = def.GitHubAPIEndpoints 750 } 751 if merged.GitHubAppID == "" { 752 merged.GitHubAppID = def.GitHubAppID 753 } 754 if merged.GitHubAppPrivateKeySecret == nil { 755 merged.GitHubAppPrivateKeySecret = def.GitHubAppPrivateKeySecret 756 } 757 if merged.CensorSecrets == nil { 758 merged.CensorSecrets = def.CensorSecrets 759 } 760 761 if merged.UploadIgnoresInterrupts == nil { 762 merged.UploadIgnoresInterrupts = def.UploadIgnoresInterrupts 763 } 764 765 if merged.SetLimitEqualsMemoryRequest == nil { 766 merged.SetLimitEqualsMemoryRequest = def.SetLimitEqualsMemoryRequest 767 } 768 769 if merged.DefaultMemoryRequest == nil { 770 merged.DefaultMemoryRequest = def.DefaultMemoryRequest 771 } 772 773 if merged.PodPendingTimeout == nil { 774 merged.PodPendingTimeout = def.PodPendingTimeout 775 } 776 777 if merged.PodRunningTimeout == nil { 778 merged.PodRunningTimeout = def.PodRunningTimeout 779 } 780 781 if merged.PodUnscheduledTimeout == nil { 782 merged.PodUnscheduledTimeout = def.PodUnscheduledTimeout 783 } 784 785 if merged.RunAsUser == nil { 786 merged.RunAsUser = def.RunAsUser 787 } 788 789 if merged.RunAsGroup == nil { 790 merged.RunAsGroup = def.RunAsGroup 791 } 792 793 if merged.FsGroup == nil { 794 merged.FsGroup = def.FsGroup 795 } 796 797 if merged.BloblessFetch == nil { 798 merged.BloblessFetch = def.BloblessFetch 799 } 800 return &merged 801 } 802 803 // Validate ensures all the values set in the DecorationConfig are valid. 804 func (d *DecorationConfig) Validate() error { 805 if d.UtilityImages == nil { 806 return errors.New("utility image config is not specified") 807 } 808 var missing []string 809 if d.UtilityImages.CloneRefs == "" { 810 missing = append(missing, "clonerefs") 811 } 812 if d.UtilityImages.InitUpload == "" { 813 missing = append(missing, "initupload") 814 } 815 if d.UtilityImages.Entrypoint == "" { 816 missing = append(missing, "entrypoint") 817 } 818 if d.UtilityImages.Sidecar == "" { 819 missing = append(missing, "sidecar") 820 } 821 if len(missing) > 0 { 822 return fmt.Errorf("the following utility images are not specified: %q", missing) 823 } 824 825 if d.GCSConfiguration == nil { 826 return errors.New("GCS upload configuration is not specified") 827 } 828 // Intentionally allow d.GCSCredentialsSecret and d.S3CredentialsSecret to 829 // be unset in which case we assume GCS permissions are provided by GKE 830 // Workload Identity: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity 831 832 if err := d.GCSConfiguration.Validate(); err != nil { 833 return fmt.Errorf("GCS configuration is invalid: %w", err) 834 } 835 if d.OauthTokenSecret != nil && len(d.SSHKeySecrets) > 0 { 836 return errors.New("both OAuth token and SSH key secrets are specified") 837 } 838 return nil 839 } 840 841 func (d *Duration) Get() time.Duration { 842 if d == nil { 843 return 0 844 } 845 return d.Duration 846 } 847 848 // UtilityImages holds pull specs for the utility images 849 // to be used for a job 850 type UtilityImages struct { 851 // CloneRefs is the pull spec used for the clonerefs utility 852 CloneRefs string `json:"clonerefs,omitempty"` 853 // InitUpload is the pull spec used for the initupload utility 854 InitUpload string `json:"initupload,omitempty"` 855 // Entrypoint is the pull spec used for the entrypoint utility 856 Entrypoint string `json:"entrypoint,omitempty"` 857 // sidecar is the pull spec used for the sidecar utility 858 Sidecar string `json:"sidecar,omitempty"` 859 } 860 861 // ApplyDefault applies the defaults for the UtilityImages decorations. If a field has a zero value, 862 // it replaces that with the value set in def. 863 func (u *UtilityImages) ApplyDefault(def *UtilityImages) *UtilityImages { 864 if u == nil { 865 return def 866 } else if def == nil { 867 return u 868 } 869 870 merged := *u.DeepCopy() 871 if merged.CloneRefs == "" { 872 merged.CloneRefs = def.CloneRefs 873 } 874 if merged.InitUpload == "" { 875 merged.InitUpload = def.InitUpload 876 } 877 if merged.Entrypoint == "" { 878 merged.Entrypoint = def.Entrypoint 879 } 880 if merged.Sidecar == "" { 881 merged.Sidecar = def.Sidecar 882 } 883 return &merged 884 } 885 886 // PathStrategy specifies minutia about how to construct the url. 887 // Usually consumed by gubernator/testgrid. 888 const ( 889 PathStrategyLegacy = "legacy" 890 PathStrategySingle = "single" 891 PathStrategyExplicit = "explicit" 892 ) 893 894 // GCSConfiguration holds options for pushing logs and 895 // artifacts to GCS from a job. 896 type GCSConfiguration struct { 897 // Bucket is the bucket to upload to, it can be: 898 // * a GCS bucket: with gs:// prefix 899 // * a S3 bucket: with s3:// prefix 900 // * a GCS bucket: without a prefix (deprecated, it's discouraged to use Bucket without prefix please add the gs:// prefix) 901 Bucket string `json:"bucket,omitempty"` 902 // PathPrefix is an optional path that follows the 903 // bucket name and comes before any structure 904 PathPrefix string `json:"path_prefix,omitempty"` 905 // PathStrategy dictates how the org and repo are used 906 // when calculating the full path to an artifact in GCS 907 PathStrategy string `json:"path_strategy,omitempty"` 908 // DefaultOrg is omitted from GCS paths when using the 909 // legacy or simple strategy 910 DefaultOrg string `json:"default_org,omitempty"` 911 // DefaultRepo is omitted from GCS paths when using the 912 // legacy or simple strategy 913 DefaultRepo string `json:"default_repo,omitempty"` 914 // MediaTypes holds additional extension media types to add to Go's 915 // builtin's and the local system's defaults. This maps extensions 916 // to media types, for example: MediaTypes["log"] = "text/plain" 917 MediaTypes map[string]string `json:"mediaTypes,omitempty"` 918 // JobURLPrefix holds the baseURL under which the jobs output can be viewed. 919 // If unset, this will be derived based on org/repo from the job_url_prefix_config. 920 JobURLPrefix string `json:"job_url_prefix,omitempty"` 921 922 // LocalOutputDir specifies a directory where files should be copied INSTEAD of uploading to blob storage. 923 // This option is useful for testing jobs that use the pod-utilities without actually uploading. 924 LocalOutputDir string `json:"local_output_dir,omitempty"` 925 // CompressFileTypes specify file types that should be gzipped prior to upload. 926 // Matching files will be compressed prior to upload, and the content-encoding on these files will be set to gzip. 927 // GCS will transcode these gzipped files transparently when viewing. See: https://cloud.google.com/storage/docs/transcoding 928 // Example: "txt", "json" 929 // Use "*" for all 930 CompressFileTypes []string `json:"compress_file_types,omitempty"` 931 } 932 933 // ApplyDefault applies the defaults for GCSConfiguration decorations. If a field has a zero value, 934 // it replaces that with the value set in def. 935 func (g *GCSConfiguration) ApplyDefault(def *GCSConfiguration) *GCSConfiguration { 936 if g == nil && def == nil { 937 return nil 938 } 939 var merged GCSConfiguration 940 if g != nil { 941 merged = *g.DeepCopy() 942 } else { 943 merged = *def.DeepCopy() 944 } 945 if g == nil || def == nil { 946 return &merged 947 } 948 949 if merged.Bucket == "" { 950 merged.Bucket = def.Bucket 951 } 952 if merged.PathPrefix == "" { 953 merged.PathPrefix = def.PathPrefix 954 } 955 if merged.PathStrategy == "" { 956 merged.PathStrategy = def.PathStrategy 957 } 958 if merged.DefaultOrg == "" { 959 merged.DefaultOrg = def.DefaultOrg 960 } 961 if merged.DefaultRepo == "" { 962 merged.DefaultRepo = def.DefaultRepo 963 } 964 965 if merged.MediaTypes == nil && len(def.MediaTypes) > 0 { 966 merged.MediaTypes = map[string]string{} 967 } 968 969 for extension, mediaType := range def.MediaTypes { 970 merged.MediaTypes[extension] = mediaType 971 } 972 973 if merged.JobURLPrefix == "" { 974 merged.JobURLPrefix = def.JobURLPrefix 975 } 976 977 if merged.LocalOutputDir == "" { 978 merged.LocalOutputDir = def.LocalOutputDir 979 } 980 if merged.CompressFileTypes == nil { 981 merged.CompressFileTypes = def.CompressFileTypes 982 } 983 return &merged 984 } 985 986 // Validate ensures all the values set in the GCSConfiguration are valid. 987 func (g *GCSConfiguration) Validate() error { 988 if _, err := ParsePath(g.Bucket); err != nil { 989 return err 990 } 991 for _, mediaType := range g.MediaTypes { 992 if _, _, err := mime.ParseMediaType(mediaType); err != nil { 993 return fmt.Errorf("invalid extension media type %q: %w", mediaType, err) 994 } 995 } 996 if g.PathStrategy != PathStrategyLegacy && g.PathStrategy != PathStrategyExplicit && g.PathStrategy != PathStrategySingle { 997 return fmt.Errorf("gcs_path_strategy must be one of %q, %q, or %q", PathStrategyLegacy, PathStrategyExplicit, PathStrategySingle) 998 } 999 if g.PathStrategy != PathStrategyExplicit && (g.DefaultOrg == "" || g.DefaultRepo == "") { 1000 return fmt.Errorf("default org and repo must be provided for GCS strategy %q", g.PathStrategy) 1001 } 1002 return nil 1003 } 1004 1005 type ProwPath url.URL 1006 1007 func (pp ProwPath) StorageProvider() string { 1008 return pp.Scheme 1009 } 1010 1011 func (pp ProwPath) Bucket() string { 1012 return pp.Host 1013 } 1014 1015 func (pp ProwPath) BucketWithScheme() string { 1016 return fmt.Sprintf("%s://%s", pp.StorageProvider(), pp.Bucket()) 1017 } 1018 1019 func (pp ProwPath) FullPath() string { 1020 return pp.Host + pp.Path 1021 } 1022 1023 func (pp *ProwPath) String() string { 1024 return (*url.URL)(pp).String() 1025 } 1026 1027 // ParsePath tries to extract the ProwPath from, e.g.: 1028 // * <bucket-name> (storageProvider gs) 1029 // * <storage-provider>://<bucket-name> 1030 func ParsePath(bucket string) (*ProwPath, error) { 1031 // default to GCS if no storage-provider is specified 1032 if !strings.Contains(bucket, "://") { 1033 bucket = "gs://" + bucket 1034 } 1035 parsedBucket, err := url.Parse(bucket) 1036 if err != nil { 1037 return nil, fmt.Errorf("path %q has invalid format, expected either <bucket-name>[/<path>] or <storage-provider>://<bucket-name>[/<path>]", bucket) 1038 } 1039 pp := ProwPath(*parsedBucket) 1040 return &pp, nil 1041 } 1042 1043 // ProwJobStatus provides runtime metadata, such as when it finished, whether it is running, etc. 1044 type ProwJobStatus struct { 1045 // StartTime is equal to the creation time of the ProwJob 1046 StartTime metav1.Time `json:"startTime,omitempty"` 1047 // PendingTime is the timestamp for when the job moved from triggered to pending 1048 PendingTime *metav1.Time `json:"pendingTime,omitempty"` 1049 // CompletionTime is the timestamp for when the job goes to a final state 1050 CompletionTime *metav1.Time `json:"completionTime,omitempty"` 1051 // +kubebuilder:validation:Enum=scheduling;triggered;pending;success;failure;aborted;error 1052 // +kubebuilder:validation:Required 1053 State ProwJobState `json:"state,omitempty"` 1054 Description string `json:"description,omitempty"` 1055 URL string `json:"url,omitempty"` 1056 1057 // PodName applies only to ProwJobs fulfilled by 1058 // plank. This field should always be the same as 1059 // the ProwJob.ObjectMeta.Name field. 1060 PodName string `json:"pod_name,omitempty"` 1061 1062 // BuildID is the build identifier vended either by tot 1063 // or the snowflake library for this job and used as an 1064 // identifier for grouping artifacts in GCS for views in 1065 // TestGrid and Gubernator. Idenitifiers vended by tot 1066 // are monotonically increasing whereas identifiers vended 1067 // by the snowflake library are not. 1068 BuildID string `json:"build_id,omitempty"` 1069 1070 // JenkinsBuildID applies only to ProwJobs fulfilled 1071 // by the jenkins-operator. This field is the build 1072 // identifier that Jenkins gave to the build for this 1073 // ProwJob. 1074 JenkinsBuildID string `json:"jenkins_build_id,omitempty"` 1075 1076 // PrevReportStates stores the previous reported prowjob state per reporter 1077 // So crier won't make duplicated report attempt 1078 PrevReportStates map[string]ProwJobState `json:"prev_report_states,omitempty"` 1079 } 1080 1081 // Complete returns true if the prow job has finished 1082 func (j *ProwJob) Complete() bool { 1083 // TODO(fejta): support a timeout? 1084 return j.Status.CompletionTime != nil 1085 } 1086 1087 // SetComplete marks the job as completed (at time now). 1088 func (j *ProwJob) SetComplete() { 1089 j.Status.CompletionTime = new(metav1.Time) 1090 *j.Status.CompletionTime = metav1.Now() 1091 } 1092 1093 // ClusterAlias specifies the key in the clusters map to use. 1094 // 1095 // This allows scheduling a prow job somewhere aside from the default build cluster. 1096 func (j *ProwJob) ClusterAlias() string { 1097 if j.Spec.Cluster == "" { 1098 return DefaultClusterAlias 1099 } 1100 return j.Spec.Cluster 1101 } 1102 1103 // Pull describes a pull request at a particular point in time. 1104 type Pull struct { 1105 Number int `json:"number"` 1106 Author string `json:"author"` 1107 SHA string `json:"sha"` 1108 Title string `json:"title,omitempty"` 1109 1110 // Ref is git ref can be checked out for a change 1111 // for example, 1112 // github: pull/123/head 1113 // gerrit: refs/changes/00/123/1 1114 Ref string `json:"ref,omitempty"` 1115 // HeadRef is the git ref (branch name) of the proposed change. This can be more human-readable than just 1116 // a PR #, and some tools want this metadata to help associate the work with a pull request (e.g. some code 1117 // scanning services, or chromatic.com). 1118 HeadRef string `json:"head_ref,omitempty"` 1119 // Link links to the pull request itself. 1120 Link string `json:"link,omitempty"` 1121 // CommitLink links to the commit identified by the SHA. 1122 CommitLink string `json:"commit_link,omitempty"` 1123 // AuthorLink links to the author of the pull request. 1124 AuthorLink string `json:"author_link,omitempty"` 1125 } 1126 1127 // Refs describes how the repo was constructed. 1128 type Refs struct { 1129 // Org is something like kubernetes or k8s.io 1130 Org string `json:"org"` 1131 // Repo is something like test-infra 1132 Repo string `json:"repo"` 1133 // RepoLink links to the source for Repo. 1134 RepoLink string `json:"repo_link,omitempty"` 1135 1136 BaseRef string `json:"base_ref,omitempty"` 1137 BaseSHA string `json:"base_sha,omitempty"` 1138 // BaseLink is a link to the commit identified by BaseSHA. 1139 BaseLink string `json:"base_link,omitempty"` 1140 1141 Pulls []Pull `json:"pulls,omitempty"` 1142 1143 // PathAlias is the location under <root-dir>/src 1144 // where this repository is cloned. If this is not 1145 // set, <root-dir>/src/github.com/org/repo will be 1146 // used as the default. 1147 PathAlias string `json:"path_alias,omitempty"` 1148 1149 // WorkDir defines if the location of the cloned 1150 // repository will be used as the default working 1151 // directory. 1152 WorkDir bool `json:"workdir,omitempty"` 1153 1154 // CloneURI is the URI that is used to clone the 1155 // repository. If unset, will default to 1156 // `https://github.com/org/repo.git`. 1157 CloneURI string `json:"clone_uri,omitempty"` 1158 // SkipSubmodules determines if submodules should be 1159 // cloned when the job is run. Defaults to false. 1160 SkipSubmodules bool `json:"skip_submodules,omitempty"` 1161 // CloneDepth is the depth of the clone that will be used. 1162 // A depth of zero will do a full clone. 1163 CloneDepth int `json:"clone_depth,omitempty"` 1164 // SkipFetchHead tells prow to avoid a git fetch <remote> call. 1165 // Multiheaded repos may need to not make this call. 1166 // The git fetch <remote> <BaseRef> call occurs regardless. 1167 SkipFetchHead bool `json:"skip_fetch_head,omitempty"` 1168 // BloblessFetch tells prow to avoid fetching objects when cloning 1169 // using the --filter=blob:none flag. If unspecified, defaults to 1170 // DecorationConfig.BloblessFetch. 1171 BloblessFetch *bool `json:"blobless_fetch,omitempty"` 1172 } 1173 1174 func (r Refs) String() string { 1175 rs := []string{} 1176 if r.BaseSHA != "" { 1177 rs = append(rs, fmt.Sprintf("%s:%s", r.BaseRef, r.BaseSHA)) 1178 } else { 1179 rs = append(rs, r.BaseRef) 1180 } 1181 1182 for _, pull := range r.Pulls { 1183 ref := fmt.Sprintf("%d:%s", pull.Number, pull.SHA) 1184 1185 if pull.Ref != "" { 1186 ref = fmt.Sprintf("%s:%s", ref, pull.Ref) 1187 } 1188 1189 rs = append(rs, ref) 1190 } 1191 return strings.Join(rs, ",") 1192 } 1193 1194 func (r Refs) OrgRepoString() string { 1195 if r.Repo != "" { 1196 return r.Org + "/" + r.Repo 1197 } 1198 return r.Org 1199 } 1200 1201 // JenkinsSpec is optional parameters for Jenkins jobs. 1202 // Currently, the only parameter supported is for telling 1203 // jenkins-operator that the job is generated by the https://go.cloudbees.com/docs/plugins/github-branch-source/#github-branch-source plugin 1204 type JenkinsSpec struct { 1205 GitHubBranchSourceJob bool `json:"github_branch_source_job,omitempty"` 1206 } 1207 1208 // TektonPipelineRunSpec is optional parameters for Tekton pipeline jobs. 1209 type TektonPipelineRunSpec struct { 1210 V1Beta1 *pipelinev1beta1.PipelineRunSpec `json:"v1beta1,omitempty"` 1211 } 1212 1213 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 1214 1215 // ProwJobList is a list of ProwJob resources 1216 type ProwJobList struct { 1217 metav1.TypeMeta `json:",inline"` 1218 metav1.ListMeta `json:"metadata"` 1219 1220 Items []ProwJob `json:"items"` 1221 }