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  }