package utils import ( "context" "time" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes" ) // DeploymentAdapter implements WorkloadAdapter for Kubernetes Deployments. type DeploymentAdapter struct { client kubernetes.Interface } // NewDeploymentAdapter creates a new DeploymentAdapter. func NewDeploymentAdapter(client kubernetes.Interface) *DeploymentAdapter { return &DeploymentAdapter{client: client} } // Type returns the workload type. func (a *DeploymentAdapter) Type() WorkloadType { return WorkloadDeployment } // Create creates a Deployment with the given config. func (a *DeploymentAdapter) Create(ctx context.Context, namespace, name string, cfg WorkloadConfig) error { opts := buildDeploymentOptions(cfg) _, err := CreateDeployment(ctx, a.client, namespace, name, opts...) return err } // Delete removes the Deployment. func (a *DeploymentAdapter) Delete(ctx context.Context, namespace, name string) error { return DeleteDeployment(ctx, a.client, namespace, name) } // WaitReady waits for the Deployment to be ready using watches. func (a *DeploymentAdapter) WaitReady(ctx context.Context, namespace, name string, timeout time.Duration) error { watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { return a.client.AppsV1().Deployments(namespace).Watch(ctx, opts) } _, err := WatchUntil(ctx, watchFunc, name, IsReady(DeploymentIsReady), timeout) return err } // WaitReloaded waits for the Deployment to have the reload annotation using watches. // It captures the current annotation value before watching so that a prior reload's annotation // does not cause a false positive — the condition triggers only when the value changes. func (a *DeploymentAdapter) WaitReloaded(ctx context.Context, namespace, name, annotationKey string, timeout time.Duration) (bool, error) { priorValue, _ := a.GetPodTemplateAnnotation(ctx, namespace, name, annotationKey) watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { return a.client.AppsV1().Deployments(namespace).Watch(ctx, opts) } _, err := WatchUntil(ctx, watchFunc, name, HasPodTemplateAnnotationChanged(DeploymentPodTemplate, annotationKey, priorValue), timeout) return HandleWatchResult(err) } // WaitEnvVar waits for the Deployment to have a STAKATER_ env var using watches. // It captures the current env var value before watching so that a prior reload's value does not // cause a false positive — the condition triggers only when the value appears or changes. func (a *DeploymentAdapter) WaitEnvVar(ctx context.Context, namespace, name, prefix string, timeout time.Duration) (bool, error) { priorValue := "" if d, err := a.client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}); err == nil { priorValue = GetEnvVarValueByPrefix(d.Spec.Template.Spec.Containers, prefix) } watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { return a.client.AppsV1().Deployments(namespace).Watch(ctx, opts) } _, err := WatchUntil(ctx, watchFunc, name, HasEnvVarPrefixChanged(DeploymentContainers, prefix, priorValue), timeout) return HandleWatchResult(err) } // WaitPaused waits for the Deployment to have the paused annotation using watches. func (a *DeploymentAdapter) WaitPaused(ctx context.Context, namespace, name, annotationKey string, timeout time.Duration) (bool, error) { watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { return a.client.AppsV1().Deployments(namespace).Watch(ctx, opts) } _, err := WatchUntil(ctx, watchFunc, name, HasAnnotation(DeploymentAnnotations, annotationKey), timeout) return HandleWatchResult(err) } // WaitUnpaused waits for the Deployment to NOT have the paused annotation using watches. func (a *DeploymentAdapter) WaitUnpaused(ctx context.Context, namespace, name, annotationKey string, timeout time.Duration) (bool, error) { watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { return a.client.AppsV1().Deployments(namespace).Watch(ctx, opts) } _, err := WatchUntil(ctx, watchFunc, name, NoAnnotation(DeploymentAnnotations, annotationKey), timeout) return HandleWatchResult(err) } // SupportsEnvVarStrategy returns true as Deployments support env var reload strategy. func (a *DeploymentAdapter) SupportsEnvVarStrategy() bool { return true } // RequiresSpecialHandling returns false as Deployments use standard rolling restart. func (a *DeploymentAdapter) RequiresSpecialHandling() bool { return false } // GetPodTemplateAnnotation returns the value of a pod template annotation. func (a *DeploymentAdapter) GetPodTemplateAnnotation(ctx context.Context, namespace, name, annotationKey string) (string, error) { deploy, err := a.client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return "", err } return deploy.Spec.Template.Annotations[annotationKey], nil } // buildDeploymentOptions converts WorkloadConfig to DeploymentOption slice. func buildDeploymentOptions(cfg WorkloadConfig) []DeploymentOption { return []DeploymentOption{ func(d *appsv1.Deployment) { if len(cfg.Annotations) > 0 { if d.Annotations == nil { d.Annotations = make(map[string]string) } for k, v := range cfg.Annotations { d.Annotations[k] = v } } ApplyWorkloadConfig(&d.Spec.Template, cfg) }, } }