github.com/kubeshop/testkube@v1.17.23/pkg/tcl/testworkflowstcl/testworkflowprocessor/intermediate.go (about) 1 // Copyright 2024 Testkube. 2 // 3 // Licensed as a Testkube Pro file under the Testkube Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt 8 9 package testworkflowprocessor 10 11 import ( 12 "errors" 13 14 corev1 "k8s.io/api/core/v1" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 17 testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" 18 "github.com/kubeshop/testkube/internal/common" 19 "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/testworkflowresolver" 20 ) 21 22 const maxConfigMapFileSize = 950 * 1024 23 24 //go:generate mockgen -destination=./mock_intermediate.go -package=testworkflowprocessor "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/testworkflowprocessor" Intermediate 25 type Intermediate interface { 26 RefCounter 27 28 ContainerDefaults() Container 29 PodConfig() testworkflowsv1.PodConfig 30 JobConfig() testworkflowsv1.JobConfig 31 32 ConfigMaps() []corev1.ConfigMap 33 Secrets() []corev1.Secret 34 Volumes() []corev1.Volume 35 36 AppendJobConfig(cfg *testworkflowsv1.JobConfig) Intermediate 37 AppendPodConfig(cfg *testworkflowsv1.PodConfig) Intermediate 38 39 AddConfigMap(configMap corev1.ConfigMap) Intermediate 40 AddSecret(secret corev1.Secret) Intermediate 41 AddVolume(volume corev1.Volume) Intermediate 42 43 AddEmptyDirVolume(source *corev1.EmptyDirVolumeSource, mountPath string) corev1.VolumeMount 44 45 AddTextFile(file string) (corev1.VolumeMount, error) 46 AddBinaryFile(file []byte) (corev1.VolumeMount, error) 47 } 48 49 type intermediate struct { 50 refCounter 51 52 // Routine 53 Root GroupStage `expr:"include"` 54 Container Container `expr:"include"` 55 56 // Job & Pod resources & data 57 Pod testworkflowsv1.PodConfig `expr:"include"` 58 Job testworkflowsv1.JobConfig `expr:"include"` 59 60 // Actual Kubernetes resources to use 61 Secs []corev1.Secret `expr:"force"` 62 Cfgs []corev1.ConfigMap `expr:"force"` 63 64 // Storing files 65 currentConfigMapStorage *corev1.ConfigMap 66 estimatedConfigMapStorage int 67 } 68 69 func NewIntermediate() Intermediate { 70 return &intermediate{ 71 Root: NewGroupStage("", true), 72 Container: NewContainer(), 73 } 74 } 75 76 func (s *intermediate) ContainerDefaults() Container { 77 return s.Container 78 } 79 80 func (s *intermediate) JobConfig() testworkflowsv1.JobConfig { 81 return s.Job 82 } 83 84 func (s *intermediate) PodConfig() testworkflowsv1.PodConfig { 85 return s.Pod 86 } 87 88 func (s *intermediate) ConfigMaps() []corev1.ConfigMap { 89 return s.Cfgs 90 } 91 92 func (s *intermediate) Secrets() []corev1.Secret { 93 return s.Secs 94 } 95 96 func (s *intermediate) Volumes() []corev1.Volume { 97 return s.Pod.Volumes 98 } 99 100 func (s *intermediate) AppendJobConfig(cfg *testworkflowsv1.JobConfig) Intermediate { 101 s.Job = *testworkflowresolver.MergeJobConfig(&s.Job, cfg) 102 return s 103 } 104 105 func (s *intermediate) AppendPodConfig(cfg *testworkflowsv1.PodConfig) Intermediate { 106 s.Pod = *testworkflowresolver.MergePodConfig(&s.Pod, cfg) 107 return s 108 } 109 110 func (s *intermediate) AddVolume(volume corev1.Volume) Intermediate { 111 s.Pod.Volumes = append(s.Pod.Volumes, volume) 112 return s 113 } 114 115 func (s *intermediate) AddConfigMap(configMap corev1.ConfigMap) Intermediate { 116 s.Cfgs = append(s.Cfgs, configMap) 117 return s 118 } 119 120 func (s *intermediate) AddSecret(secret corev1.Secret) Intermediate { 121 s.Secs = append(s.Secs, secret) 122 return s 123 } 124 125 func (s *intermediate) AddEmptyDirVolume(source *corev1.EmptyDirVolumeSource, mountPath string) corev1.VolumeMount { 126 if source == nil { 127 source = &corev1.EmptyDirVolumeSource{} 128 } 129 ref := s.NextRef() 130 s.AddVolume(corev1.Volume{Name: ref, VolumeSource: corev1.VolumeSource{EmptyDir: source}}) 131 return corev1.VolumeMount{Name: ref, MountPath: mountPath} 132 } 133 134 // Handling files 135 136 func (s *intermediate) getInternalConfigMapStorage(size int) *corev1.ConfigMap { 137 if size > maxConfigMapFileSize { 138 return nil 139 } 140 if size+s.estimatedConfigMapStorage > maxConfigMapFileSize || s.currentConfigMapStorage == nil { 141 ref := s.NextRef() 142 s.Cfgs = append(s.Cfgs, corev1.ConfigMap{ 143 ObjectMeta: metav1.ObjectMeta{Name: "{{execution.id}}-" + ref}, 144 Immutable: common.Ptr(true), 145 Data: map[string]string{}, 146 BinaryData: map[string][]byte{}, 147 }) 148 s.currentConfigMapStorage = &s.Cfgs[len(s.Cfgs)-1] 149 s.Pod.Volumes = append(s.Pod.Volumes, corev1.Volume{ 150 Name: s.currentConfigMapStorage.Name + "-vol", 151 VolumeSource: corev1.VolumeSource{ 152 ConfigMap: &corev1.ConfigMapVolumeSource{ 153 LocalObjectReference: corev1.LocalObjectReference{Name: s.currentConfigMapStorage.Name}, 154 }, 155 }, 156 }) 157 } 158 return s.currentConfigMapStorage 159 } 160 161 func (s *intermediate) AddTextFile(file string) (corev1.VolumeMount, error) { 162 cfg := s.getInternalConfigMapStorage(len(file)) 163 if cfg == nil { 164 return corev1.VolumeMount{}, errors.New("the maximum file size is 950KiB") 165 } 166 s.estimatedConfigMapStorage += len(file) 167 ref := s.NextRef() 168 cfg.Data[ref] = file 169 return corev1.VolumeMount{ 170 Name: cfg.Name + "-vol", 171 ReadOnly: true, 172 SubPath: ref, 173 }, nil 174 } 175 176 func (s *intermediate) AddBinaryFile(file []byte) (corev1.VolumeMount, error) { 177 cfg := s.getInternalConfigMapStorage(len(file)) 178 if cfg == nil { 179 return corev1.VolumeMount{}, errors.New("the maximum file size is 950KiB") 180 } 181 s.estimatedConfigMapStorage += len(file) 182 ref := s.NextRef() 183 cfg.BinaryData[ref] = file 184 return corev1.VolumeMount{ 185 Name: cfg.Name + "-vol", 186 ReadOnly: true, 187 SubPath: ref, 188 }, nil 189 }