github.com/abayer/test-infra@v0.0.5/prow/pod-utils/decorate/podspec.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  	"path"
    22  	"sort"
    23  
    24  	"k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	"k8s.io/test-infra/prow/clonerefs"
    28  	"k8s.io/test-infra/prow/entrypoint"
    29  	"k8s.io/test-infra/prow/gcsupload"
    30  	"k8s.io/test-infra/prow/initupload"
    31  	"k8s.io/test-infra/prow/kube"
    32  	"k8s.io/test-infra/prow/pod-utils/clone"
    33  	"k8s.io/test-infra/prow/pod-utils/downwardapi"
    34  	"k8s.io/test-infra/prow/pod-utils/wrapper"
    35  	"k8s.io/test-infra/prow/sidecar"
    36  )
    37  
    38  const (
    39  	logMountName            = "logs"
    40  	logMountPath            = "/logs"
    41  	artifactsEnv            = "ARTIFACTS"
    42  	artifactsPath           = logMountPath + "/artifacts"
    43  	codeMountName           = "code"
    44  	codeMountPath           = "/home/prow/go"
    45  	gopathEnv               = "GOPATH"
    46  	toolsMountName          = "tools"
    47  	toolsMountPath          = "/tools"
    48  	gcsCredentialsMountName = "gcs-credentials"
    49  	gcsCredentialsMountPath = "/secrets/gcs"
    50  	sshKeysMountNamePrefix  = "ssh-keys"
    51  	sshKeysMountPathPrefix  = "/secrets/ssh"
    52  )
    53  
    54  // Labels returns a string slice with label consts from kube.
    55  func Labels() []string {
    56  	return []string{kube.ProwJobTypeLabel, kube.CreatedByProw, kube.ProwJobIDLabel}
    57  }
    58  
    59  // VolumeMounts returns a string slice with *MountName consts in it.
    60  func VolumeMounts() []string {
    61  	return []string{logMountName, codeMountName, toolsMountName, gcsCredentialsMountName}
    62  }
    63  
    64  // VolumeMountPaths returns a string slice with *MountPath consts in it.
    65  func VolumeMountPaths() []string {
    66  	return []string{logMountPath, codeMountPath, toolsMountPath, gcsCredentialsMountPath}
    67  }
    68  
    69  // ProwJobToPod converts a ProwJob to a Pod that will run the tests.
    70  func ProwJobToPod(pj kube.ProwJob, buildID string) (*v1.Pod, error) {
    71  	if pj.Spec.PodSpec == nil {
    72  		return nil, fmt.Errorf("prowjob %q lacks a pod spec", pj.Name)
    73  	}
    74  
    75  	rawEnv, err := downwardapi.EnvForSpec(downwardapi.NewJobSpec(pj.Spec, buildID, pj.Name))
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	spec := pj.Spec.PodSpec.DeepCopy()
    81  	spec.RestartPolicy = "Never"
    82  	spec.Containers[0].Name = kube.TestContainerName
    83  
    84  	if pj.Spec.DecorationConfig == nil {
    85  		spec.Containers[0].Env = append(spec.Containers[0].Env, kubeEnv(rawEnv)...)
    86  	} else {
    87  		if err := decorate(spec, &pj, rawEnv); err != nil {
    88  			return nil, fmt.Errorf("error decorating podspec: %v", err)
    89  		}
    90  	}
    91  
    92  	podLabels := make(map[string]string)
    93  	for k, v := range pj.ObjectMeta.Labels {
    94  		podLabels[k] = v
    95  	}
    96  	podLabels[kube.CreatedByProw] = "true"
    97  	podLabels[kube.ProwJobTypeLabel] = string(pj.Spec.Type)
    98  	podLabels[kube.ProwJobIDLabel] = pj.ObjectMeta.Name
    99  	return &v1.Pod{
   100  		ObjectMeta: metav1.ObjectMeta{
   101  			Name:   pj.ObjectMeta.Name,
   102  			Labels: podLabels,
   103  			Annotations: map[string]string{
   104  				kube.ProwJobAnnotation: pj.Spec.Job,
   105  			},
   106  		},
   107  		Spec: *spec,
   108  	}, nil
   109  }
   110  
   111  func decorate(spec *kube.PodSpec, pj *kube.ProwJob, rawEnv map[string]string) error {
   112  	rawEnv[artifactsEnv] = artifactsPath
   113  	rawEnv[gopathEnv] = codeMountPath
   114  	logMount := kube.VolumeMount{
   115  		Name:      logMountName,
   116  		MountPath: logMountPath,
   117  	}
   118  	logVolume := kube.Volume{
   119  		Name: logMountName,
   120  		VolumeSource: kube.VolumeSource{
   121  			EmptyDir: &kube.EmptyDirVolumeSource{},
   122  		},
   123  	}
   124  
   125  	codeMount := kube.VolumeMount{
   126  		Name:      codeMountName,
   127  		MountPath: codeMountPath,
   128  	}
   129  	codeVolume := kube.Volume{
   130  		Name: codeMountName,
   131  		VolumeSource: kube.VolumeSource{
   132  			EmptyDir: &kube.EmptyDirVolumeSource{},
   133  		},
   134  	}
   135  
   136  	toolsMount := kube.VolumeMount{
   137  		Name:      toolsMountName,
   138  		MountPath: toolsMountPath,
   139  	}
   140  	toolsVolume := kube.Volume{
   141  		Name: toolsMountName,
   142  		VolumeSource: kube.VolumeSource{
   143  			EmptyDir: &kube.EmptyDirVolumeSource{},
   144  		},
   145  	}
   146  
   147  	gcsCredentialsMount := kube.VolumeMount{
   148  		Name:      gcsCredentialsMountName,
   149  		MountPath: gcsCredentialsMountPath,
   150  	}
   151  	gcsCredentialsVolume := kube.Volume{
   152  		Name: gcsCredentialsMountName,
   153  		VolumeSource: kube.VolumeSource{
   154  			Secret: &kube.SecretSource{
   155  				SecretName: pj.Spec.DecorationConfig.GCSCredentialsSecret,
   156  			},
   157  		},
   158  	}
   159  
   160  	var sshKeysVolumes []kube.Volume
   161  	var cloneLog string
   162  	var refs []*kube.Refs
   163  	if pj.Spec.Refs != nil {
   164  		refs = append(refs, pj.Spec.Refs)
   165  	}
   166  	refs = append(refs, pj.Spec.ExtraRefs...)
   167  	willCloneRefs := len(refs) > 0 && !pj.Spec.DecorationConfig.SkipCloning
   168  	if willCloneRefs {
   169  		var sshKeyMode int32 = 0400 // this is octal, so symbolic ref is `u+r`
   170  		var sshKeysMounts []kube.VolumeMount
   171  		var sshKeyPaths []string
   172  		for _, secret := range pj.Spec.DecorationConfig.SSHKeySecrets {
   173  			name := fmt.Sprintf("%s-%s", sshKeysMountNamePrefix, secret)
   174  			keyPath := path.Join(sshKeysMountPathPrefix, secret)
   175  			sshKeyPaths = append(sshKeyPaths, keyPath)
   176  			sshKeysMounts = append(sshKeysMounts, kube.VolumeMount{
   177  				Name:      name,
   178  				MountPath: keyPath,
   179  				ReadOnly:  true,
   180  			})
   181  			sshKeysVolumes = append(sshKeysVolumes, kube.Volume{
   182  				Name: name,
   183  				VolumeSource: kube.VolumeSource{
   184  					Secret: &kube.SecretSource{
   185  						SecretName:  secret,
   186  						DefaultMode: &sshKeyMode,
   187  					},
   188  				},
   189  			})
   190  		}
   191  
   192  		cloneLog = fmt.Sprintf("%s/clone.json", logMountPath)
   193  		cloneConfigEnv, err := clonerefs.Encode(clonerefs.Options{
   194  			SrcRoot:      codeMountPath,
   195  			Log:          cloneLog,
   196  			GitUserName:  clonerefs.DefaultGitUserName,
   197  			GitUserEmail: clonerefs.DefaultGitUserEmail,
   198  			GitRefs:      refs,
   199  			KeyFiles:     sshKeyPaths,
   200  		})
   201  		if err != nil {
   202  			return fmt.Errorf("could not encode clone configuration as JSON: %v", err)
   203  		}
   204  
   205  		spec.InitContainers = append(spec.InitContainers, kube.Container{
   206  			Name:         "clonerefs",
   207  			Image:        pj.Spec.DecorationConfig.UtilityImages.CloneRefs,
   208  			Command:      []string{"/clonerefs"},
   209  			Env:          kubeEnv(map[string]string{clonerefs.JSONConfigEnvVar: cloneConfigEnv}),
   210  			VolumeMounts: append([]kube.VolumeMount{logMount, codeMount}, sshKeysMounts...),
   211  		})
   212  	}
   213  	gcsOptions := gcsupload.Options{
   214  		// TODO: pass the artifact dir here too once we figure that out
   215  		GCSConfiguration:   pj.Spec.DecorationConfig.GCSConfiguration,
   216  		GcsCredentialsFile: fmt.Sprintf("%s/service-account.json", gcsCredentialsMountPath),
   217  		DryRun:             false,
   218  	}
   219  
   220  	initUploadOptions := initupload.Options{
   221  		Options: &gcsOptions,
   222  	}
   223  	if willCloneRefs {
   224  		initUploadOptions.Log = cloneLog
   225  	}
   226  	initUploadConfigEnv, err := initupload.Encode(initUploadOptions)
   227  	if err != nil {
   228  		return fmt.Errorf("could not encode initupload configuration as JSON: %v", err)
   229  	}
   230  
   231  	entrypointLocation := fmt.Sprintf("%s/entrypoint", toolsMountPath)
   232  
   233  	spec.InitContainers = append(spec.InitContainers,
   234  		kube.Container{
   235  			Name:    "initupload",
   236  			Image:   pj.Spec.DecorationConfig.UtilityImages.InitUpload,
   237  			Command: []string{"/initupload"},
   238  			Env: kubeEnv(map[string]string{
   239  				initupload.JSONConfigEnvVar: initUploadConfigEnv,
   240  				downwardapi.JobSpecEnv:      rawEnv[downwardapi.JobSpecEnv], // TODO: shouldn't need this?
   241  			}),
   242  			VolumeMounts: []kube.VolumeMount{logMount, gcsCredentialsMount},
   243  		},
   244  		kube.Container{
   245  			Name:         "place-tools",
   246  			Image:        pj.Spec.DecorationConfig.UtilityImages.Entrypoint,
   247  			Command:      []string{"/bin/cp"},
   248  			Args:         []string{"/entrypoint", entrypointLocation},
   249  			VolumeMounts: []kube.VolumeMount{toolsMount},
   250  		},
   251  	)
   252  
   253  	wrapperOptions := wrapper.Options{
   254  		ProcessLog: fmt.Sprintf("%s/process-log.txt", logMountPath),
   255  		MarkerFile: fmt.Sprintf("%s/marker-file.txt", logMountPath),
   256  	}
   257  	entrypointConfigEnv, err := entrypoint.Encode(entrypoint.Options{
   258  		Args:        append(spec.Containers[0].Command, spec.Containers[0].Args...),
   259  		Options:     &wrapperOptions,
   260  		Timeout:     pj.Spec.DecorationConfig.Timeout,
   261  		GracePeriod: pj.Spec.DecorationConfig.GracePeriod,
   262  		ArtifactDir: artifactsPath,
   263  	})
   264  	if err != nil {
   265  		return fmt.Errorf("could not encode entrypoint configuration as JSON: %v", err)
   266  	}
   267  	allEnv := rawEnv
   268  	allEnv[entrypoint.JSONConfigEnvVar] = entrypointConfigEnv
   269  
   270  	spec.Containers[0].Command = []string{entrypointLocation}
   271  	spec.Containers[0].Args = []string{}
   272  	spec.Containers[0].Env = append(spec.Containers[0].Env, kubeEnv(allEnv)...)
   273  	spec.Containers[0].VolumeMounts = append(spec.Containers[0].VolumeMounts, logMount, toolsMount)
   274  
   275  	gcsOptions.Items = append(gcsOptions.Items, artifactsPath)
   276  	sidecarConfigEnv, err := sidecar.Encode(sidecar.Options{
   277  		GcsOptions:     &gcsOptions,
   278  		WrapperOptions: &wrapperOptions,
   279  	})
   280  	if err != nil {
   281  		return fmt.Errorf("could not encode sidecar configuration as JSON: %v", err)
   282  	}
   283  
   284  	spec.Containers = append(spec.Containers, kube.Container{
   285  		Name:    "sidecar",
   286  		Image:   pj.Spec.DecorationConfig.UtilityImages.Sidecar,
   287  		Command: []string{"/sidecar"},
   288  		Env: kubeEnv(map[string]string{
   289  			sidecar.JSONConfigEnvVar: sidecarConfigEnv,
   290  			downwardapi.JobSpecEnv:   rawEnv[downwardapi.JobSpecEnv], // TODO: shouldn't need this?
   291  		}),
   292  		VolumeMounts: []kube.VolumeMount{logMount, gcsCredentialsMount},
   293  	})
   294  	spec.Volumes = append(spec.Volumes, logVolume, toolsVolume, gcsCredentialsVolume)
   295  
   296  	if willCloneRefs {
   297  		spec.Containers[0].WorkingDir = clone.PathForRefs(codeMountPath, refs[0])
   298  		spec.Containers[0].VolumeMounts = append(spec.Containers[0].VolumeMounts, codeMount)
   299  		spec.Volumes = append(spec.Volumes, append(sshKeysVolumes, codeVolume)...)
   300  	}
   301  
   302  	return nil
   303  }
   304  
   305  // kubeEnv transforms a mapping of environment variables
   306  // into their serialized form for a PodSpec, sorting by
   307  // the name of the env vars
   308  func kubeEnv(environment map[string]string) []v1.EnvVar {
   309  	var keys []string
   310  	for key := range environment {
   311  		keys = append(keys, key)
   312  	}
   313  	sort.Strings(keys)
   314  
   315  	var kubeEnvironment []v1.EnvVar
   316  	for _, key := range keys {
   317  		kubeEnvironment = append(kubeEnvironment, v1.EnvVar{
   318  			Name:  key,
   319  			Value: environment[key],
   320  		})
   321  	}
   322  
   323  	return kubeEnvironment
   324  }