From 4a357e9896f24740d5f3f3eeb7c0ada5c52ea8ec Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 21 Sep 2025 20:08:22 -0400 Subject: [PATCH] fix: properly suspend and resume k8s CronJob objects when executing scheduler-cron-write --- plugins/scheduler-k3s/Makefile | 2 +- plugins/scheduler-k3s/k8s.go | 29 ++++++++++ .../scheduler-k3s/src/triggers/triggers.go | 4 ++ plugins/scheduler-k3s/triggers.go | 58 +++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/plugins/scheduler-k3s/Makefile b/plugins/scheduler-k3s/Makefile index e894570d5..b873f3701 100644 --- a/plugins/scheduler-k3s/Makefile +++ b/plugins/scheduler-k3s/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/annotations:set subcommands/autoscaling-auth:set subcommands/autoscaling-auth:report subcommands/cluster-add subcommands/cluster-list subcommands/cluster-remove subcommands/ensure-charts subcommands/initialize subcommands/labels:set subcommands/report subcommands/set subcommands/show-kubeconfig subcommands/uninstall -TRIGGERS = triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/report triggers/scheduler-app-status triggers/scheduler-deploy triggers/scheduler-enter triggers/scheduler-is-deployed triggers/scheduler-logs triggers/scheduler-proxy-config triggers/scheduler-proxy-logs triggers/scheduler-post-delete triggers/scheduler-run triggers/scheduler-run-list triggers/scheduler-stop +TRIGGERS = triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/report triggers/scheduler-app-status triggers/scheduler-deploy triggers/scheduler-enter triggers/scheduler-is-deployed triggers/scheduler-logs triggers/scheduler-proxy-config triggers/scheduler-proxy-logs triggers/scheduler-post-delete triggers/scheduler-run triggers/scheduler-run-list triggers/scheduler-stop triggers/scheduler-cron-write BUILD = commands subcommands triggers PLUGIN_NAME = scheduler-k3s diff --git a/plugins/scheduler-k3s/k8s.go b/plugins/scheduler-k3s/k8s.go index a078f8ead..aa5a07543 100644 --- a/plugins/scheduler-k3s/k8s.go +++ b/plugins/scheduler-k3s/k8s.go @@ -805,6 +805,35 @@ func (k KubernetesClient) ListTriggerAuthentications(ctx context.Context, input return triggerAuthentications, nil } +// ResumeCronJobsInput contains all the information needed to resume a Kubernetes cron job +type ResumeCronJobsInput struct { + // LabelSelector is the Kubernetes label selector + LabelSelector string + + // Namespace is the Kubernetes namespace + Namespace string +} + +// ResumeCronJobs resumes a Kubernetes cron job +func (k KubernetesClient) ResumeCronJobs(ctx context.Context, input ResumeCronJobsInput) error { + cronJobs, err := k.Client.BatchV1().CronJobs(input.Namespace).List(ctx, metav1.ListOptions{ + LabelSelector: input.LabelSelector, + }) + if err != nil { + return err + } + + for _, cronJob := range cronJobs.Items { + cronJob.Spec.Suspend = ptr.To(false) + _, err := k.Client.BatchV1().CronJobs(input.Namespace).Update(ctx, &cronJob, metav1.UpdateOptions{}) + if err != nil { + return err + } + } + + return nil +} + // ScaleDeploymentInput contains all the information needed to scale a Kubernetes deployment type ScaleDeploymentInput struct { // Name is the Kubernetes deployment name diff --git a/plugins/scheduler-k3s/src/triggers/triggers.go b/plugins/scheduler-k3s/src/triggers/triggers.go index dd48a6b71..15caf5146 100644 --- a/plugins/scheduler-k3s/src/triggers/triggers.go +++ b/plugins/scheduler-k3s/src/triggers/triggers.go @@ -123,6 +123,10 @@ func main() { scheduler := flag.Arg(0) appName := flag.Arg(1) err = scheduler_k3s.TriggerSchedulerStop(scheduler, appName) + case "scheduler-cron-write": + scheduler := flag.Arg(0) + appName := flag.Arg(1) + err = scheduler_k3s.TriggerSchedulerCronWrite(scheduler, appName) case "scheduler-post-delete": scheduler := flag.Arg(0) appName := flag.Arg(1) diff --git a/plugins/scheduler-k3s/triggers.go b/plugins/scheduler-k3s/triggers.go index efaba74b0..4ca07841f 100644 --- a/plugins/scheduler-k3s/triggers.go +++ b/plugins/scheduler-k3s/triggers.go @@ -163,6 +163,64 @@ func TriggerSchedulerAppStatus(scheduler string, appName string) error { return nil } +// TriggerSchedulerCronWrite writes out cron entries for a given application +func TriggerSchedulerCronWrite(scheduler string, appName string) error { + if scheduler != "k3s" { + return nil + } + + allCronEntries, err := cron.FetchCronEntries(cron.FetchCronEntriesInput{AppName: appName}) + if err != nil { + return fmt.Errorf("Error fetching cron entries: %w", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + signals := make(chan os.Signal, 1) + signal.Notify(signals, os.Interrupt, syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGTERM) + go func() { + <-signals + common.LogWarn(fmt.Sprintf("Deployment of %s has been cancelled", appName)) + cancel() + }() + + namespace := getComputedNamespace(appName) + + clientset, err := NewKubernetesClient() + if err != nil { + return fmt.Errorf("Error creating kubernetes client: %w", err) + } + + for _, cronEntry := range allCronEntries { + labelSelector := []string{ + fmt.Sprintf("app.kubernetes.io/part-of=%s", appName), + fmt.Sprintf("dokku.com/cron-id=%s", cronEntry.ID), + } + + if cronEntry.Maintenance { + err = clientset.SuspendCronJobs(ctx, SuspendCronJobsInput{ + Namespace: namespace, + LabelSelector: strings.Join(labelSelector, ","), + }) + if err != nil { + return fmt.Errorf("Error suspending cron jobs: %w", err) + } + } else { + err = clientset.ResumeCronJobs(ctx, ResumeCronJobsInput{ + Namespace: namespace, + LabelSelector: strings.Join(labelSelector, ","), + }) + if err != nil { + return fmt.Errorf("Error resuming cron jobs: %w", err) + } + } + } + + return nil +} + // TriggerSchedulerDeploy deploys an image tag for a given application func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) error { if scheduler != "k3s" {