Files
Reloader/test/e2e/utils/workload_argo.go
2026-01-14 19:41:04 +01:00

154 lines
5.3 KiB
Go

package utils
import (
"context"
"errors"
"time"
rolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
rolloutsclient "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/utils/ptr"
)
// ArgoRolloutAdapter implements WorkloadAdapter for Argo Rollouts.
type ArgoRolloutAdapter struct {
rolloutsClient rolloutsclient.Interface
}
// NewArgoRolloutAdapter creates a new ArgoRolloutAdapter.
func NewArgoRolloutAdapter(rolloutsClient rolloutsclient.Interface) *ArgoRolloutAdapter {
return &ArgoRolloutAdapter{
rolloutsClient: rolloutsClient,
}
}
// Type returns the workload type.
func (a *ArgoRolloutAdapter) Type() WorkloadType {
return WorkloadArgoRollout
}
// Create creates an Argo Rollout with the given config.
func (a *ArgoRolloutAdapter) Create(ctx context.Context, namespace, name string, cfg WorkloadConfig) error {
rollout := baseRollout(name)
opts := buildRolloutOptions(cfg)
for _, opt := range opts {
opt(rollout)
}
_, err := a.rolloutsClient.ArgoprojV1alpha1().Rollouts(namespace).Create(ctx, rollout, metav1.CreateOptions{})
return err
}
// Delete removes the Argo Rollout.
func (a *ArgoRolloutAdapter) Delete(ctx context.Context, namespace, name string) error {
return a.rolloutsClient.ArgoprojV1alpha1().Rollouts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
}
// WaitReady waits for the Argo Rollout to be ready using watches.
func (a *ArgoRolloutAdapter) WaitReady(ctx context.Context, namespace, name string, timeout time.Duration) error {
watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
return a.rolloutsClient.ArgoprojV1alpha1().Rollouts(namespace).Watch(ctx, opts)
}
_, err := WatchUntil(ctx, watchFunc, name, IsReady(RolloutIsReady), timeout)
return err
}
// WaitReloaded waits for the Argo Rollout to have the reload annotation using watches.
func (a *ArgoRolloutAdapter) WaitReloaded(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.rolloutsClient.ArgoprojV1alpha1().Rollouts(namespace).Watch(ctx, opts)
}
_, err := WatchUntil(ctx, watchFunc, name, HasPodTemplateAnnotation(RolloutPodTemplate, annotationKey), timeout)
if errors.Is(err, ErrWatchTimeout) {
return false, nil
}
return err == nil, err
}
// WaitEnvVar waits for the Argo Rollout to have a STAKATER_ env var using watches.
func (a *ArgoRolloutAdapter) WaitEnvVar(ctx context.Context, namespace, name, prefix string, timeout time.Duration) (bool, error) {
watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
return a.rolloutsClient.ArgoprojV1alpha1().Rollouts(namespace).Watch(ctx, opts)
}
_, err := WatchUntil(ctx, watchFunc, name, HasEnvVarPrefix(RolloutContainers, prefix), timeout)
if errors.Is(err, ErrWatchTimeout) {
return false, nil
}
return err == nil, err
}
// WaitRestartAt waits for the Argo Rollout to have the restartAt field set using watches.
// This is used when Reloader is configured with rollout strategy=restart.
func (a *ArgoRolloutAdapter) WaitRestartAt(ctx context.Context, namespace, name string, timeout time.Duration) (bool, error) {
watchFunc := func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
return a.rolloutsClient.ArgoprojV1alpha1().Rollouts(namespace).Watch(ctx, opts)
}
_, err := WatchUntil(ctx, watchFunc, name, IsReady(RolloutHasRestartAt), timeout)
if errors.Is(err, ErrWatchTimeout) {
return false, nil
}
return err == nil, err
}
// SupportsEnvVarStrategy returns true as Argo Rollouts support env var reload strategy.
func (a *ArgoRolloutAdapter) SupportsEnvVarStrategy() bool {
return true
}
// RequiresSpecialHandling returns false as Argo Rollouts use standard rolling restart.
func (a *ArgoRolloutAdapter) RequiresSpecialHandling() bool {
return false
}
// baseRollout returns a minimal Rollout template.
func baseRollout(name string) *rolloutv1alpha1.Rollout {
return &rolloutv1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: rolloutv1alpha1.RolloutSpec{
Replicas: ptr.To[int32](1),
Selector: &metav1.LabelSelector{
MatchLabels: 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},
}},
},
},
Strategy: rolloutv1alpha1.RolloutStrategy{
Canary: &rolloutv1alpha1.CanaryStrategy{
Steps: []rolloutv1alpha1.CanaryStep{
{SetWeight: ptr.To[int32](100)},
},
},
},
},
}
}
// buildRolloutOptions converts WorkloadConfig to RolloutOption slice.
func buildRolloutOptions(cfg WorkloadConfig) []RolloutOption {
return []RolloutOption{
func(r *rolloutv1alpha1.Rollout) {
// Set annotations on Rollout level (where Reloader checks them)
if len(cfg.Annotations) > 0 {
if r.Annotations == nil {
r.Annotations = make(map[string]string)
}
for k, v := range cfg.Annotations {
r.Annotations[k] = v
}
}
ApplyWorkloadConfig(&r.Spec.Template, cfg)
},
}
}