Skip to content

Commit

Permalink
Merge pull request #57 from janlauber/cronjobs
Browse files Browse the repository at this point in the history
feat: Add CronJobSpec to RolloutSpec
  • Loading branch information
janlauber committed May 22, 2024
2 parents 92cc180 + b88b913 commit 1ef0337
Show file tree
Hide file tree
Showing 8 changed files with 414 additions and 147 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,29 @@ spec:
- host: "reflex.oneclickapps.dev"
path: "/test"
tls: false
cronjobs:
- name: some-bash-job
suspend: false
image:
password: ""
registry: docker.io
repository: library/busybox
tag: latest
username: ""
schedule: "*/1 * * * *"
command: ["echo", "hello"]
maxRetries: 3
backoffLimit: 2
env:
- name: SOME_ENV
value: "some-value"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
serviceAccountName: "nginx"
```

Expand Down
14 changes: 14 additions & 0 deletions api/v1alpha1/rollout_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ type IngressRule struct {
TlsSecretName string `json:"tlsSecretName,omitempty"`
}

type CronJobSpec struct {
Name string `json:"name"`
Suspend bool `json:"suspend"`
Image ImageSpec `json:"image"`
Schedule string `json:"schedule"`
Command []string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
MaxRetries int32 `json:"maxRetries,omitempty"`
BackoffLimit int32 `json:"backoffLimit,omitempty"`
Env []EnvVar `json:"env,omitempty"`
Resources ResourceRequirements `json:"resources"`
}

// RolloutSpec defines the desired state of Rollout
type RolloutSpec struct {
Args []string `json:"args,omitempty"`
Expand All @@ -112,6 +125,7 @@ type RolloutSpec struct {
Volumes []VolumeSpec `json:"volumes,omitempty"`
Interfaces []InterfaceSpec `json:"interfaces,omitempty"`
ServiceAccountName string `json:"serviceAccountName"`
CronJobs []CronJobSpec `json:"cronjobs,omitempty"`
}

type Resources struct {
Expand Down
39 changes: 39 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions config/crd/bases/one-click.dev_rollouts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,92 @@ spec:
items:
type: string
type: array
cronjobs:
items:
properties:
args:
items:
type: string
type: array
backoffLimit:
format: int32
type: integer
command:
items:
type: string
type: array
env:
items:
properties:
name:
type: string
value:
type: string
required:
- name
- value
type: object
type: array
image:
properties:
password:
type: string
registry:
type: string
repository:
type: string
tag:
type: string
username:
type: string
required:
- registry
- repository
- tag
type: object
maxRetries:
format: int32
type: integer
name:
type: string
resources:
properties:
limits:
properties:
cpu:
type: string
memory:
type: string
required:
- cpu
- memory
type: object
requests:
properties:
cpu:
type: string
memory:
type: string
required:
- cpu
- memory
type: object
required:
- limits
- requests
type: object
schedule:
type: string
suspend:
type: boolean
required:
- image
- name
- resources
- schedule
- suspend
type: object
type: array
env:
items:
properties:
Expand Down
121 changes: 121 additions & 0 deletions controllers/cronjobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package controllers

import (
"context"
"fmt"
"reflect"

batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"

oneclickiov1alpha1 "github.com/janlauber/one-click-operator/api/v1alpha1"
)

func (r *RolloutReconciler) reconcileCronJobs(ctx context.Context, instance *oneclickiov1alpha1.Rollout) error {
log := log.FromContext(ctx)

// Track the CronJobs defined in the Rollout spec
definedCronJobs := make(map[string]oneclickiov1alpha1.CronJobSpec)
for _, cronJobSpec := range instance.Spec.CronJobs {
definedCronJobs[cronJobSpec.Name] = cronJobSpec

// Handle image pull secret if username and password are provided
var imagePullSecrets []corev1.LocalObjectReference
if cronJobSpec.Image.Username != "" && cronJobSpec.Image.Password != "" {
secretName := cronJobSpec.Name + "-imagepullsecret"
if err := reconcileImagePullSecret(ctx, r.Client, instance, cronJobSpec.Image, secretName, instance.Namespace); err != nil {
return err
}
imagePullSecrets = append(imagePullSecrets, corev1.LocalObjectReference{Name: secretName})
}

cronJob := &batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: cronJobSpec.Name,
Namespace: instance.Namespace,
},
Spec: batchv1.CronJobSpec{
Suspend: &cronJobSpec.Suspend,
Schedule: cronJobSpec.Schedule,
JobTemplate: batchv1.JobTemplateSpec{
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: cronJobSpec.Name,
Image: fmt.Sprintf("%s/%s:%s", cronJobSpec.Image.Registry, cronJobSpec.Image.Repository, cronJobSpec.Image.Tag),
Command: cronJobSpec.Command,
Args: cronJobSpec.Args,
Env: getEnvVars(cronJobSpec.Env),
Resources: createResourceRequirements(cronJobSpec.Resources),
},
},
RestartPolicy: corev1.RestartPolicyOnFailure,
ImagePullSecrets: imagePullSecrets,
},
},
BackoffLimit: &cronJobSpec.BackoffLimit,
},
},
},
}

// Set Rollout instance as the owner and controller
if err := ctrl.SetControllerReference(instance, cronJob, r.Scheme); err != nil {
return err
}

// Check if this CronJob already exists
found := &batchv1.CronJob{}
err := r.Get(ctx, types.NamespacedName{Name: cronJob.Name, Namespace: cronJob.Namespace}, found)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating a new CronJob", "CronJob.Namespace", cronJob.Namespace, "CronJob.Name", cronJob.Name)
err = r.Create(ctx, cronJob)
if err != nil {
return err
}
} else if err != nil {
return err
} else {
// Update existing CronJob if needed
if needsUpdateCronJob(found, cronJob) {
found.Spec = cronJob.Spec
log.Info("Updating existing CronJob", "CronJob.Namespace", cronJob.Namespace, "CronJob.Name", cronJob.Name)
err = r.Update(ctx, found)
if err != nil {
return err
}
}
}
}

// List all CronJobs in the namespace to find any that are not defined in the Rollout spec
var existingCronJobs batchv1.CronJobList
if err := r.List(ctx, &existingCronJobs, client.InNamespace(instance.Namespace), client.MatchingFields{"metadata.ownerReferences.uid": string(instance.UID)}); err != nil {
return err
}

for _, existingCronJob := range existingCronJobs.Items {
if _, exists := definedCronJobs[existingCronJob.Name]; !exists {
// CronJob is not defined in the Rollout spec, so delete it
log.Info("Deleting CronJob not defined in Rollout spec", "CronJob.Namespace", existingCronJob.Namespace, "CronJob.Name", existingCronJob.Name)
if err := r.Delete(ctx, &existingCronJob); err != nil {
return err
}
}
}

return nil
}

// Helper function to check if the CronJob needs to be updated
func needsUpdateCronJob(current *batchv1.CronJob, desired *batchv1.CronJob) bool {
return !reflect.DeepEqual(current.Spec, desired.Spec)
}

0 comments on commit 1ef0337

Please sign in to comment.