mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 04:26:28 +00:00
The CSI e2e tests wait for the SPCPS version change before calling WaitReloaded/WaitEnvVar, but Reloader reacts to that same SPCPS update. When Reloader won the race, WaitReloaded captured the already-reloaded annotation as its baseline and then timed out waiting for a further change (seen in CI: "Init container with CSI volume should reload..."). Add WaitReloadedFrom/WaitEnvVarFrom adapter variants that take a caller-supplied baseline, and have the CSI tests capture that baseline before updating the Vault secret. Negative tests also benefit: an erroneous reload that lands during the CSI sync wait is now detected instead of silently absorbed into the baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
162 lines
6.5 KiB
Go
162 lines
6.5 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
openshiftappsv1 "github.com/openshift/api/apps/v1"
|
|
openshiftclient "github.com/openshift/client-go/apps/clientset/versioned"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/watch"
|
|
)
|
|
|
|
// DeploymentConfigOption is a function that modifies a DeploymentConfig.
|
|
type DeploymentConfigOption func(*openshiftappsv1.DeploymentConfig)
|
|
|
|
// DeploymentConfigAdapter implements WorkloadAdapter for OpenShift DeploymentConfigs.
|
|
type DeploymentConfigAdapter struct {
|
|
openshiftClient openshiftclient.Interface
|
|
}
|
|
|
|
// NewDeploymentConfigAdapter creates a new DeploymentConfigAdapter.
|
|
func NewDeploymentConfigAdapter(openshiftClient openshiftclient.Interface) *DeploymentConfigAdapter {
|
|
return &DeploymentConfigAdapter{
|
|
openshiftClient: openshiftClient,
|
|
}
|
|
}
|
|
|
|
// Type returns the workload type.
|
|
func (a *DeploymentConfigAdapter) Type() WorkloadType {
|
|
return WorkloadDeploymentConfig
|
|
}
|
|
|
|
// Create creates a DeploymentConfig with the given config.
|
|
func (a *DeploymentConfigAdapter) Create(ctx context.Context, namespace, name string, cfg WorkloadConfig) error {
|
|
dc := baseDeploymentConfig(name)
|
|
opts := buildDeploymentConfigOptions(cfg)
|
|
for _, opt := range opts {
|
|
opt(dc)
|
|
}
|
|
_, err := a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Create(ctx, dc, metav1.CreateOptions{})
|
|
return err
|
|
}
|
|
|
|
// Delete removes the DeploymentConfig.
|
|
func (a *DeploymentConfigAdapter) Delete(ctx context.Context, namespace, name string) error {
|
|
return a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
|
}
|
|
|
|
// WaitReady waits for the DeploymentConfig to be ready using watches.
|
|
func (a *DeploymentConfigAdapter) WaitReady(ctx context.Context, namespace, name string, timeout time.Duration) error {
|
|
watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
|
|
return a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Watch(ctx, opts)
|
|
}
|
|
_, err := WatchUntil(ctx, watchFunc, name, IsReady(DeploymentConfigIsReady), timeout)
|
|
return err
|
|
}
|
|
|
|
// WaitReloaded waits for the DeploymentConfig to have the reload annotation using watches.
|
|
// Captures the current annotation value first to avoid false positives from prior reloads.
|
|
func (a *DeploymentConfigAdapter) WaitReloaded(ctx context.Context, namespace, name, annotationKey string, timeout time.Duration) (bool, error) {
|
|
priorValue, _ := a.GetPodTemplateAnnotation(ctx, namespace, name, annotationKey)
|
|
return a.WaitReloadedFrom(ctx, namespace, name, annotationKey, priorValue, timeout)
|
|
}
|
|
|
|
// WaitReloadedFrom waits for the reload annotation to be present with a value different from
|
|
// priorValue, which the caller captured before triggering the reload.
|
|
func (a *DeploymentConfigAdapter) WaitReloadedFrom(ctx context.Context, namespace, name, annotationKey, priorValue string, timeout time.Duration) (bool, error) {
|
|
watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
|
|
return a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Watch(ctx, opts)
|
|
}
|
|
_, err := WatchUntil(ctx, watchFunc, name, HasPodTemplateAnnotationChanged(DeploymentConfigPodTemplate, annotationKey, priorValue), timeout)
|
|
return HandleWatchResult(err)
|
|
}
|
|
|
|
// WaitEnvVar waits for the DeploymentConfig to have a STAKATER_ env var using watches.
|
|
// Captures the current env var value first to avoid false positives from prior reloads.
|
|
func (a *DeploymentConfigAdapter) WaitEnvVar(ctx context.Context, namespace, name, prefix string, timeout time.Duration) (bool, error) {
|
|
priorValue := ""
|
|
if dc, err := a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{}); err == nil && dc.Spec.Template != nil {
|
|
priorValue = GetEnvVarValueByPrefix(dc.Spec.Template.Spec.Containers, prefix)
|
|
}
|
|
return a.WaitEnvVarFrom(ctx, namespace, name, prefix, priorValue, timeout)
|
|
}
|
|
|
|
// WaitEnvVarFrom waits for a STAKATER_ env var whose value differs from priorValue, which the
|
|
// caller captured before triggering the reload.
|
|
func (a *DeploymentConfigAdapter) WaitEnvVarFrom(ctx context.Context, namespace, name, prefix, priorValue string, timeout time.Duration) (bool, error) {
|
|
watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
|
|
return a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Watch(ctx, opts)
|
|
}
|
|
_, err := WatchUntil(ctx, watchFunc, name, HasEnvVarPrefixChanged(DeploymentConfigContainers, prefix, priorValue), timeout)
|
|
return HandleWatchResult(err)
|
|
}
|
|
|
|
// SupportsEnvVarStrategy returns true as DeploymentConfigs support env var reload strategy.
|
|
func (a *DeploymentConfigAdapter) SupportsEnvVarStrategy() bool {
|
|
return true
|
|
}
|
|
|
|
// RequiresSpecialHandling returns false as DeploymentConfigs use standard rolling restart.
|
|
func (a *DeploymentConfigAdapter) RequiresSpecialHandling() bool {
|
|
return false
|
|
}
|
|
|
|
// GetPodTemplateAnnotation returns the value of a pod template annotation.
|
|
func (a *DeploymentConfigAdapter) GetPodTemplateAnnotation(ctx context.Context, namespace, name, annotationKey string) (string, error) {
|
|
dc, err := a.openshiftClient.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if dc.Spec.Template == nil {
|
|
return "", nil
|
|
}
|
|
return dc.Spec.Template.Annotations[annotationKey], nil
|
|
}
|
|
|
|
// baseDeploymentConfig returns a minimal DeploymentConfig template.
|
|
func baseDeploymentConfig(name string) *openshiftappsv1.DeploymentConfig {
|
|
return &openshiftappsv1.DeploymentConfig{
|
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
|
Spec: openshiftappsv1.DeploymentConfigSpec{
|
|
Replicas: 1,
|
|
Selector: map[string]string{"app": name},
|
|
Template: &corev1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{"app": name},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{
|
|
Name: "main",
|
|
Image: DefaultImage,
|
|
Command: []string{"sh", "-c", DefaultCommand},
|
|
}},
|
|
},
|
|
},
|
|
Triggers: openshiftappsv1.DeploymentTriggerPolicies{
|
|
{Type: openshiftappsv1.DeploymentTriggerOnConfigChange},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// buildDeploymentConfigOptions converts WorkloadConfig to DeploymentConfigOption slice.
|
|
func buildDeploymentConfigOptions(cfg WorkloadConfig) []DeploymentConfigOption {
|
|
return []DeploymentConfigOption{
|
|
func(dc *openshiftappsv1.DeploymentConfig) {
|
|
if len(cfg.Annotations) > 0 {
|
|
if dc.Annotations == nil {
|
|
dc.Annotations = make(map[string]string)
|
|
}
|
|
for k, v := range cfg.Annotations {
|
|
dc.Annotations[k] = v
|
|
}
|
|
}
|
|
if dc.Spec.Template != nil {
|
|
ApplyWorkloadConfig(dc.Spec.Template, cfg)
|
|
}
|
|
},
|
|
}
|
|
}
|