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  }