mirror of
https://github.com/stakater/Reloader.git
synced 2026-05-18 22:56:38 +00:00
208 lines
6.2 KiB
Go
208 lines
6.2 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
batchv1 "k8s.io/api/batch/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
)
|
|
|
|
// JobAdapter implements WorkloadAdapter for Kubernetes Jobs.
|
|
// Note: Jobs are handled specially by Reloader - they are recreated rather than updated.
|
|
type JobAdapter struct {
|
|
client kubernetes.Interface
|
|
}
|
|
|
|
// NewJobAdapter creates a new JobAdapter.
|
|
func NewJobAdapter(client kubernetes.Interface) *JobAdapter {
|
|
return &JobAdapter{client: client}
|
|
}
|
|
|
|
// Type returns the workload type.
|
|
func (a *JobAdapter) Type() WorkloadType {
|
|
return WorkloadJob
|
|
}
|
|
|
|
// Create creates a Job with the given config.
|
|
func (a *JobAdapter) Create(ctx context.Context, namespace, name string, cfg WorkloadConfig) error {
|
|
opts := buildJobOptions(cfg)
|
|
_, err := CreateJob(ctx, a.client, namespace, name, opts...)
|
|
return err
|
|
}
|
|
|
|
// Delete removes the Job.
|
|
func (a *JobAdapter) Delete(ctx context.Context, namespace, name string) error {
|
|
return DeleteJob(ctx, a.client, namespace, name)
|
|
}
|
|
|
|
// WaitReady waits for the Job to exist.
|
|
func (a *JobAdapter) WaitReady(ctx context.Context, namespace, name string, timeout time.Duration) error {
|
|
return WaitForJobExists(ctx, a.client, namespace, name, timeout)
|
|
}
|
|
|
|
// WaitReloaded waits for the Job to be recreated (new UID).
|
|
// For Jobs, Reloader recreates the Job rather than updating annotations.
|
|
func (a *JobAdapter) WaitReloaded(ctx context.Context, namespace, name, annotationKey string, timeout time.Duration) (bool, error) {
|
|
// For Jobs, we check if it was recreated by looking for a new UID
|
|
// This requires storing the original UID before the test
|
|
// For simplicity, we use the same pattern as other workloads
|
|
// The test should verify recreation using WaitForJobRecreated instead
|
|
return false, nil
|
|
}
|
|
|
|
// WaitEnvVar is not supported for Jobs as they don't use env var reload strategy.
|
|
func (a *JobAdapter) WaitEnvVar(ctx context.Context, namespace, name, prefix string, timeout time.Duration) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
// SupportsEnvVarStrategy returns false as Jobs don't support env var reload strategy.
|
|
func (a *JobAdapter) SupportsEnvVarStrategy() bool {
|
|
return false
|
|
}
|
|
|
|
// RequiresSpecialHandling returns true as Jobs are recreated by Reloader.
|
|
func (a *JobAdapter) RequiresSpecialHandling() bool {
|
|
return true
|
|
}
|
|
|
|
// GetOriginalUID retrieves the current UID of the Job for recreation verification.
|
|
func (a *JobAdapter) GetOriginalUID(ctx context.Context, namespace, name string) (string, error) {
|
|
job, err := GetJob(ctx, a.client, namespace, name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(job.UID), nil
|
|
}
|
|
|
|
// WaitForRecreation waits for the Job to be recreated with a new UID.
|
|
func (a *JobAdapter) WaitForRecreation(ctx context.Context, namespace, name, originalUID string, timeout time.Duration) (string, bool, error) {
|
|
return WaitForJobRecreated(ctx, a.client, namespace, name, originalUID, timeout)
|
|
}
|
|
|
|
// buildJobOptions converts WorkloadConfig to JobOption slice.
|
|
func buildJobOptions(cfg WorkloadConfig) []JobOption {
|
|
var opts []JobOption
|
|
|
|
// Add annotations
|
|
if len(cfg.Annotations) > 0 {
|
|
opts = append(opts, WithJobAnnotations(cfg.Annotations))
|
|
}
|
|
|
|
// Add envFrom references
|
|
if cfg.UseConfigMapEnvFrom && cfg.ConfigMapName != "" {
|
|
opts = append(opts, WithJobConfigMapEnvFrom(cfg.ConfigMapName))
|
|
}
|
|
if cfg.UseSecretEnvFrom && cfg.SecretName != "" {
|
|
opts = append(opts, WithJobSecretEnvFrom(cfg.SecretName))
|
|
}
|
|
|
|
// Add volume mounts
|
|
if cfg.UseConfigMapVolume && cfg.ConfigMapName != "" {
|
|
opts = append(opts, WithJobConfigMapVolume(cfg.ConfigMapName))
|
|
}
|
|
if cfg.UseSecretVolume && cfg.SecretName != "" {
|
|
opts = append(opts, WithJobSecretVolume(cfg.SecretName))
|
|
}
|
|
|
|
// Add projected volume
|
|
if cfg.UseProjectedVolume {
|
|
opts = append(opts, WithJobProjectedVolume(cfg.ConfigMapName, cfg.SecretName))
|
|
}
|
|
|
|
return opts
|
|
}
|
|
|
|
// WithJobConfigMapVolume adds a volume mount for a ConfigMap to a Job.
|
|
func WithJobConfigMapVolume(name string) JobOption {
|
|
return func(j *batchv1.Job) {
|
|
volumeName := "cm-" + name
|
|
j.Spec.Template.Spec.Volumes = append(
|
|
j.Spec.Template.Spec.Volumes,
|
|
corev1.Volume{
|
|
Name: volumeName,
|
|
VolumeSource: corev1.VolumeSource{
|
|
ConfigMap: &corev1.ConfigMapVolumeSource{
|
|
LocalObjectReference: corev1.LocalObjectReference{Name: name},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
j.Spec.Template.Spec.Containers[0].VolumeMounts = append(
|
|
j.Spec.Template.Spec.Containers[0].VolumeMounts,
|
|
corev1.VolumeMount{
|
|
Name: volumeName,
|
|
MountPath: "/etc/config/" + name,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
// WithJobSecretVolume adds a volume mount for a Secret to a Job.
|
|
func WithJobSecretVolume(name string) JobOption {
|
|
return func(j *batchv1.Job) {
|
|
volumeName := "secret-" + name
|
|
j.Spec.Template.Spec.Volumes = append(
|
|
j.Spec.Template.Spec.Volumes,
|
|
corev1.Volume{
|
|
Name: volumeName,
|
|
VolumeSource: corev1.VolumeSource{
|
|
Secret: &corev1.SecretVolumeSource{
|
|
SecretName: name,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
j.Spec.Template.Spec.Containers[0].VolumeMounts = append(
|
|
j.Spec.Template.Spec.Containers[0].VolumeMounts,
|
|
corev1.VolumeMount{
|
|
Name: volumeName,
|
|
MountPath: "/etc/secrets/" + name,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
// WithJobProjectedVolume adds a projected volume with ConfigMap and/or Secret sources to a Job.
|
|
func WithJobProjectedVolume(cmName, secretName string) JobOption {
|
|
return func(j *batchv1.Job) {
|
|
volumeName := "projected-config"
|
|
sources := []corev1.VolumeProjection{}
|
|
|
|
if cmName != "" {
|
|
sources = append(sources, corev1.VolumeProjection{
|
|
ConfigMap: &corev1.ConfigMapProjection{
|
|
LocalObjectReference: corev1.LocalObjectReference{Name: cmName},
|
|
},
|
|
})
|
|
}
|
|
if secretName != "" {
|
|
sources = append(sources, corev1.VolumeProjection{
|
|
Secret: &corev1.SecretProjection{
|
|
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
|
|
},
|
|
})
|
|
}
|
|
|
|
j.Spec.Template.Spec.Volumes = append(
|
|
j.Spec.Template.Spec.Volumes,
|
|
corev1.Volume{
|
|
Name: volumeName,
|
|
VolumeSource: corev1.VolumeSource{
|
|
Projected: &corev1.ProjectedVolumeSource{
|
|
Sources: sources,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
j.Spec.Template.Spec.Containers[0].VolumeMounts = append(
|
|
j.Spec.Template.Spec.Containers[0].VolumeMounts,
|
|
corev1.VolumeMount{
|
|
Name: volumeName,
|
|
MountPath: "/etc/projected",
|
|
},
|
|
)
|
|
}
|
|
}
|