github.com/kubeshop/testkube@v1.17.23/pkg/cronjob/client.go (about) 1 package cronjob 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "strings" 8 9 "go.uber.org/zap" 10 v1 "k8s.io/api/batch/v1" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/apimachinery/pkg/util/yaml" 13 batchv1 "k8s.io/client-go/applyconfigurations/batch/v1" 14 "k8s.io/client-go/kubernetes" 15 kyaml "sigs.k8s.io/kustomize/kyaml/yaml" 16 "sigs.k8s.io/kustomize/kyaml/yaml/merge2" 17 18 "github.com/kubeshop/testkube/pkg/k8sclient" 19 "github.com/kubeshop/testkube/pkg/log" 20 "github.com/kubeshop/testkube/pkg/utils" 21 ) 22 23 // Client data struct for managing running cron jobs 24 type Client struct { 25 ClientSet *kubernetes.Clientset 26 Log *zap.SugaredLogger 27 serviceName string 28 servicePort int 29 cronJobTemplate string 30 Namespace string 31 } 32 33 type CronJobOptions struct { 34 Schedule string 35 Resource string 36 Data string 37 Labels map[string]string 38 CronJobTemplate string 39 CronJobTemplateExtensions string 40 } 41 42 type templateParameters struct { 43 Id string 44 Name string 45 Namespace string 46 ServiceName string 47 ServicePort int 48 Schedule string 49 Resource string 50 CronJobTemplate string 51 CronJobTemplateExtensions string 52 Data string 53 Labels map[string]string 54 } 55 56 // NewClient is a method to create new cron job client 57 func NewClient(serviceName string, servicePort int, cronJobTemplate string, namespace string) (*Client, error) { 58 clientSet, err := k8sclient.ConnectToK8s() 59 if err != nil { 60 return nil, err 61 } 62 63 return &Client{ 64 ClientSet: clientSet, 65 Log: log.DefaultLogger, 66 serviceName: serviceName, 67 servicePort: servicePort, 68 cronJobTemplate: cronJobTemplate, 69 Namespace: namespace, 70 }, nil 71 } 72 73 // Get is a method to retrieve an existing cron job 74 func (c *Client) Get(name string) (*v1.CronJob, error) { 75 cronJobClient := c.ClientSet.BatchV1().CronJobs(c.Namespace) 76 ctx := context.Background() 77 78 cronJob, err := cronJobClient.Get(ctx, name, metav1.GetOptions{}) 79 if err != nil { 80 return nil, err 81 } 82 83 return cronJob, nil 84 } 85 86 // Apply is a method to create or update a cron job 87 func (c *Client) Apply(id, name string, options CronJobOptions) error { 88 template := c.cronJobTemplate 89 if options.CronJobTemplate != "" { 90 template = options.CronJobTemplate 91 } 92 93 cronJobClient := c.ClientSet.BatchV1().CronJobs(c.Namespace) 94 ctx := context.Background() 95 96 parameters := templateParameters{ 97 Id: id, 98 Name: name, 99 Namespace: c.Namespace, 100 ServiceName: c.serviceName, 101 ServicePort: c.servicePort, 102 Schedule: options.Schedule, 103 Resource: options.Resource, 104 CronJobTemplate: template, 105 CronJobTemplateExtensions: options.CronJobTemplateExtensions, 106 Data: options.Data, 107 Labels: options.Labels, 108 } 109 110 cronJobSpec, err := NewApplySpec(c.Log, parameters) 111 if err != nil { 112 return err 113 } 114 115 if _, err := cronJobClient.Apply(ctx, cronJobSpec, metav1.ApplyOptions{ 116 FieldManager: "application/apply-patch"}); err != nil { 117 return err 118 } 119 120 return nil 121 } 122 123 // UpdateLabels is a method to update an existing cron job labels 124 func (c *Client) UpdateLabels(cronJobSpec *v1.CronJob, oldLabels, newLabels map[string]string) error { 125 cronJobClient := c.ClientSet.BatchV1().CronJobs(c.Namespace) 126 ctx := context.Background() 127 128 for key := range oldLabels { 129 delete(cronJobSpec.Labels, key) 130 } 131 132 for key, value := range newLabels { 133 cronJobSpec.Labels[key] = value 134 } 135 136 if _, err := cronJobClient.Update(ctx, cronJobSpec, metav1.UpdateOptions{}); err != nil { 137 return err 138 } 139 140 return nil 141 } 142 143 // Delete is a method to delete an existing cron job 144 func (c *Client) Delete(name string) error { 145 cronJobClient := c.ClientSet.BatchV1().CronJobs(c.Namespace) 146 ctx := context.Background() 147 148 if err := cronJobClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil { 149 return err 150 } 151 152 return nil 153 } 154 155 // DeleteAll is a method to delete all existing cron jobs 156 func (c *Client) DeleteAll(resource, selector string) error { 157 cronJobClient := c.ClientSet.BatchV1().CronJobs(c.Namespace) 158 ctx := context.Background() 159 160 filter := fmt.Sprintf("testkube=%s", resource) 161 if selector != "" { 162 filter += "," + selector 163 } 164 165 if err := cronJobClient.DeleteCollection(ctx, metav1.DeleteOptions{}, 166 metav1.ListOptions{LabelSelector: filter}); err != nil { 167 return err 168 } 169 170 return nil 171 } 172 173 // NewApplySpec is a method to return cron job apply spec 174 func NewApplySpec(log *zap.SugaredLogger, parameters templateParameters) (*batchv1.CronJobApplyConfiguration, error) { 175 tmpl, err := utils.NewTemplate("cronJob").Parse(parameters.CronJobTemplate) 176 if err != nil { 177 return nil, fmt.Errorf("creating cron job spec from options.CronJobTemplate error: %w", err) 178 } 179 180 parameters.Data = strings.ReplaceAll(parameters.Data, "'", "''''") 181 var buffer bytes.Buffer 182 if err = tmpl.ExecuteTemplate(&buffer, "cronJob", parameters); err != nil { 183 return nil, fmt.Errorf("executing cron job spec template: %w", err) 184 } 185 186 var cronJob batchv1.CronJobApplyConfiguration 187 cronJobSpec := buffer.String() 188 if parameters.CronJobTemplateExtensions != "" { 189 tmplExt, err := utils.NewTemplate("cronJobExt").Parse(parameters.CronJobTemplateExtensions) 190 if err != nil { 191 return nil, fmt.Errorf("creating cron job extensions spec from default template error: %w", err) 192 } 193 194 var bufferExt bytes.Buffer 195 if err = tmplExt.ExecuteTemplate(&bufferExt, "cronJobExt", parameters); err != nil { 196 return nil, fmt.Errorf("executing cron job extensions spec default template: %w", err) 197 } 198 199 if cronJobSpec, err = merge2.MergeStrings(bufferExt.String(), cronJobSpec, false, kyaml.MergeOptions{}); err != nil { 200 return nil, fmt.Errorf("merging cron job spec templates: %w", err) 201 } 202 } 203 204 log.Debug("Cron job specification", cronJobSpec) 205 decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(cronJobSpec), len(cronJobSpec)) 206 if err := decoder.Decode(&cronJob); err != nil { 207 return nil, fmt.Errorf("decoding cron job spec error: %w", err) 208 } 209 210 for key, value := range parameters.Labels { 211 cronJob.Labels[key] = value 212 } 213 214 return &cronJob, nil 215 } 216 217 // GetMetadataName returns cron job metadata name 218 func GetMetadataName(name, resource string) string { 219 return fmt.Sprintf("%s-%s", name, resource) 220 }