From 7b14a21623434606fbb6458adfe2492ff6eb7886 Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Thu, 19 Dec 2024 11:40:55 +1100 Subject: [PATCH] feat: cronjob configureable timeouts --- internal/generator/services.go | 16 ++++ internal/generator/services_test.go | 85 ++++++++++++++++ internal/lagoon/lagoon.go | 1 + internal/templating/templates_cronjob.go | 6 ++ internal/templating/templates_cronjob_test.go | 50 ++++++++++ .../test-resources/cronjob/result-cli-1.yaml | 3 + .../test-resources/cronjob/result-cli-2.yaml | 3 + .../test-resources/cronjob/result-cli-3.yaml | 96 +++++++++++++++++++ ...ob-cronjob-node-some-other-drush-cron.yaml | 1 + .../cronjob-cronjob-cli-drush-cron2.yaml | 1 + .../cronjob-cronjob-cli-drush-cron2.yaml | 1 + .../cronjob-cronjob-cli-drush-cron2.yaml | 1 + .../cronjob-cronjob-cli-drush-cron2.yaml | 1 + 13 files changed, 265 insertions(+) create mode 100644 internal/templating/test-resources/cronjob/result-cli-3.yaml diff --git a/internal/generator/services.go b/internal/generator/services.go index b14b0b77..5c2878ee 100644 --- a/internal/generator/services.go +++ b/internal/generator/services.go @@ -6,6 +6,7 @@ import ( "regexp" "strconv" "strings" + "time" composetypes "github.com/compose-spec/compose-go/types" "github.com/uselagoon/build-deploy-tool/internal/helpers" @@ -490,6 +491,21 @@ func composeToServiceValues( if err != nil { return nil, fmt.Errorf("unable to convert crontab for cronjob %s: %v", cronjob.Name, err) } + // handle setting the cronjob timeout here + // can't be greater than 24hrs and must match go time duration https://pkg.go.dev/time#ParseDuration + if cronjob.Timeout != "" { + cronjobTimeout, err := time.ParseDuration(cronjob.Timeout) + if err != nil { + return nil, fmt.Errorf("unable to convert timeout for cronjob %s: %v", cronjob.Name, err) + } + // max cronjob timeout is 24 hours + if cronjobTimeout > time.Duration(24*time.Hour) { + return nil, fmt.Errorf("timeout for cronjob %s cannot be longer than 24 hours", cronjob.Name) + } + } else { + // default cronjob timeout is 4h + cronjob.Timeout = "4h" + } // if the cronjob is inpod, or the cronjob has an inpod flag override if inpod || (cronjob.InPod != nil && *cronjob.InPod) { inpodcronjobs = append(inpodcronjobs, cronjob) diff --git a/internal/generator/services_test.go b/internal/generator/services_test.go index 2cd53e3a..599202fb 100644 --- a/internal/generator/services_test.go +++ b/internal/generator/services_test.go @@ -944,6 +944,7 @@ func Test_composeToServiceValues(t *testing.T) { Service: "cli", Schedule: "3,8,13,18,23,28,33,38,43,48,53,58 * * * *", Command: "drush cron", + Timeout: "4h", }, }, NativeCronjobs: []lagoon.Cronjob{ @@ -952,12 +953,14 @@ func Test_composeToServiceValues(t *testing.T) { Service: "cli", Schedule: "5 2 * * *", Command: "env", + Timeout: "4h", }, { Name: "cronjob-cli-my-cronjob-that-has-a-very-very-v-znwv36", Service: "cli", Schedule: "5 2 * * *", Command: "drush cron", + Timeout: "4h", }, }, ImageBuild: &ImageBuild{ @@ -1278,6 +1281,88 @@ func Test_composeToServiceValues(t *testing.T) { want: nil, wantErr: true, }, + { + name: "test24 - failure on cronjob timeout longer than 24h", + args: args{ + buildValues: &BuildValues{ + Namespace: "example-project-main", + Project: "example-project", + ImageRegistry: "harbor.example", + Environment: "main", + Branch: "main", + BuildType: "branch", + ServiceTypeOverrides: &lagoon.EnvironmentVariable{}, + LagoonYAML: lagoon.YAML{ + Environments: lagoon.Environments{ + "main": lagoon.Environment{ + Cronjobs: []lagoon.Cronjob{ + { + Name: "My Cronjob that has a very very very long timeout", + Command: "drush cron", + Service: "cli", + Schedule: "5 2 * * *", + Timeout: "48h", + }, + }, + }, + }, + }, + }, + composeService: "cli", + composeServiceValues: composetypes.ServiceConfig{ + Labels: composetypes.Labels{ + "lagoon.type": "cli", + }, + Build: &composetypes.BuildConfig{ + Context: ".", + Dockerfile: "../testdata/basic/docker/basic.dockerfile", + }, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "test25 - failure on invalid cronjob timeout", + args: args{ + buildValues: &BuildValues{ + Namespace: "example-project-main", + Project: "example-project", + ImageRegistry: "harbor.example", + Environment: "main", + Branch: "main", + BuildType: "branch", + ServiceTypeOverrides: &lagoon.EnvironmentVariable{}, + LagoonYAML: lagoon.YAML{ + Environments: lagoon.Environments{ + "main": lagoon.Environment{ + Cronjobs: []lagoon.Cronjob{ + { + Name: "My Cronjob that has an invalid timeout", + Command: "drush cron", + Service: "cli", + Schedule: "5 2 * * *", + Timeout: "2hrs", + }, + }, + }, + }, + }, + }, + composeService: "cli", + composeServiceValues: composetypes.ServiceConfig{ + Labels: composetypes.Labels{ + "lagoon.type": "cli", + }, + Build: &composetypes.BuildConfig{ + Context: ".", + Dockerfile: "../testdata/basic/docker/basic.dockerfile", + }, + }, + }, + want: nil, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/lagoon/lagoon.go b/internal/lagoon/lagoon.go index 40eb731a..dfff8a91 100644 --- a/internal/lagoon/lagoon.go +++ b/internal/lagoon/lagoon.go @@ -35,6 +35,7 @@ type Cronjob struct { Schedule string `json:"schedule"` Command string `json:"command"` InPod *bool `json:"inPod"` + Timeout string `json:"timeout"` } type Override struct { diff --git a/internal/templating/templates_cronjob.go b/internal/templating/templates_cronjob.go index 713c7ee8..4ea7dbbe 100644 --- a/internal/templating/templates_cronjob.go +++ b/internal/templating/templates_cronjob.go @@ -2,6 +2,7 @@ package templating import ( "fmt" + "time" "github.com/uselagoon/build-deploy-tool/internal/generator" "github.com/uselagoon/build-deploy-tool/internal/helpers" @@ -104,6 +105,11 @@ func GenerateCronjobTemplate( cronjob.Spec.StartingDeadlineSeconds = helpers.Int64Ptr(240) cronjob.Spec.JobTemplate.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyNever + // time has already been parsed in generator/services to check for errors + // and the default timeout is added in generator/services + cronjobTimeout, _ := time.ParseDuration(nCronjob.Timeout) + cronjob.Spec.JobTemplate.Spec.ActiveDeadlineSeconds = (*int64)(&cronjobTimeout) + if serviceValues.CronjobUseSpotInstances { // handle spot instance label and affinity/tolerations/selectors additionalLabels["lagoon.sh/spot"] = "true" diff --git a/internal/templating/templates_cronjob_test.go b/internal/templating/templates_cronjob_test.go index c30fb3ea..c19ff650 100644 --- a/internal/templating/templates_cronjob_test.go +++ b/internal/templating/templates_cronjob_test.go @@ -50,12 +50,14 @@ func TestGenerateCronjobTemplate(t *testing.T) { Service: "myservice", Command: "sleep 300", Schedule: "5 2 * * *", + Timeout: "4h", }, { Name: "cronjob-myservice-my-other-cronjobbb", Service: "myservice", Command: "env", Schedule: "25 6 * * *", + Timeout: "4h", }, }, }, @@ -72,6 +74,7 @@ func TestGenerateCronjobTemplate(t *testing.T) { Service: "myservice", Command: "sleep 300", Schedule: "5 2 * * *", + Timeout: "4h", }, }, }, @@ -116,12 +119,14 @@ func TestGenerateCronjobTemplate(t *testing.T) { Service: "myservice", Command: "sleep 300", Schedule: "5 2 * * *", + Timeout: "4h", }, { Name: "cronjob-myservice-my-other-cronjobbb", Service: "myservice", Command: "env", Schedule: "25 6 * * *", + Timeout: "4h", }, }, }, @@ -138,6 +143,7 @@ func TestGenerateCronjobTemplate(t *testing.T) { Service: "myservice", Command: "sleep 300", Schedule: "5 2 * * *", + Timeout: "4h", }, }, }, @@ -146,6 +152,50 @@ func TestGenerateCronjobTemplate(t *testing.T) { }, want: "test-resources/cronjob/result-cli-2.yaml", }, + { + name: "test3 - cli - security context - timeout", + args: args{ + buildValues: generator.BuildValues{ + Project: "example-project", + Environment: "environment-name", + EnvironmentType: "production", + Namespace: "myexample-project-environment-name", + BuildType: "branch", + LagoonVersion: "v2.x.x", + Kubernetes: "generator.local", + Branch: "environment-name", + ImageReferences: map[string]string{ + "myservice": "harbor.example.com/example-project/environment-name/myservice@latest", + }, + GitSHA: "0", + ConfigMapSha: "32bf1359ac92178c8909f0ef938257b477708aa0d78a5a15ad7c2d7919adf273", + PodSecurityContext: generator.PodSecurityContext{ + RunAsGroup: 0, + RunAsUser: 10000, + FsGroup: 10001, + OnRootMismatch: true, + }, + Services: []generator.ServiceValues{ + { + Name: "myservice", + OverrideName: "myservice", + Type: "cli", + DBaaSEnvironment: "production", + NativeCronjobs: []lagoon.Cronjob{ + { + Name: "cronjob-myservice-my-cronjob", + Service: "myservice", + Command: "sleep 300", + Schedule: "5 2 * * *", + Timeout: "24h", + }, + }, + }, + }, + }, + }, + want: "test-resources/cronjob/result-cli-3.yaml", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/templating/test-resources/cronjob/result-cli-1.yaml b/internal/templating/test-resources/cronjob/result-cli-1.yaml index a1640223..463955c7 100644 --- a/internal/templating/test-resources/cronjob/result-cli-1.yaml +++ b/internal/templating/test-resources/cronjob/result-cli-1.yaml @@ -25,6 +25,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: @@ -115,6 +116,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: @@ -205,6 +207,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: diff --git a/internal/templating/test-resources/cronjob/result-cli-2.yaml b/internal/templating/test-resources/cronjob/result-cli-2.yaml index 3b8c4db1..a3a0b8fb 100644 --- a/internal/templating/test-resources/cronjob/result-cli-2.yaml +++ b/internal/templating/test-resources/cronjob/result-cli-2.yaml @@ -25,6 +25,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: @@ -120,6 +121,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: @@ -215,6 +217,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: diff --git a/internal/templating/test-resources/cronjob/result-cli-3.yaml b/internal/templating/test-resources/cronjob/result-cli-3.yaml new file mode 100644 index 00000000..1c2c9337 --- /dev/null +++ b/internal/templating/test-resources/cronjob/result-cli-3.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + annotations: + lagoon.sh/branch: environment-name + lagoon.sh/version: v2.x.x + creationTimestamp: null + labels: + app.kubernetes.io/instance: cronjob-myservice + app.kubernetes.io/managed-by: build-deploy-tool + app.kubernetes.io/name: cronjob-cli + lagoon.sh/buildType: branch + lagoon.sh/environment: environment-name + lagoon.sh/environmentType: production + lagoon.sh/project: example-project + lagoon.sh/service: myservice + lagoon.sh/service-type: cli + lagoon.sh/template: cli-0.1.0 + name: cronjob-myservice-my-cronjob +spec: + concurrencyPolicy: Forbid + failedJobsHistoryLimit: 1 + jobTemplate: + metadata: + creationTimestamp: null + spec: + activeDeadlineSeconds: 86400000000000 + template: + metadata: + annotations: + lagoon.sh/branch: environment-name + lagoon.sh/configMapSha: 32bf1359ac92178c8909f0ef938257b477708aa0d78a5a15ad7c2d7919adf273 + lagoon.sh/version: v2.x.x + creationTimestamp: null + labels: + app.kubernetes.io/instance: cronjob-myservice + app.kubernetes.io/managed-by: build-deploy-tool + app.kubernetes.io/name: cronjob-cli + lagoon.sh/buildType: branch + lagoon.sh/environment: environment-name + lagoon.sh/environmentType: production + lagoon.sh/project: example-project + lagoon.sh/service: myservice + lagoon.sh/service-type: cli + lagoon.sh/template: cli-0.1.0 + spec: + containers: + - command: + - /lagoon/cronjob.sh + - sleep 300 + env: + - name: LAGOON_GIT_SHA + value: "0" + - name: SERVICE_NAME + value: myservice + envFrom: + - configMapRef: + name: lagoon-env + image: harbor.example.com/example-project/environment-name/myservice@latest + imagePullPolicy: Always + name: cronjob-myservice-my-cronjob + resources: + requests: + cpu: 10m + memory: 10Mi + securityContext: {} + volumeMounts: + - mountPath: /var/run/secrets/lagoon/sshkey/ + name: lagoon-sshkey + readOnly: true + dnsConfig: + options: + - name: timeout + value: "60" + - name: attempts + value: "10" + enableServiceLinks: false + imagePullSecrets: + - name: lagoon-internal-registry-secret + priorityClassName: lagoon-priority-production + restartPolicy: Never + securityContext: + fsGroup: 10001 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 0 + runAsUser: 10000 + volumes: + - name: lagoon-sshkey + secret: + defaultMode: 420 + secretName: lagoon-sshkey + schedule: 5 2 * * * + startingDeadlineSeconds: 240 + successfulJobsHistoryLimit: 0 +status: {} diff --git a/internal/testdata/basic/service-templates/test11-basic-polysite-cronjobs/cronjob-cronjob-node-some-other-drush-cron.yaml b/internal/testdata/basic/service-templates/test11-basic-polysite-cronjobs/cronjob-cronjob-node-some-other-drush-cron.yaml index 5133d082..feeb9744 100644 --- a/internal/testdata/basic/service-templates/test11-basic-polysite-cronjobs/cronjob-cronjob-node-some-other-drush-cron.yaml +++ b/internal/testdata/basic/service-templates/test11-basic-polysite-cronjobs/cronjob-cronjob-node-some-other-drush-cron.yaml @@ -25,6 +25,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: diff --git a/internal/testdata/complex/service-templates/test2-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml b/internal/testdata/complex/service-templates/test2-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml index 71513956..31916109 100644 --- a/internal/testdata/complex/service-templates/test2-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml +++ b/internal/testdata/complex/service-templates/test2-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml @@ -25,6 +25,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: diff --git a/internal/testdata/complex/service-templates/test2b-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml b/internal/testdata/complex/service-templates/test2b-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml index 0f77f760..4f110ea8 100644 --- a/internal/testdata/complex/service-templates/test2b-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml +++ b/internal/testdata/complex/service-templates/test2b-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml @@ -25,6 +25,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: diff --git a/internal/testdata/complex/service-templates/test2c-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml b/internal/testdata/complex/service-templates/test2c-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml index 9e9a0d27..d316dec7 100644 --- a/internal/testdata/complex/service-templates/test2c-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml +++ b/internal/testdata/complex/service-templates/test2c-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml @@ -26,6 +26,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: diff --git a/internal/testdata/complex/service-templates/test2d-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml b/internal/testdata/complex/service-templates/test2d-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml index e0cae640..9127d11d 100644 --- a/internal/testdata/complex/service-templates/test2d-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml +++ b/internal/testdata/complex/service-templates/test2d-nginx-php/cronjob-cronjob-cli-drush-cron2.yaml @@ -25,6 +25,7 @@ spec: metadata: creationTimestamp: null spec: + activeDeadlineSeconds: 14400000000000 template: metadata: annotations: