sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/pod-utils/decorate/podspec_test.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 decorate
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strconv"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	coreapi "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/equality"
    30  	"k8s.io/apimachinery/pkg/api/resource"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/util/diff"
    33  	utilpointer "k8s.io/utils/pointer"
    34  	"sigs.k8s.io/yaml"
    35  
    36  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    37  	"sigs.k8s.io/prow/pkg/clonerefs"
    38  	"sigs.k8s.io/prow/pkg/entrypoint"
    39  	"sigs.k8s.io/prow/pkg/gcsupload"
    40  	"sigs.k8s.io/prow/pkg/github"
    41  	"sigs.k8s.io/prow/pkg/initupload"
    42  	"sigs.k8s.io/prow/pkg/pod-utils/wrapper"
    43  	"sigs.k8s.io/prow/pkg/sidecar"
    44  	"sigs.k8s.io/prow/pkg/testutil"
    45  )
    46  
    47  func pStr(str string) *string {
    48  	return &str
    49  }
    50  func pInt64(i int64) *int64 {
    51  	return &i
    52  }
    53  
    54  func cookieVolumeOnly(secret string) coreapi.Volume {
    55  	v, _, _ := cookiefileVolume(secret)
    56  	return v
    57  }
    58  
    59  func cookieMountOnly(secret string) coreapi.VolumeMount {
    60  	_, vm, _ := cookiefileVolume(secret)
    61  	return vm
    62  }
    63  func cookiePathOnly(secret string) string {
    64  	_, _, vp := cookiefileVolume(secret)
    65  	return vp
    66  }
    67  
    68  func TestCloneRefs(t *testing.T) {
    69  	truth := true
    70  	logMount := coreapi.VolumeMount{
    71  		Name:      "log",
    72  		MountPath: "/log-mount",
    73  	}
    74  	codeMount := coreapi.VolumeMount{
    75  		Name:      "code",
    76  		MountPath: "/code-mount",
    77  	}
    78  	tmpMount := coreapi.VolumeMount{
    79  		Name:      "clonerefs-tmp",
    80  		MountPath: "/tmp",
    81  	}
    82  	tmpVolume := coreapi.Volume{
    83  		Name: "clonerefs-tmp",
    84  		VolumeSource: coreapi.VolumeSource{
    85  			EmptyDir: &coreapi.EmptyDirVolumeSource{},
    86  		},
    87  	}
    88  	envOrDie := func(opt clonerefs.Options) []coreapi.EnvVar {
    89  		e, err := cloneEnv(opt)
    90  		if err != nil {
    91  			t.Fatal(err)
    92  		}
    93  		return e
    94  	}
    95  	sshVolumeOnly := func(secret string) coreapi.Volume {
    96  		v, _ := sshVolume(secret)
    97  		return v
    98  	}
    99  
   100  	sshMountOnly := func(secret string) coreapi.VolumeMount {
   101  		_, vm := sshVolume(secret)
   102  		return vm
   103  	}
   104  
   105  	cases := []struct {
   106  		name              string
   107  		pj                prowapi.ProwJob
   108  		codeMountOverride *coreapi.VolumeMount
   109  		logMountOverride  *coreapi.VolumeMount
   110  		expected          *coreapi.Container
   111  		volumes           []coreapi.Volume
   112  		err               bool
   113  	}{
   114  		{
   115  			name: "empty returns nil",
   116  		},
   117  		{
   118  			name: "nil refs and extrarefs returns nil",
   119  			pj: prowapi.ProwJob{
   120  				Spec: prowapi.ProwJobSpec{
   121  					DecorationConfig: &prowapi.DecorationConfig{},
   122  				},
   123  			},
   124  		},
   125  		{
   126  			name: "nil DecorationConfig returns nil",
   127  			pj: prowapi.ProwJob{
   128  				Spec: prowapi.ProwJobSpec{
   129  					Refs: &prowapi.Refs{},
   130  				},
   131  			},
   132  		},
   133  		{
   134  			name: "SkipCloning returns nil",
   135  			pj: prowapi.ProwJob{
   136  				Spec: prowapi.ProwJobSpec{
   137  					Refs: &prowapi.Refs{},
   138  					DecorationConfig: &prowapi.DecorationConfig{
   139  						SkipCloning: &truth,
   140  					},
   141  				},
   142  			},
   143  		},
   144  		{
   145  			name: "reject empty code mount name",
   146  			pj: prowapi.ProwJob{
   147  				Spec: prowapi.ProwJobSpec{
   148  					DecorationConfig: &prowapi.DecorationConfig{},
   149  					Refs:             &prowapi.Refs{},
   150  				},
   151  			},
   152  			codeMountOverride: &coreapi.VolumeMount{
   153  				MountPath: "/whatever",
   154  			},
   155  			err: true,
   156  		},
   157  		{
   158  			name: "reject empty code mountpath",
   159  			pj: prowapi.ProwJob{
   160  				Spec: prowapi.ProwJobSpec{
   161  					DecorationConfig: &prowapi.DecorationConfig{},
   162  					Refs:             &prowapi.Refs{},
   163  				},
   164  			},
   165  			codeMountOverride: &coreapi.VolumeMount{
   166  				Name: "wee",
   167  			},
   168  			err: true,
   169  		},
   170  		{
   171  			name: "reject empty log mount name",
   172  			pj: prowapi.ProwJob{
   173  				Spec: prowapi.ProwJobSpec{
   174  					DecorationConfig: &prowapi.DecorationConfig{},
   175  					Refs:             &prowapi.Refs{},
   176  				},
   177  			},
   178  			logMountOverride: &coreapi.VolumeMount{
   179  				MountPath: "/whatever",
   180  			},
   181  			err: true,
   182  		},
   183  		{
   184  			name: "reject empty log mountpath",
   185  			pj: prowapi.ProwJob{
   186  				Spec: prowapi.ProwJobSpec{
   187  					DecorationConfig: &prowapi.DecorationConfig{},
   188  					Refs:             &prowapi.Refs{},
   189  				},
   190  			},
   191  			logMountOverride: &coreapi.VolumeMount{
   192  				Name: "wee",
   193  			},
   194  			err: true,
   195  		},
   196  		{
   197  			name: "create clonerefs container when refs are set",
   198  			pj: prowapi.ProwJob{
   199  				Spec: prowapi.ProwJobSpec{
   200  					Refs: &prowapi.Refs{},
   201  					DecorationConfig: &prowapi.DecorationConfig{
   202  						UtilityImages: &prowapi.UtilityImages{},
   203  					},
   204  				},
   205  			},
   206  			expected: &coreapi.Container{
   207  				Name: cloneRefsName,
   208  				Env: envOrDie(clonerefs.Options{
   209  					GitRefs:            []prowapi.Refs{{}},
   210  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   211  					GitUserName:        clonerefs.DefaultGitUserName,
   212  					SrcRoot:            codeMount.MountPath,
   213  					Log:                CloneLogPath(logMount),
   214  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   215  				}),
   216  				VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount},
   217  			},
   218  			volumes: []coreapi.Volume{tmpVolume},
   219  		},
   220  		{
   221  			name: "create clonerefs containers when extrarefs are set",
   222  			pj: prowapi.ProwJob{
   223  				Spec: prowapi.ProwJobSpec{
   224  					ExtraRefs: []prowapi.Refs{{}},
   225  					DecorationConfig: &prowapi.DecorationConfig{
   226  						UtilityImages: &prowapi.UtilityImages{},
   227  					},
   228  				},
   229  			},
   230  			expected: &coreapi.Container{
   231  				Name: cloneRefsName,
   232  				Env: envOrDie(clonerefs.Options{
   233  					GitRefs:            []prowapi.Refs{{}},
   234  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   235  					GitUserName:        clonerefs.DefaultGitUserName,
   236  					SrcRoot:            codeMount.MountPath,
   237  					Log:                CloneLogPath(logMount),
   238  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   239  				}),
   240  				VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount},
   241  			},
   242  			volumes: []coreapi.Volume{tmpVolume},
   243  		},
   244  		{
   245  			name: "append extrarefs after refs",
   246  			pj: prowapi.ProwJob{
   247  				Spec: prowapi.ProwJobSpec{
   248  					Refs:      &prowapi.Refs{Org: "first"},
   249  					ExtraRefs: []prowapi.Refs{{Org: "second"}, {Org: "third"}},
   250  					DecorationConfig: &prowapi.DecorationConfig{
   251  						UtilityImages: &prowapi.UtilityImages{},
   252  					},
   253  				},
   254  			},
   255  			expected: &coreapi.Container{
   256  				Name: cloneRefsName,
   257  				Env: envOrDie(clonerefs.Options{
   258  					GitRefs:            []prowapi.Refs{{Org: "first"}, {Org: "second"}, {Org: "third"}},
   259  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   260  					GitUserName:        clonerefs.DefaultGitUserName,
   261  					SrcRoot:            codeMount.MountPath,
   262  					Log:                CloneLogPath(logMount),
   263  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   264  				}),
   265  				VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount},
   266  			},
   267  			volumes: []coreapi.Volume{tmpVolume},
   268  		},
   269  		{
   270  			name: "append ssh secrets when set",
   271  			pj: prowapi.ProwJob{
   272  				Spec: prowapi.ProwJobSpec{
   273  					Refs: &prowapi.Refs{},
   274  					DecorationConfig: &prowapi.DecorationConfig{
   275  						UtilityImages: &prowapi.UtilityImages{},
   276  						SSHKeySecrets: []string{"super", "secret"},
   277  					},
   278  				},
   279  			},
   280  			expected: &coreapi.Container{
   281  				Name: cloneRefsName,
   282  				Env: envOrDie(clonerefs.Options{
   283  					GitRefs:            []prowapi.Refs{{}},
   284  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   285  					GitUserName:        clonerefs.DefaultGitUserName,
   286  					KeyFiles:           []string{sshMountOnly("super").MountPath, sshMountOnly("secret").MountPath},
   287  					SrcRoot:            codeMount.MountPath,
   288  					Log:                CloneLogPath(logMount),
   289  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   290  				}),
   291  				VolumeMounts: []coreapi.VolumeMount{
   292  					logMount,
   293  					codeMount,
   294  					sshMountOnly("super"),
   295  					sshMountOnly("secret"),
   296  					tmpMount,
   297  				},
   298  			},
   299  			volumes: []coreapi.Volume{sshVolumeOnly("super"), sshVolumeOnly("secret"), tmpVolume},
   300  		},
   301  		{
   302  			name: "include ssh host fingerprints when set",
   303  			pj: prowapi.ProwJob{
   304  				Spec: prowapi.ProwJobSpec{
   305  					ExtraRefs: []prowapi.Refs{{}},
   306  					DecorationConfig: &prowapi.DecorationConfig{
   307  						UtilityImages:       &prowapi.UtilityImages{},
   308  						SSHHostFingerprints: []string{"thumb", "pinky"},
   309  					},
   310  				},
   311  			},
   312  			expected: &coreapi.Container{
   313  				Name: cloneRefsName,
   314  				Env: envOrDie(clonerefs.Options{
   315  					GitRefs:            []prowapi.Refs{{}},
   316  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   317  					GitUserName:        clonerefs.DefaultGitUserName,
   318  					SrcRoot:            codeMount.MountPath,
   319  					HostFingerprints:   []string{"thumb", "pinky"},
   320  					Log:                CloneLogPath(logMount),
   321  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   322  				}),
   323  				VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount},
   324  			},
   325  			volumes: []coreapi.Volume{tmpVolume},
   326  		},
   327  		{
   328  			name: "include cookiefile secrets when set",
   329  			pj: prowapi.ProwJob{
   330  				Spec: prowapi.ProwJobSpec{
   331  					ExtraRefs: []prowapi.Refs{{}},
   332  					DecorationConfig: &prowapi.DecorationConfig{
   333  						UtilityImages:    &prowapi.UtilityImages{},
   334  						CookiefileSecret: pStr("oatmeal"),
   335  					},
   336  				},
   337  			},
   338  			expected: &coreapi.Container{
   339  				Name: cloneRefsName,
   340  				Args: []string{"--cookiefile=" + cookiePathOnly("oatmeal")},
   341  				Env: envOrDie(clonerefs.Options{
   342  					CookiePath:         cookiePathOnly("oatmeal"),
   343  					GitRefs:            []prowapi.Refs{{}},
   344  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   345  					GitUserName:        clonerefs.DefaultGitUserName,
   346  					SrcRoot:            codeMount.MountPath,
   347  					Log:                CloneLogPath(logMount),
   348  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   349  				}),
   350  				VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount, cookieMountOnly("oatmeal")},
   351  			},
   352  			volumes: []coreapi.Volume{tmpVolume, cookieVolumeOnly("oatmeal")},
   353  		},
   354  		{
   355  			name: "intentional empty string cookiefile secrets is valid",
   356  			pj: prowapi.ProwJob{
   357  				Spec: prowapi.ProwJobSpec{
   358  					ExtraRefs: []prowapi.Refs{{}},
   359  					DecorationConfig: &prowapi.DecorationConfig{
   360  						UtilityImages:    &prowapi.UtilityImages{},
   361  						CookiefileSecret: pStr(""),
   362  					},
   363  				},
   364  			},
   365  			expected: &coreapi.Container{
   366  				Name: cloneRefsName,
   367  				Env: envOrDie(clonerefs.Options{
   368  					GitRefs:            []prowapi.Refs{{}},
   369  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   370  					GitUserName:        clonerefs.DefaultGitUserName,
   371  					SrcRoot:            codeMount.MountPath,
   372  					Log:                CloneLogPath(logMount),
   373  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   374  				}),
   375  				VolumeMounts: []coreapi.VolumeMount{logMount, codeMount, tmpMount},
   376  			},
   377  			volumes: []coreapi.Volume{tmpVolume},
   378  		},
   379  		{
   380  			name: "include oauth token secret when set",
   381  			pj: prowapi.ProwJob{
   382  				Spec: prowapi.ProwJobSpec{
   383  					ExtraRefs: []prowapi.Refs{{}},
   384  					DecorationConfig: &prowapi.DecorationConfig{
   385  						UtilityImages: &prowapi.UtilityImages{},
   386  						OauthTokenSecret: &prowapi.OauthTokenSecret{
   387  							Name: "oauth-secret",
   388  							Key:  "oauth-file",
   389  						},
   390  					},
   391  				},
   392  			},
   393  			expected: &coreapi.Container{
   394  				Name: cloneRefsName,
   395  				Env: envOrDie(clonerefs.Options{
   396  					GitRefs:            []prowapi.Refs{{}},
   397  					GitUserEmail:       clonerefs.DefaultGitUserEmail,
   398  					GitUserName:        clonerefs.DefaultGitUserName,
   399  					SrcRoot:            codeMount.MountPath,
   400  					Log:                CloneLogPath(logMount),
   401  					OauthTokenFile:     "/secrets/oauth/oauth-file",
   402  					GitHubAPIEndpoints: []string{github.DefaultAPIEndpoint},
   403  				}),
   404  				VolumeMounts: []coreapi.VolumeMount{
   405  					logMount, codeMount,
   406  					{Name: "oauth-secret", ReadOnly: true, MountPath: "/secrets/oauth"},
   407  					tmpMount,
   408  				},
   409  			},
   410  			volumes: []coreapi.Volume{
   411  				{
   412  					Name: "oauth-secret",
   413  					VolumeSource: coreapi.VolumeSource{
   414  						Secret: &coreapi.SecretVolumeSource{
   415  							SecretName: "oauth-secret",
   416  							Items: []coreapi.KeyToPath{{
   417  								Key:  "oauth-file",
   418  								Path: "./oauth-file",
   419  							}},
   420  						},
   421  					},
   422  				},
   423  				tmpVolume,
   424  			},
   425  		},
   426  		{
   427  			name: "include GitHub App ID and private key when set",
   428  			pj: prowapi.ProwJob{
   429  				Spec: prowapi.ProwJobSpec{
   430  					ExtraRefs: []prowapi.Refs{{}},
   431  					DecorationConfig: &prowapi.DecorationConfig{
   432  						UtilityImages: &prowapi.UtilityImages{},
   433  						GitHubAppID:   "123456",
   434  						GitHubAppPrivateKeySecret: &prowapi.GitHubAppPrivateKeySecret{
   435  							Name: "github-app-secret",
   436  							Key:  "private-key",
   437  						},
   438  					},
   439  				},
   440  			},
   441  			expected: &coreapi.Container{
   442  				Name: cloneRefsName,
   443  				Env: envOrDie(clonerefs.Options{
   444  					GitRefs:                 []prowapi.Refs{{}},
   445  					GitUserEmail:            clonerefs.DefaultGitUserEmail,
   446  					GitUserName:             clonerefs.DefaultGitUserName,
   447  					SrcRoot:                 codeMount.MountPath,
   448  					Log:                     CloneLogPath(logMount),
   449  					GitHubAPIEndpoints:      []string{github.DefaultAPIEndpoint},
   450  					GitHubAppID:             "123456",
   451  					GitHubAppPrivateKeyFile: "/secrets/github-app/private-key",
   452  				}),
   453  				VolumeMounts: []coreapi.VolumeMount{
   454  					logMount, codeMount,
   455  					{
   456  						Name:      "github-app-secret",
   457  						ReadOnly:  true,
   458  						MountPath: "/secrets/github-app",
   459  					},
   460  					tmpMount,
   461  				},
   462  			},
   463  			volumes: []coreapi.Volume{
   464  				{
   465  					Name: "github-app-secret",
   466  					VolumeSource: coreapi.VolumeSource{
   467  						Secret: &coreapi.SecretVolumeSource{
   468  							SecretName: "github-app-secret",
   469  							Items: []coreapi.KeyToPath{{
   470  								Key:  "private-key",
   471  								Path: "./private-key",
   472  							}},
   473  						},
   474  					},
   475  				},
   476  				tmpVolume,
   477  			},
   478  		},
   479  		{
   480  			name: "include custom GitHub API endpoints when set",
   481  			pj: prowapi.ProwJob{
   482  				Spec: prowapi.ProwJobSpec{
   483  					ExtraRefs: []prowapi.Refs{{}},
   484  					DecorationConfig: &prowapi.DecorationConfig{
   485  						UtilityImages:      &prowapi.UtilityImages{},
   486  						GitHubAPIEndpoints: []string{"http://example.com"},
   487  						GitHubAppID:        "123456",
   488  						GitHubAppPrivateKeySecret: &prowapi.GitHubAppPrivateKeySecret{
   489  							Name: "github-app-secret",
   490  							Key:  "private-key",
   491  						},
   492  					},
   493  				},
   494  			},
   495  			expected: &coreapi.Container{
   496  				Name: cloneRefsName,
   497  				Env: envOrDie(clonerefs.Options{
   498  					GitRefs:                 []prowapi.Refs{{}},
   499  					GitUserEmail:            clonerefs.DefaultGitUserEmail,
   500  					GitUserName:             clonerefs.DefaultGitUserName,
   501  					SrcRoot:                 codeMount.MountPath,
   502  					Log:                     CloneLogPath(logMount),
   503  					GitHubAPIEndpoints:      []string{"http://example.com"},
   504  					GitHubAppID:             "123456",
   505  					GitHubAppPrivateKeyFile: "/secrets/github-app/private-key",
   506  				}),
   507  				VolumeMounts: []coreapi.VolumeMount{
   508  					logMount, codeMount,
   509  					{
   510  						Name:      "github-app-secret",
   511  						ReadOnly:  true,
   512  						MountPath: "/secrets/github-app",
   513  					},
   514  					tmpMount,
   515  				},
   516  			},
   517  			volumes: []coreapi.Volume{
   518  				{
   519  					Name: "github-app-secret",
   520  					VolumeSource: coreapi.VolumeSource{
   521  						Secret: &coreapi.SecretVolumeSource{
   522  							SecretName: "github-app-secret",
   523  							Items: []coreapi.KeyToPath{{
   524  								Key:  "private-key",
   525  								Path: "./private-key",
   526  							}},
   527  						},
   528  					},
   529  				},
   530  				tmpVolume,
   531  			},
   532  		},
   533  	}
   534  
   535  	for _, tc := range cases {
   536  		t.Run(tc.name, func(t *testing.T) {
   537  			lm := logMount
   538  			if tc.logMountOverride != nil {
   539  				lm = *tc.logMountOverride
   540  			}
   541  			cm := codeMount
   542  			if tc.codeMountOverride != nil {
   543  				cm = *tc.codeMountOverride
   544  			}
   545  			actual, refs, volumes, err := CloneRefs(tc.pj, cm, lm)
   546  			switch {
   547  			case err != nil:
   548  				if !tc.err {
   549  					t.Errorf("unexpected error: %v", err)
   550  				}
   551  			case tc.err:
   552  				t.Error("failed to receive expected exception")
   553  			case !equality.Semantic.DeepEqual(tc.expected, actual):
   554  				t.Errorf("unexpected container:\n%s", diff.ObjectReflectDiff(tc.expected, actual))
   555  			case !equality.Semantic.DeepEqual(tc.volumes, volumes):
   556  				t.Errorf("unexpected volume:\n%s", diff.ObjectReflectDiff(tc.volumes, volumes))
   557  			case actual != nil:
   558  				var er []prowapi.Refs
   559  				if tc.pj.Spec.Refs != nil {
   560  					er = append(er, *tc.pj.Spec.Refs)
   561  				}
   562  				er = append(er, tc.pj.Spec.ExtraRefs...)
   563  				if !equality.Semantic.DeepEqual(refs, er) {
   564  					t.Errorf("unexpected refs:\n%s", diff.ObjectReflectDiff(er, refs))
   565  				}
   566  			}
   567  		})
   568  	}
   569  }
   570  
   571  func TestProwJobToPod(t *testing.T) {
   572  	truth := true
   573  	tests := []struct {
   574  		podName  string
   575  		buildID  string
   576  		labels   map[string]string
   577  		pjSpec   prowapi.ProwJobSpec
   578  		pjStatus prowapi.ProwJobStatus
   579  	}{
   580  		{
   581  			podName: "pod",
   582  			buildID: "blabla",
   583  			labels:  map[string]string{"needstobe": "inherited"},
   584  			pjSpec: prowapi.ProwJobSpec{
   585  				Type:    prowapi.PresubmitJob,
   586  				Job:     "job-name",
   587  				Context: "job-context",
   588  				Agent:   prowapi.KubernetesAgent,
   589  				Refs: &prowapi.Refs{
   590  					Org:     "org-name",
   591  					Repo:    "repo-name",
   592  					BaseRef: "base-ref",
   593  					BaseSHA: "base-sha",
   594  					Pulls: []prowapi.Pull{{
   595  						Number:  1,
   596  						Author:  "author-name",
   597  						SHA:     "pull-sha",
   598  						HeadRef: "pull-branch-name",
   599  						Title:   "pull-title",
   600  					}},
   601  				},
   602  				PodSpec: &coreapi.PodSpec{
   603  					Containers: []coreapi.Container{
   604  						{
   605  							Image: "tester",
   606  							Env: []coreapi.EnvVar{
   607  								{Name: "MY_ENV", Value: "rocks"},
   608  							},
   609  						},
   610  					},
   611  				},
   612  			},
   613  			pjStatus: prowapi.ProwJobStatus{
   614  				BuildID: "blabla",
   615  			},
   616  		},
   617  		{
   618  			podName: "pod",
   619  			buildID: "blabla",
   620  			labels:  map[string]string{"needstobe": "inherited"},
   621  			pjSpec: prowapi.ProwJobSpec{
   622  				Type:    prowapi.PresubmitJob,
   623  				Job:     "job-name",
   624  				Context: "job-context",
   625  				DecorationConfig: &prowapi.DecorationConfig{
   626  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   627  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   628  					UtilityImages: &prowapi.UtilityImages{
   629  						CloneRefs:  "clonerefs:tag",
   630  						InitUpload: "initupload:tag",
   631  						Entrypoint: "entrypoint:tag",
   632  						Sidecar:    "sidecar:tag",
   633  					},
   634  					GCSConfiguration: &prowapi.GCSConfiguration{
   635  						Bucket:       "my-bucket",
   636  						PathStrategy: "legacy",
   637  						DefaultOrg:   "kubernetes",
   638  						DefaultRepo:  "kubernetes",
   639  						MediaTypes:   map[string]string{"log": "text/plain"},
   640  					},
   641  					GCSCredentialsSecret: pStr("secret-name"),
   642  					CookiefileSecret:     pStr("yummy/.gitcookies"),
   643  				},
   644  				Agent: prowapi.KubernetesAgent,
   645  				Refs: &prowapi.Refs{
   646  					Org:     "org-name",
   647  					Repo:    "repo-name",
   648  					BaseRef: "base-ref",
   649  					BaseSHA: "base-sha",
   650  					Pulls: []prowapi.Pull{{
   651  						Number:  1,
   652  						Author:  "author-name",
   653  						SHA:     "pull-sha",
   654  						HeadRef: "my-big-change",
   655  						Title:   "pull-title",
   656  					}},
   657  					PathAlias: "somewhere/else",
   658  				},
   659  				ExtraRefs: []prowapi.Refs{},
   660  				PodSpec: &coreapi.PodSpec{
   661  					Containers: []coreapi.Container{
   662  						{
   663  							Image:   "tester",
   664  							Command: []string{"/bin/thing"},
   665  							Args:    []string{"some", "args"},
   666  							Env: []coreapi.EnvVar{
   667  								{Name: "MY_ENV", Value: "rocks"},
   668  							},
   669  						},
   670  					},
   671  				},
   672  			},
   673  		},
   674  		{
   675  			podName: "pod",
   676  			buildID: "blabla",
   677  			labels:  map[string]string{"needstobe": "inherited"},
   678  			pjSpec: prowapi.ProwJobSpec{
   679  				Type:    prowapi.PresubmitJob,
   680  				Job:     "job-name",
   681  				Context: "job-context",
   682  				DecorationConfig: &prowapi.DecorationConfig{
   683  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   684  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   685  					UtilityImages: &prowapi.UtilityImages{
   686  						CloneRefs:  "clonerefs:tag",
   687  						InitUpload: "initupload:tag",
   688  						Entrypoint: "entrypoint:tag",
   689  						Sidecar:    "sidecar:tag",
   690  					},
   691  					GCSConfiguration: &prowapi.GCSConfiguration{
   692  						Bucket:       "my-bucket",
   693  						PathStrategy: "legacy",
   694  						DefaultOrg:   "kubernetes",
   695  						DefaultRepo:  "kubernetes",
   696  					},
   697  					GCSCredentialsSecret: pStr("secret-name"),
   698  					CookiefileSecret:     pStr("yummy"),
   699  				},
   700  				Agent: prowapi.KubernetesAgent,
   701  				Refs: &prowapi.Refs{
   702  					Org:     "org-name",
   703  					Repo:    "repo-name",
   704  					BaseRef: "base-ref",
   705  					BaseSHA: "base-sha",
   706  					Pulls: []prowapi.Pull{{
   707  						Number:  1,
   708  						Author:  "author-name",
   709  						SHA:     "pull-sha",
   710  						HeadRef: "fix-typos-99",
   711  						Title:   "pull-title",
   712  					}},
   713  					PathAlias: "somewhere/else",
   714  				},
   715  				ExtraRefs: []prowapi.Refs{},
   716  				PodSpec: &coreapi.PodSpec{
   717  					Containers: []coreapi.Container{
   718  						{
   719  							Image:   "tester",
   720  							Command: []string{"/bin/thing"},
   721  							Args:    []string{"some", "args"},
   722  							Env: []coreapi.EnvVar{
   723  								{Name: "MY_ENV", Value: "rocks"},
   724  							},
   725  						},
   726  					},
   727  				},
   728  			},
   729  		},
   730  		{
   731  			podName: "pod",
   732  			buildID: "blabla",
   733  			labels:  map[string]string{"needstobe": "inherited"},
   734  			pjSpec: prowapi.ProwJobSpec{
   735  				Type:    prowapi.PresubmitJob,
   736  				Job:     "job-name",
   737  				Context: "job-context",
   738  				DecorationConfig: &prowapi.DecorationConfig{
   739  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   740  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   741  					UtilityImages: &prowapi.UtilityImages{
   742  						CloneRefs:  "clonerefs:tag",
   743  						InitUpload: "initupload:tag",
   744  						Entrypoint: "entrypoint:tag",
   745  						Sidecar:    "sidecar:tag",
   746  					},
   747  					GCSConfiguration: &prowapi.GCSConfiguration{
   748  						Bucket:       "my-bucket",
   749  						PathStrategy: "legacy",
   750  						DefaultOrg:   "kubernetes",
   751  						DefaultRepo:  "kubernetes",
   752  					},
   753  					GCSCredentialsSecret: pStr("secret-name"),
   754  					SSHKeySecrets:        []string{"ssh-1", "ssh-2"},
   755  					SSHHostFingerprints:  []string{"hello", "world"},
   756  				},
   757  				Agent: prowapi.KubernetesAgent,
   758  				Refs: &prowapi.Refs{
   759  					Org:     "org-name",
   760  					Repo:    "repo-name",
   761  					BaseRef: "base-ref",
   762  					BaseSHA: "base-sha",
   763  					Pulls: []prowapi.Pull{{
   764  						Number:  1,
   765  						Author:  "author-name",
   766  						SHA:     "pull-sha",
   767  						HeadRef: "fixes-fixes-fixes",
   768  						Title:   "pull-title",
   769  					}},
   770  					PathAlias: "somewhere/else",
   771  				},
   772  				ExtraRefs: []prowapi.Refs{},
   773  				PodSpec: &coreapi.PodSpec{
   774  					Containers: []coreapi.Container{
   775  						{
   776  							Image:   "tester",
   777  							Command: []string{"/bin/thing"},
   778  							Args:    []string{"some", "args"},
   779  							Env: []coreapi.EnvVar{
   780  								{Name: "MY_ENV", Value: "rocks"},
   781  							},
   782  							TerminationMessagePolicy: coreapi.TerminationMessageReadFile,
   783  						},
   784  					},
   785  				},
   786  			},
   787  		},
   788  		{
   789  			podName: "pod",
   790  			buildID: "blabla",
   791  			labels:  map[string]string{"needstobe": "inherited"},
   792  			pjSpec: prowapi.ProwJobSpec{
   793  				Type:    prowapi.PresubmitJob,
   794  				Job:     "job-name",
   795  				Context: "job-context",
   796  				DecorationConfig: &prowapi.DecorationConfig{
   797  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   798  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   799  					UtilityImages: &prowapi.UtilityImages{
   800  						CloneRefs:  "clonerefs:tag",
   801  						InitUpload: "initupload:tag",
   802  						Entrypoint: "entrypoint:tag",
   803  						Sidecar:    "sidecar:tag",
   804  					},
   805  					GCSConfiguration: &prowapi.GCSConfiguration{
   806  						Bucket:       "my-bucket",
   807  						PathStrategy: "legacy",
   808  						DefaultOrg:   "kubernetes",
   809  						DefaultRepo:  "kubernetes",
   810  					},
   811  					GCSCredentialsSecret: pStr("secret-name"),
   812  					SSHKeySecrets:        []string{"ssh-1", "ssh-2"},
   813  				},
   814  				Agent: prowapi.KubernetesAgent,
   815  				Refs: &prowapi.Refs{
   816  					Org:     "org-name",
   817  					Repo:    "repo-name",
   818  					BaseRef: "base-ref",
   819  					BaseSHA: "base-sha",
   820  					Pulls: []prowapi.Pull{{
   821  						Number:  1,
   822  						Author:  "author-name",
   823  						SHA:     "pull-sha",
   824  						HeadRef: "fixes-9",
   825  						Title:   "pull-title",
   826  					}},
   827  					PathAlias: "somewhere/else",
   828  				},
   829  				ExtraRefs: []prowapi.Refs{},
   830  				PodSpec: &coreapi.PodSpec{
   831  					Containers: []coreapi.Container{
   832  						{
   833  							Image:   "tester",
   834  							Command: []string{"/bin/thing"},
   835  							Args:    []string{"some", "args"},
   836  							Env: []coreapi.EnvVar{
   837  								{Name: "MY_ENV", Value: "rocks"},
   838  							},
   839  						},
   840  					},
   841  				},
   842  			},
   843  		},
   844  		{
   845  			podName: "pod",
   846  			buildID: "blabla",
   847  			labels:  map[string]string{"needstobe": "inherited"},
   848  			pjSpec: prowapi.ProwJobSpec{
   849  				Type:    prowapi.PeriodicJob,
   850  				Job:     "job-name",
   851  				Context: "job-context",
   852  				DecorationConfig: &prowapi.DecorationConfig{
   853  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   854  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   855  					UtilityImages: &prowapi.UtilityImages{
   856  						CloneRefs:  "clonerefs:tag",
   857  						InitUpload: "initupload:tag",
   858  						Entrypoint: "entrypoint:tag",
   859  						Sidecar:    "sidecar:tag",
   860  					},
   861  					GCSConfiguration: &prowapi.GCSConfiguration{
   862  						Bucket:       "my-bucket",
   863  						PathStrategy: "legacy",
   864  						DefaultOrg:   "kubernetes",
   865  						DefaultRepo:  "kubernetes",
   866  					},
   867  					GCSCredentialsSecret: pStr("secret-name"),
   868  					SSHKeySecrets:        []string{"ssh-1", "ssh-2"},
   869  				},
   870  				Agent: prowapi.KubernetesAgent,
   871  				PodSpec: &coreapi.PodSpec{
   872  					Containers: []coreapi.Container{
   873  						{
   874  							Image:   "tester",
   875  							Command: []string{"/bin/thing"},
   876  							Args:    []string{"some", "args"},
   877  							Env: []coreapi.EnvVar{
   878  								{Name: "MY_ENV", Value: "rocks"},
   879  							},
   880  						},
   881  					},
   882  				},
   883  			},
   884  		},
   885  		{
   886  			podName: "pod",
   887  			buildID: "blabla",
   888  			labels:  map[string]string{"needstobe": "inherited"},
   889  			pjSpec: prowapi.ProwJobSpec{
   890  				Type:    prowapi.PresubmitJob,
   891  				Job:     "job-name",
   892  				Context: "job-context",
   893  				DecorationConfig: &prowapi.DecorationConfig{
   894  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   895  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   896  					UtilityImages: &prowapi.UtilityImages{
   897  						CloneRefs:  "clonerefs:tag",
   898  						InitUpload: "initupload:tag",
   899  						Entrypoint: "entrypoint:tag",
   900  						Sidecar:    "sidecar:tag",
   901  					},
   902  					GCSConfiguration: &prowapi.GCSConfiguration{
   903  						Bucket:       "my-bucket",
   904  						PathStrategy: "legacy",
   905  						DefaultOrg:   "kubernetes",
   906  						DefaultRepo:  "kubernetes",
   907  					},
   908  					GCSCredentialsSecret: pStr("secret-name"),
   909  					SSHKeySecrets:        []string{"ssh-1", "ssh-2"},
   910  					SkipCloning:          &truth,
   911  				},
   912  				Agent: prowapi.KubernetesAgent,
   913  				Refs: &prowapi.Refs{
   914  					Org:     "org-name",
   915  					Repo:    "repo-name",
   916  					BaseRef: "base-ref",
   917  					BaseSHA: "base-sha",
   918  					Pulls: []prowapi.Pull{{
   919  						Number:  1,
   920  						Author:  "author-name",
   921  						SHA:     "pull-sha",
   922  						HeadRef: "best-branch-name",
   923  						Title:   "pull-title",
   924  					}},
   925  					PathAlias: "somewhere/else",
   926  				},
   927  				ExtraRefs: []prowapi.Refs{
   928  					{
   929  						Org:  "extra-org",
   930  						Repo: "extra-repo",
   931  					},
   932  				},
   933  				PodSpec: &coreapi.PodSpec{
   934  					Containers: []coreapi.Container{
   935  						{
   936  							Image:   "tester",
   937  							Command: []string{"/bin/thing"},
   938  							Args:    []string{"some", "args"},
   939  							Env: []coreapi.EnvVar{
   940  								{Name: "MY_ENV", Value: "rocks"},
   941  							},
   942  						},
   943  					},
   944  				},
   945  			},
   946  		},
   947  		{
   948  			podName: "pod",
   949  			buildID: "blabla",
   950  			labels:  map[string]string{"needstobe": "inherited"},
   951  			pjSpec: prowapi.ProwJobSpec{
   952  				Type:    prowapi.PresubmitJob,
   953  				Job:     "job-name",
   954  				Context: "job-context",
   955  				DecorationConfig: &prowapi.DecorationConfig{
   956  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
   957  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
   958  					UtilityImages: &prowapi.UtilityImages{
   959  						CloneRefs:  "clonerefs:tag",
   960  						InitUpload: "initupload:tag",
   961  						Entrypoint: "entrypoint:tag",
   962  						Sidecar:    "sidecar:tag",
   963  					},
   964  					GCSConfiguration: &prowapi.GCSConfiguration{
   965  						Bucket:       "my-bucket",
   966  						PathStrategy: "legacy",
   967  						DefaultOrg:   "kubernetes",
   968  						DefaultRepo:  "kubernetes",
   969  					},
   970  					GCSCredentialsSecret: pStr("secret-name"),
   971  					SSHKeySecrets:        []string{"ssh-1", "ssh-2"},
   972  					CookiefileSecret:     pStr("yummy"),
   973  				},
   974  				Agent: prowapi.KubernetesAgent,
   975  				Refs: &prowapi.Refs{
   976  					Org:     "org-name",
   977  					Repo:    "repo-name",
   978  					BaseRef: "base-ref",
   979  					BaseSHA: "base-sha",
   980  					Pulls: []prowapi.Pull{{
   981  						Number:  1,
   982  						Author:  "author-name",
   983  						SHA:     "pull-sha",
   984  						HeadRef: "pr-head-ref-11",
   985  						Title:   "pull-title",
   986  					}},
   987  					PathAlias: "somewhere/else",
   988  				},
   989  				ExtraRefs: []prowapi.Refs{
   990  					{
   991  						Org:  "extra-org",
   992  						Repo: "extra-repo",
   993  					},
   994  				},
   995  				PodSpec: &coreapi.PodSpec{
   996  					Containers: []coreapi.Container{
   997  						{
   998  							Name:    "test-0",
   999  							Image:   "tester",
  1000  							Command: []string{"/bin/thing"},
  1001  							Args:    []string{"some", "args"},
  1002  							Env: []coreapi.EnvVar{
  1003  								{Name: "MY_ENV", Value: "rocks"},
  1004  							},
  1005  						},
  1006  						{
  1007  							Name:    "test-1",
  1008  							Image:   "othertester",
  1009  							Command: []string{"/bin/otherthing"},
  1010  							Args:    []string{"other", "args"},
  1011  							Env: []coreapi.EnvVar{
  1012  								{Name: "MY_ENV", Value: "stones"},
  1013  							},
  1014  						},
  1015  					},
  1016  				},
  1017  			},
  1018  		},
  1019  		{
  1020  			podName: "pod",
  1021  			buildID: "blabla",
  1022  			labels:  map[string]string{"needstobe": "inherited"},
  1023  			pjSpec: prowapi.ProwJobSpec{
  1024  				Type:    prowapi.PresubmitJob,
  1025  				Job:     "job-name",
  1026  				Context: "job-context",
  1027  				DecorationConfig: &prowapi.DecorationConfig{
  1028  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
  1029  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
  1030  					UtilityImages: &prowapi.UtilityImages{
  1031  						CloneRefs:  "clonerefs:tag",
  1032  						InitUpload: "initupload:tag",
  1033  						Entrypoint: "entrypoint:tag",
  1034  						Sidecar:    "sidecar:tag",
  1035  					},
  1036  					GCSConfiguration: &prowapi.GCSConfiguration{
  1037  						Bucket:       "my-bucket",
  1038  						PathStrategy: "legacy",
  1039  						DefaultOrg:   "kubernetes",
  1040  						DefaultRepo:  "kubernetes",
  1041  						MediaTypes:   map[string]string{"log": "text/plain"},
  1042  					},
  1043  					// Specify K8s SA rather than cloud storage secret key.
  1044  					DefaultServiceAccountName: pStr("default-SA"),
  1045  					CookiefileSecret:          pStr("yummy/.gitcookies"),
  1046  				},
  1047  				Agent: prowapi.KubernetesAgent,
  1048  				Refs: &prowapi.Refs{
  1049  					Org:     "org-name",
  1050  					Repo:    "repo-name",
  1051  					BaseRef: "base-ref",
  1052  					BaseSHA: "base-sha",
  1053  					Pulls: []prowapi.Pull{{
  1054  						Number:  1,
  1055  						Author:  "author-name",
  1056  						SHA:     "pull-sha",
  1057  						HeadRef: "orig-branch-name",
  1058  						Title:   "pull-title",
  1059  					}},
  1060  					PathAlias: "somewhere/else",
  1061  				},
  1062  				ExtraRefs: []prowapi.Refs{},
  1063  				PodSpec: &coreapi.PodSpec{
  1064  					Containers: []coreapi.Container{
  1065  						{
  1066  							Image:   "tester",
  1067  							Command: []string{"/bin/thing"},
  1068  							Args:    []string{"some", "args"},
  1069  							Env: []coreapi.EnvVar{
  1070  								{Name: "MY_ENV", Value: "rocks"},
  1071  							},
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  		},
  1077  		{
  1078  			podName: "pod",
  1079  			buildID: "blabla",
  1080  			labels:  map[string]string{"needstobe": "inherited"},
  1081  			pjSpec: prowapi.ProwJobSpec{
  1082  				Type:    prowapi.PresubmitJob,
  1083  				Job:     "job-name",
  1084  				Context: "job-context",
  1085  				DecorationConfig: &prowapi.DecorationConfig{
  1086  					Timeout:     &prowapi.Duration{Duration: 120 * time.Minute},
  1087  					GracePeriod: &prowapi.Duration{Duration: 10 * time.Second},
  1088  					UtilityImages: &prowapi.UtilityImages{
  1089  						CloneRefs:  "clonerefs:tag",
  1090  						InitUpload: "initupload:tag",
  1091  						Entrypoint: "entrypoint:tag",
  1092  						Sidecar:    "sidecar:tag",
  1093  					},
  1094  					GCSConfiguration: &prowapi.GCSConfiguration{
  1095  						Bucket:       "my-bucket",
  1096  						PathStrategy: "legacy",
  1097  						DefaultOrg:   "kubernetes",
  1098  						DefaultRepo:  "kubernetes",
  1099  						MediaTypes:   map[string]string{"log": "text/plain"},
  1100  					},
  1101  					// Specify K8s SA rather than cloud storage secret key.
  1102  					DefaultServiceAccountName: pStr("default-SA"),
  1103  					CookiefileSecret:          pStr("yummy/.gitcookies"),
  1104  					RunAsGroup:                pInt64(1000),
  1105  					RunAsUser:                 pInt64(1000),
  1106  					FsGroup:                   pInt64(2000),
  1107  				},
  1108  				Agent: prowapi.KubernetesAgent,
  1109  				Refs: &prowapi.Refs{
  1110  					Org:     "org-name",
  1111  					Repo:    "repo-name",
  1112  					BaseRef: "base-ref",
  1113  					BaseSHA: "base-sha",
  1114  					Pulls: []prowapi.Pull{{
  1115  						Number:  1,
  1116  						Author:  "author-name",
  1117  						SHA:     "pull-sha",
  1118  						HeadRef: "orig-branch-name",
  1119  						Title:   "pull-title",
  1120  					}},
  1121  					PathAlias: "somewhere/else",
  1122  				},
  1123  				ExtraRefs: []prowapi.Refs{},
  1124  				PodSpec: &coreapi.PodSpec{
  1125  					Containers: []coreapi.Container{
  1126  						{
  1127  							Image:   "tester",
  1128  							Command: []string{"/bin/thing"},
  1129  							Args:    []string{"some", "args"},
  1130  							Env: []coreapi.EnvVar{
  1131  								{Name: "MY_ENV", Value: "rocks"},
  1132  							},
  1133  						},
  1134  					},
  1135  				},
  1136  			},
  1137  		},
  1138  	}
  1139  
  1140  	findContainer := func(name string, pod coreapi.Pod) *coreapi.Container {
  1141  		for _, c := range pod.Spec.Containers {
  1142  			if c.Name == name {
  1143  				return &c
  1144  			}
  1145  		}
  1146  		return nil
  1147  	}
  1148  	findEnv := func(key string, container coreapi.Container) *string {
  1149  		for _, env := range container.Env {
  1150  			if env.Name == key {
  1151  				v := env.Value
  1152  				return &v
  1153  			}
  1154  
  1155  		}
  1156  		return nil
  1157  	}
  1158  
  1159  	type checker interface {
  1160  		ConfigVar() string
  1161  		LoadConfig(string) error
  1162  		Validate() error
  1163  	}
  1164  
  1165  	checkEnv := func(pod coreapi.Pod, name string, opt checker) error {
  1166  		c := findContainer(name, pod)
  1167  		if c == nil {
  1168  			return nil
  1169  		}
  1170  		env := opt.ConfigVar()
  1171  		val := findEnv(env, *c)
  1172  		if val == nil {
  1173  			return fmt.Errorf("missing %s env var", env)
  1174  		}
  1175  		if err := opt.LoadConfig(*val); err != nil {
  1176  			return fmt.Errorf("load: %w", err)
  1177  		}
  1178  		if err := opt.Validate(); err != nil {
  1179  			return fmt.Errorf("validate: %w", err)
  1180  		}
  1181  		return nil
  1182  	}
  1183  
  1184  	for i, test := range tests {
  1185  		t.Run(strconv.Itoa(i), func(t *testing.T) {
  1186  			pj := prowapi.ProwJob{ObjectMeta: metav1.ObjectMeta{Name: test.podName, Labels: test.labels}, Spec: test.pjSpec, Status: test.pjStatus}
  1187  			pj.Status.BuildID = test.buildID
  1188  			got, err := ProwJobToPod(pj)
  1189  			if err != nil {
  1190  				t.Errorf("unexpected error: %v", err)
  1191  			}
  1192  			fixtureName := filepath.Join("testdata", fmt.Sprintf("%s.yaml", strings.ReplaceAll(t.Name(), "/", "_")))
  1193  			if os.Getenv("UPDATE") != "" {
  1194  				marshalled, err := yaml.Marshal(got)
  1195  				if err != nil {
  1196  					t.Fatalf("failed to marhsal pod: %v", err)
  1197  				}
  1198  				if err := os.WriteFile(fixtureName, marshalled, 0644); err != nil {
  1199  					t.Errorf("failed to update fixture: %v", err)
  1200  				}
  1201  			}
  1202  			expectedRaw, err := os.ReadFile(fixtureName)
  1203  			if err != nil {
  1204  				t.Fatalf("failed to read fixture: %v", err)
  1205  			}
  1206  			expected := &coreapi.Pod{}
  1207  			if err := yaml.Unmarshal(expectedRaw, expected); err != nil {
  1208  				t.Fatalf("failed to unmarshal fixture: %v", err)
  1209  			}
  1210  			if !equality.Semantic.DeepEqual(got, expected) {
  1211  				t.Errorf("unexpected pod diff:\n%s. You can update the fixtures by running this test with UPDATE=true if this is expected.", diff.ObjectReflectDiff(expected, got))
  1212  			}
  1213  			if err := checkEnv(*got, "sidecar", sidecar.NewOptions()); err != nil {
  1214  				t.Errorf("bad sidecar env: %v", err)
  1215  			}
  1216  			if err := checkEnv(*got, "initupload", initupload.NewOptions()); err != nil {
  1217  				t.Errorf("bad clonerefs env: %v", err)
  1218  			}
  1219  			if err := checkEnv(*got, "clonerefs", &clonerefs.Options{}); err != nil {
  1220  				t.Errorf("bad clonerefs env: %v", err)
  1221  			}
  1222  			if test.pjSpec.DecorationConfig != nil { // all jobs get a test container
  1223  				// But only decorated jobs need valid entrypoint options
  1224  				if err := checkEnv(*got, "test", entrypoint.NewOptions()); err != nil {
  1225  					t.Errorf("bad test entrypoint: %v", err)
  1226  				}
  1227  			}
  1228  		})
  1229  	}
  1230  }
  1231  
  1232  func TestProwJobToPod_setsTerminationGracePeriodSeconds(t *testing.T) {
  1233  	testCases := []struct {
  1234  		name                                  string
  1235  		prowjob                               *prowapi.ProwJob
  1236  		expectedTerminationGracePeriodSeconds int64
  1237  	}{
  1238  		{
  1239  			name: "GracePeriodSeconds from decoration config",
  1240  			prowjob: &prowapi.ProwJob{
  1241  				Spec: prowapi.ProwJobSpec{
  1242  					PodSpec: &coreapi.PodSpec{Containers: []coreapi.Container{{}}},
  1243  					DecorationConfig: &prowapi.DecorationConfig{
  1244  						UtilityImages: &prowapi.UtilityImages{},
  1245  						GracePeriod:   &prowapi.Duration{Duration: 10 * time.Second},
  1246  					},
  1247  				},
  1248  			},
  1249  			expectedTerminationGracePeriodSeconds: 12,
  1250  		},
  1251  		{
  1252  			name: "Existing GracePeriodSeconds is not overwritten",
  1253  			prowjob: &prowapi.ProwJob{
  1254  				Spec: prowapi.ProwJobSpec{
  1255  					PodSpec: &coreapi.PodSpec{TerminationGracePeriodSeconds: utilpointer.Int64(60), Containers: []coreapi.Container{{}}},
  1256  					DecorationConfig: &prowapi.DecorationConfig{
  1257  						UtilityImages: &prowapi.UtilityImages{},
  1258  						Timeout:       &prowapi.Duration{Duration: 10 * time.Second},
  1259  					},
  1260  				},
  1261  			},
  1262  			expectedTerminationGracePeriodSeconds: 60,
  1263  		},
  1264  	}
  1265  
  1266  	for idx := range testCases {
  1267  		tc := testCases[idx]
  1268  		t.Run(tc.name, func(t *testing.T) {
  1269  			t.Parallel()
  1270  			if err := decorate(tc.prowjob.Spec.PodSpec, tc.prowjob, map[string]string{}, ""); err != nil {
  1271  				t.Fatalf("decoration failed: %v", err)
  1272  			}
  1273  			if tc.prowjob.Spec.PodSpec.TerminationGracePeriodSeconds == nil || *tc.prowjob.Spec.PodSpec.TerminationGracePeriodSeconds != tc.expectedTerminationGracePeriodSeconds {
  1274  				t.Errorf("expected pods TerminationGracePeriodSeconds to be %d was %v", tc.expectedTerminationGracePeriodSeconds, tc.prowjob.Spec.PodSpec.TerminationGracePeriodSeconds)
  1275  			}
  1276  		})
  1277  	}
  1278  }
  1279  
  1280  func TestSidecar(t *testing.T) {
  1281  	var testCases = []struct {
  1282  		name                                    string
  1283  		config                                  *prowapi.DecorationConfig
  1284  		gcsOptions                              gcsupload.Options
  1285  		blobStorageMounts                       []coreapi.VolumeMount
  1286  		logMount                                coreapi.VolumeMount
  1287  		outputMount                             *coreapi.VolumeMount
  1288  		encodedJobSpec                          string
  1289  		requirePassingEntries, ignoreInterrupts bool
  1290  		secretVolumeMounts                      []coreapi.VolumeMount
  1291  		wrappers                                []wrapper.Options
  1292  	}{
  1293  		{
  1294  			name: "basic case",
  1295  			config: &prowapi.DecorationConfig{
  1296  				UtilityImages: &prowapi.UtilityImages{Sidecar: "sidecar-image"},
  1297  			},
  1298  			gcsOptions: gcsupload.Options{
  1299  				Items:            []string{"first", "second"},
  1300  				GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "bucket"},
  1301  			},
  1302  			blobStorageMounts:     []coreapi.VolumeMount{{Name: "blob", MountPath: "/blob"}},
  1303  			logMount:              coreapi.VolumeMount{Name: "logs", MountPath: "/logs"},
  1304  			outputMount:           &coreapi.VolumeMount{Name: "outputs", MountPath: "/outputs"},
  1305  			encodedJobSpec:        "spec",
  1306  			requirePassingEntries: true,
  1307  			ignoreInterrupts:      true,
  1308  			wrappers:              []wrapper.Options{{Args: []string{"yes"}}},
  1309  		},
  1310  		{
  1311  			name: "with secrets",
  1312  			config: &prowapi.DecorationConfig{
  1313  				UtilityImages: &prowapi.UtilityImages{Sidecar: "sidecar-image"},
  1314  			},
  1315  			gcsOptions: gcsupload.Options{
  1316  				Items:            []string{"first", "second"},
  1317  				GCSConfiguration: &prowapi.GCSConfiguration{Bucket: "bucket"},
  1318  			},
  1319  			blobStorageMounts:     []coreapi.VolumeMount{{Name: "blob", MountPath: "/blob"}},
  1320  			logMount:              coreapi.VolumeMount{Name: "logs", MountPath: "/logs"},
  1321  			outputMount:           &coreapi.VolumeMount{Name: "outputs", MountPath: "/outputs"},
  1322  			encodedJobSpec:        "spec",
  1323  			requirePassingEntries: true,
  1324  			ignoreInterrupts:      true,
  1325  			secretVolumeMounts: []coreapi.VolumeMount{
  1326  				{Name: "very", MountPath: "/very"},
  1327  				{Name: "secret", MountPath: "/secret"},
  1328  				{Name: "stuff", MountPath: "/stuff"},
  1329  			},
  1330  			wrappers: []wrapper.Options{{Args: []string{"yes"}}},
  1331  		},
  1332  	}
  1333  
  1334  	for _, testCase := range testCases {
  1335  		t.Run(testCase.name, func(t *testing.T) {
  1336  			container, err := Sidecar(
  1337  				testCase.config, testCase.gcsOptions,
  1338  				testCase.blobStorageMounts, testCase.logMount, testCase.outputMount,
  1339  				testCase.encodedJobSpec,
  1340  				testCase.requirePassingEntries, testCase.ignoreInterrupts,
  1341  				testCase.secretVolumeMounts, testCase.wrappers...,
  1342  			)
  1343  			if err != nil {
  1344  				t.Fatalf("%s: got an error from Sidecar(): %v", testCase.name, err)
  1345  			}
  1346  			testutil.CompareWithSerializedFixture(t, container)
  1347  		})
  1348  	}
  1349  }
  1350  
  1351  func TestDecorate(t *testing.T) {
  1352  	gCSCredentialsSecret := "gcs-secret"
  1353  	defaultServiceAccountName := "default-sa"
  1354  	censor := true
  1355  	ignoreInterrupts := true
  1356  	resourcePtr := func(s string) *resource.Quantity {
  1357  		q := resource.MustParse(s)
  1358  		return &q
  1359  	}
  1360  	var testCases = []struct {
  1361  		name      string
  1362  		spec      *coreapi.PodSpec
  1363  		pj        *prowapi.ProwJob
  1364  		rawEnv    map[string]string
  1365  		outputDir string
  1366  	}{
  1367  		{
  1368  			name: "basic happy case",
  1369  			spec: &coreapi.PodSpec{
  1370  				Volumes: []coreapi.Volume{
  1371  					{Name: "secret", VolumeSource: coreapi.VolumeSource{Secret: &coreapi.SecretVolumeSource{SecretName: "secretname"}}},
  1372  				},
  1373  				Containers: []coreapi.Container{
  1374  					{Name: "test", Command: []string{"/bin/ls"}, Args: []string{"-l", "-a"}, VolumeMounts: []coreapi.VolumeMount{{Name: "secret", MountPath: "/secret"}}},
  1375  				},
  1376  				ServiceAccountName: "tester",
  1377  			},
  1378  			pj: &prowapi.ProwJob{
  1379  				Spec: prowapi.ProwJobSpec{
  1380  					DecorationConfig: &prowapi.DecorationConfig{
  1381  						Timeout:     &prowapi.Duration{Duration: time.Minute},
  1382  						GracePeriod: &prowapi.Duration{Duration: time.Hour},
  1383  						UtilityImages: &prowapi.UtilityImages{
  1384  							CloneRefs:  "cloneimage",
  1385  							InitUpload: "initimage",
  1386  							Entrypoint: "entrypointimage",
  1387  							Sidecar:    "sidecarimage",
  1388  						},
  1389  						Resources: &prowapi.Resources{
  1390  							CloneRefs:       &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1391  							InitUpload:      &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1392  							PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1393  							Sidecar:         &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1394  						},
  1395  						GCSConfiguration: &prowapi.GCSConfiguration{
  1396  							Bucket:       "bucket",
  1397  							PathStrategy: "single",
  1398  							DefaultOrg:   "org",
  1399  							DefaultRepo:  "repo",
  1400  						},
  1401  						GCSCredentialsSecret:      &gCSCredentialsSecret,
  1402  						DefaultServiceAccountName: &defaultServiceAccountName,
  1403  					},
  1404  					Refs: &prowapi.Refs{
  1405  						Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234",
  1406  						Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}},
  1407  					},
  1408  					ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}},
  1409  				},
  1410  			},
  1411  			rawEnv: map[string]string{"custom": "env"},
  1412  		},
  1413  		{
  1414  			name: "enforcing memory limit",
  1415  			spec: &coreapi.PodSpec{
  1416  				Containers: []coreapi.Container{
  1417  					{
  1418  						Name:    "test",
  1419  						Command: []string{"/bin/ls"},
  1420  						Args:    []string{"-l", "-a"},
  1421  						Resources: coreapi.ResourceRequirements{
  1422  							Requests: coreapi.ResourceList{
  1423  								"memory": resource.MustParse("8Gi"),
  1424  							},
  1425  							Limits: coreapi.ResourceList{
  1426  								"memory": resource.MustParse("100Gi"),
  1427  							},
  1428  						},
  1429  					},
  1430  				},
  1431  				ServiceAccountName: "tester",
  1432  			},
  1433  			pj: &prowapi.ProwJob{
  1434  				Spec: prowapi.ProwJobSpec{
  1435  					DecorationConfig: &prowapi.DecorationConfig{
  1436  						Timeout:     &prowapi.Duration{Duration: time.Minute},
  1437  						GracePeriod: &prowapi.Duration{Duration: time.Hour},
  1438  						UtilityImages: &prowapi.UtilityImages{
  1439  							CloneRefs:  "cloneimage",
  1440  							InitUpload: "initimage",
  1441  							Entrypoint: "entrypointimage",
  1442  							Sidecar:    "sidecarimage",
  1443  						},
  1444  						Resources: &prowapi.Resources{
  1445  							CloneRefs:       &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1446  							InitUpload:      &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1447  							PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1448  							Sidecar:         &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1449  						},
  1450  						GCSConfiguration: &prowapi.GCSConfiguration{
  1451  							Bucket:       "bucket",
  1452  							PathStrategy: "single",
  1453  							DefaultOrg:   "org",
  1454  							DefaultRepo:  "repo",
  1455  						},
  1456  						GCSCredentialsSecret:        &gCSCredentialsSecret,
  1457  						DefaultServiceAccountName:   &defaultServiceAccountName,
  1458  						SetLimitEqualsMemoryRequest: utilpointer.Bool(true),
  1459  					},
  1460  					Refs: &prowapi.Refs{
  1461  						Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234",
  1462  						Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}},
  1463  					},
  1464  					ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}},
  1465  				},
  1466  			},
  1467  			rawEnv: map[string]string{"custom": "env"},
  1468  		},
  1469  		{
  1470  			name: "default memory request",
  1471  			spec: &coreapi.PodSpec{
  1472  				Containers: []coreapi.Container{
  1473  					{
  1474  						Name:    "test",
  1475  						Command: []string{"/bin/ls"},
  1476  						Args:    []string{"-l", "-a"},
  1477  						Resources: coreapi.ResourceRequirements{
  1478  							Requests: coreapi.ResourceList{
  1479  								"memory": resource.MustParse("8Gi"),
  1480  							},
  1481  							Limits: coreapi.ResourceList{
  1482  								"memory": resource.MustParse("100Gi"),
  1483  							},
  1484  						},
  1485  					},
  1486  					{
  1487  						Name:    "test2",
  1488  						Command: []string{"/bin/ls"},
  1489  						Args:    []string{"-l", "-a"},
  1490  					},
  1491  				},
  1492  				ServiceAccountName: "tester",
  1493  			},
  1494  			pj: &prowapi.ProwJob{
  1495  				Spec: prowapi.ProwJobSpec{
  1496  					DecorationConfig: &prowapi.DecorationConfig{
  1497  						Timeout:     &prowapi.Duration{Duration: time.Minute},
  1498  						GracePeriod: &prowapi.Duration{Duration: time.Hour},
  1499  						UtilityImages: &prowapi.UtilityImages{
  1500  							CloneRefs:  "cloneimage",
  1501  							InitUpload: "initimage",
  1502  							Entrypoint: "entrypointimage",
  1503  							Sidecar:    "sidecarimage",
  1504  						},
  1505  						Resources: &prowapi.Resources{
  1506  							CloneRefs:       &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1507  							InitUpload:      &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1508  							PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1509  							Sidecar:         &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1510  						},
  1511  						GCSConfiguration: &prowapi.GCSConfiguration{
  1512  							Bucket:       "bucket",
  1513  							PathStrategy: "single",
  1514  							DefaultOrg:   "org",
  1515  							DefaultRepo:  "repo",
  1516  						},
  1517  						GCSCredentialsSecret:        &gCSCredentialsSecret,
  1518  						DefaultServiceAccountName:   &defaultServiceAccountName,
  1519  						SetLimitEqualsMemoryRequest: utilpointer.Bool(true),
  1520  						DefaultMemoryRequest:        resourcePtr("4Gi"),
  1521  					},
  1522  					Refs: &prowapi.Refs{
  1523  						Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234",
  1524  						Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}},
  1525  					},
  1526  					ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}},
  1527  				},
  1528  			},
  1529  			rawEnv: map[string]string{"custom": "env"},
  1530  		},
  1531  		{
  1532  			name: "censor secrets in sidecar",
  1533  			spec: &coreapi.PodSpec{
  1534  				Volumes: []coreapi.Volume{
  1535  					{Name: "secret", VolumeSource: coreapi.VolumeSource{Secret: &coreapi.SecretVolumeSource{SecretName: "secretname"}}},
  1536  				},
  1537  				Containers: []coreapi.Container{
  1538  					{Name: "test", Command: []string{"/bin/ls"}, Args: []string{"-l", "-a"}, VolumeMounts: []coreapi.VolumeMount{{Name: "secret", MountPath: "/secret"}}},
  1539  				},
  1540  				ServiceAccountName: "tester",
  1541  			},
  1542  			pj: &prowapi.ProwJob{
  1543  				Spec: prowapi.ProwJobSpec{
  1544  					DecorationConfig: &prowapi.DecorationConfig{
  1545  						Timeout:     &prowapi.Duration{Duration: time.Minute},
  1546  						GracePeriod: &prowapi.Duration{Duration: time.Hour},
  1547  						UtilityImages: &prowapi.UtilityImages{
  1548  							CloneRefs:  "cloneimage",
  1549  							InitUpload: "initimage",
  1550  							Entrypoint: "entrypointimage",
  1551  							Sidecar:    "sidecarimage",
  1552  						},
  1553  						Resources: &prowapi.Resources{
  1554  							CloneRefs:       &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1555  							InitUpload:      &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1556  							PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1557  							Sidecar:         &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1558  						},
  1559  						GCSConfiguration: &prowapi.GCSConfiguration{
  1560  							Bucket:       "bucket",
  1561  							PathStrategy: "single",
  1562  							DefaultOrg:   "org",
  1563  							DefaultRepo:  "repo",
  1564  						},
  1565  						GCSCredentialsSecret:      &gCSCredentialsSecret,
  1566  						DefaultServiceAccountName: &defaultServiceAccountName,
  1567  						CensorSecrets:             &censor,
  1568  					},
  1569  					Refs: &prowapi.Refs{
  1570  						Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234",
  1571  						Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}},
  1572  					},
  1573  					ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}},
  1574  				},
  1575  			},
  1576  			rawEnv: map[string]string{"custom": "env"},
  1577  		},
  1578  		{
  1579  			name: "ignore interrupts in sidecar",
  1580  			spec: &coreapi.PodSpec{
  1581  				Volumes: []coreapi.Volume{
  1582  					{Name: "secret", VolumeSource: coreapi.VolumeSource{Secret: &coreapi.SecretVolumeSource{SecretName: "secretname"}}},
  1583  				},
  1584  				Containers: []coreapi.Container{
  1585  					{Name: "test", Command: []string{"/bin/ls"}, Args: []string{"-l", "-a"}, VolumeMounts: []coreapi.VolumeMount{{Name: "secret", MountPath: "/secret"}}},
  1586  				},
  1587  				ServiceAccountName: "tester",
  1588  			},
  1589  			pj: &prowapi.ProwJob{
  1590  				Spec: prowapi.ProwJobSpec{
  1591  					DecorationConfig: &prowapi.DecorationConfig{
  1592  						Timeout:                 &prowapi.Duration{Duration: time.Minute},
  1593  						GracePeriod:             &prowapi.Duration{Duration: time.Hour},
  1594  						UploadIgnoresInterrupts: &ignoreInterrupts,
  1595  						UtilityImages: &prowapi.UtilityImages{
  1596  							CloneRefs:  "cloneimage",
  1597  							InitUpload: "initimage",
  1598  							Entrypoint: "entrypointimage",
  1599  							Sidecar:    "sidecarimage",
  1600  						},
  1601  						Resources: &prowapi.Resources{
  1602  							CloneRefs:       &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1603  							InitUpload:      &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1604  							PlaceEntrypoint: &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1605  							Sidecar:         &coreapi.ResourceRequirements{Limits: coreapi.ResourceList{"cpu": resource.Quantity{}}, Requests: coreapi.ResourceList{"memory": resource.Quantity{}}},
  1606  						},
  1607  						GCSConfiguration: &prowapi.GCSConfiguration{
  1608  							Bucket:       "bucket",
  1609  							PathStrategy: "single",
  1610  							DefaultOrg:   "org",
  1611  							DefaultRepo:  "repo",
  1612  						},
  1613  						GCSCredentialsSecret:      &gCSCredentialsSecret,
  1614  						DefaultServiceAccountName: &defaultServiceAccountName,
  1615  					},
  1616  					Refs: &prowapi.Refs{
  1617  						Org: "org", Repo: "repo", BaseRef: "main", BaseSHA: "abcd1234",
  1618  						Pulls: []prowapi.Pull{{Number: 1, SHA: "aksdjhfkds"}},
  1619  					},
  1620  					ExtraRefs: []prowapi.Refs{{Org: "other", Repo: "something", BaseRef: "release", BaseSHA: "sldijfsd"}},
  1621  				},
  1622  			},
  1623  			rawEnv: map[string]string{"custom": "env"},
  1624  		},
  1625  	}
  1626  
  1627  	for _, testCase := range testCases {
  1628  		t.Run(testCase.name, func(t *testing.T) {
  1629  			if err := decorate(testCase.spec, testCase.pj, testCase.rawEnv, testCase.outputDir); err != nil {
  1630  				t.Fatalf("got an error from decorate(): %v", err)
  1631  			}
  1632  			testutil.CompareWithSerializedFixture(t, testCase.spec)
  1633  		})
  1634  	}
  1635  }