Files
flagger/pkg/controller/scheduler_deployment_fixture_test.go
T
Pedram Pourmohammad 8eaa0408af Keep canary alive when primary promotion fails
When the canary analysis succeeds, Flagger copies the canary pod spec
to the primary and waits for the primary rollout to finish. If the
primary fails to become ready, the non-retriable readiness error
triggered the standard analysis rollback, which routes all traffic to
the primary and scales the canary to zero.

During promotion the primary already runs the new (failing) spec while
the canary is the only healthy copy of the new revision still serving
traffic. Rolling back therefore sends all traffic to the broken primary
and deletes the working canary, taking the application down.

Halt the promotion instead: when the primary is not ready and the canary
is in the Promoting or Finalising phase, mark the rollout as failed and
alert, but keep the canary running and leave routing untouched until the
primary recovers or a corrected revision is applied.

Fixes #1898

Signed-off-by: Pedram Pourmohammad <eragon.pedy@gmail.com>
2026-06-27 19:56:03 +03:30

843 lines
22 KiB
Go

/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
hpav2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
istiov1alpha1 "github.com/fluxcd/flagger/pkg/apis/istio/common/v1alpha1"
istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1"
"github.com/fluxcd/flagger/pkg/canary"
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
fakeFlagger "github.com/fluxcd/flagger/pkg/client/clientset/versioned/fake"
informers "github.com/fluxcd/flagger/pkg/client/informers/externalversions"
"github.com/fluxcd/flagger/pkg/logger"
"github.com/fluxcd/flagger/pkg/metrics"
"github.com/fluxcd/flagger/pkg/metrics/observers"
"github.com/fluxcd/flagger/pkg/notifier"
"github.com/fluxcd/flagger/pkg/router"
)
type fixture struct {
canary *flaggerv1.Canary
kubeClient kubernetes.Interface
meshClient clientset.Interface
flaggerClient clientset.Interface
deployer canary.Controller
ctrl *Controller
logger *zap.SugaredLogger
router router.Interface
}
func (f fixture) makePrimaryReady(t *testing.T) {
primaryName := fmt.Sprintf("%s-primary", f.canary.Spec.TargetRef.Name)
f.makeReady(t, primaryName)
}
func (f fixture) makeCanaryReady(t *testing.T) {
f.makeReady(t, f.canary.Spec.TargetRef.Name)
}
func (f fixture) makeReady(t *testing.T, name string) {
p, err := f.kubeClient.AppsV1().
Deployments("default").
Get(context.TODO(), name, metav1.GetOptions{})
require.NoError(t, err)
p.Status = appsv1.DeploymentStatus{Replicas: 1, UpdatedReplicas: 1,
ReadyReplicas: 1, AvailableReplicas: 1}
_, err = f.kubeClient.AppsV1().Deployments("default").Update(context.TODO(), p, metav1.UpdateOptions{})
require.NoError(t, err)
}
// makePrimaryNotReady puts the primary into a stuck rollout (progress deadline exceeded).
func (f fixture) makePrimaryNotReady(t *testing.T) {
primaryName := fmt.Sprintf("%s-primary", f.canary.Spec.TargetRef.Name)
p, err := f.kubeClient.AppsV1().
Deployments("default").
Get(context.TODO(), primaryName, metav1.GetOptions{})
require.NoError(t, err)
p.Status = appsv1.DeploymentStatus{
ObservedGeneration: p.Generation,
Replicas: 1,
UpdatedReplicas: 1,
ReadyReplicas: 0,
AvailableReplicas: 0,
Conditions: []appsv1.DeploymentCondition{
{
Type: appsv1.DeploymentProgressing,
Status: corev1.ConditionFalse,
Reason: "ProgressDeadlineExceeded",
},
{
Type: appsv1.DeploymentAvailable,
Status: corev1.ConditionFalse,
Reason: "MinimumReplicasUnavailable",
LastUpdateTime: metav1.Now(),
},
},
}
_, err = f.kubeClient.AppsV1().Deployments("default").Update(context.TODO(), p, metav1.UpdateOptions{})
require.NoError(t, err)
}
func newDeploymentFixture(c *flaggerv1.Canary) fixture {
if c == nil {
c = newDeploymentTestCanary()
}
// init Flagger clientset and register objects
flaggerClient := fakeFlagger.NewSimpleClientset(
c,
newDeploymentTestMetricTemplate(),
newDeploymentTestMetricTemplateCustomVars(),
newDeploymentTestAlertProvider(),
)
// init Kubernetes clientset and register objects
kubeClient := fake.NewSimpleClientset(
newDeploymentTestDeployment(),
newDeploymentTestService(),
newDeploymentTestHPA(),
newDeploymentTestConfigMap(),
newDeploymentTestConfigMapEnv(),
newDeploymentTestConfigMapVol(),
newDeploymentTestSecret(),
newDeploymentTestSecretEnv(),
newDeploymentTestSecretVol(),
newDeploymentTestAlertProviderSecret(),
)
logger, _ := logger.NewLogger("debug")
// init controller
flaggerInformerFactory := informers.NewSharedInformerFactory(flaggerClient, 0)
fi := Informers{
CanaryInformer: flaggerInformerFactory.Flagger().V1beta1().Canaries(),
MetricInformer: flaggerInformerFactory.Flagger().V1beta1().MetricTemplates(),
AlertInformer: flaggerInformerFactory.Flagger().V1beta1().AlertProviders(),
}
// init router
rf := router.NewFactory(nil, kubeClient, flaggerClient, nil, "annotationsPrefix", "", logger, flaggerClient, true)
// init observer
observerFactory, _ := observers.NewFactory(testMetricsServerURL)
// init canary factory
configTracker := &canary.ConfigTracker{
Logger: logger,
KubeClient: kubeClient,
FlaggerClient: flaggerClient,
}
canaryFactory := canary.NewFactory(kubeClient, flaggerClient, nil, configTracker, []string{"app", "name"}, []string{""}, logger)
ctrl := &Controller{
kubeClient: kubeClient,
flaggerClient: flaggerClient,
flaggerInformers: fi,
flaggerSynced: fi.CanaryInformer.Informer().HasSynced,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerAgentName),
eventRecorder: &record.FakeRecorder{},
logger: logger,
canaries: new(sync.Map),
flaggerWindow: time.Second,
canaryFactory: canaryFactory,
observerFactory: observerFactory,
recorder: metrics.NewRecorder(controllerAgentName, false),
routerFactory: rf,
notifier: &notifier.NopNotifier{},
}
ctrl.flaggerSynced = alwaysReady
ctrl.flaggerInformers.CanaryInformer.Informer().GetIndexer().Add(c)
ctrl.flaggerInformers.MetricInformer.Informer().GetIndexer().Add(newDeploymentTestMetricTemplate())
ctrl.flaggerInformers.MetricInformer.Informer().GetIndexer().Add(newDeploymentTestMetricTemplateCustomVars())
ctrl.flaggerInformers.AlertInformer.Informer().GetIndexer().Add(newDeploymentTestAlertProvider())
meshRouter := rf.MeshRouter("istio", "")
return fixture{
canary: c,
deployer: canaryFactory.Controller(flaggerv1.LocalObjectReference{
Kind: "Deployment",
}),
logger: logger,
flaggerClient: flaggerClient,
meshClient: flaggerClient,
kubeClient: kubeClient,
ctrl: ctrl,
router: meshRouter,
}
}
func newDeploymentTestConfigMap() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-env",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDeploymentTestConfigMapV2() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-env",
},
Data: map[string]string{
"color": "blue",
"output": "console",
},
}
}
func newDeploymentTestConfigMapEnv() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-all-env",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDeploymentTestConfigMapVol() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-config-vol",
},
Data: map[string]string{
"color": "red",
},
}
}
func newDeploymentTestSecret() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-env",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
"username": []byte("test"),
"password": []byte("test"),
},
}
}
func newDeploymentTestSecretV2() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-env",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test2"),
"username": []byte("test"),
"password": []byte("test"),
},
}
}
func newDeploymentTestSecretEnv() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-all-env",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
},
}
}
func newDeploymentTestSecretVol() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo-secret-vol",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"apiKey": []byte("test"),
},
}
}
func newDeploymentTestCanary() *flaggerv1.Canary {
cd := &flaggerv1.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: flaggerv1.CanarySpec{
TargetRef: flaggerv1.LocalObjectReference{
Name: "podinfo",
APIVersion: "apps/v1",
Kind: "Deployment",
},
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
APIVersion: "autoscaling/v2",
Kind: "HorizontalPodAutoscaler",
}, Service: flaggerv1.CanaryService{
Port: 9898,
}, Analysis: &flaggerv1.CanaryAnalysis{
Threshold: 10,
StepWeight: 10,
MaxWeight: 50,
Metrics: []flaggerv1.CanaryMetric{
{
Name: "request-success-rate",
Threshold: 99,
Interval: "1m",
},
{
Name: "request-duration",
ThresholdRange: &flaggerv1.CanaryThresholdRange{
Min: toFloatPtr(0),
Max: toFloatPtr(500000),
},
Interval: "1m",
},
{
Name: "custom",
ThresholdRange: &flaggerv1.CanaryThresholdRange{
Min: toFloatPtr(0),
Max: toFloatPtr(100),
},
Interval: "1m",
TemplateRef: &flaggerv1.CrossNamespaceObjectReference{
Name: "envoy",
Namespace: "default",
},
},
},
},
},
}
return cd
}
func newDeploymentTestCanaryMirror() *flaggerv1.Canary {
cd := newDeploymentTestCanary()
cd.Spec.Analysis.Mirror = true
return cd
}
func newDeploymentTestCanaryAB() *flaggerv1.Canary {
cd := &flaggerv1.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: flaggerv1.CanarySpec{
TargetRef: flaggerv1.LocalObjectReference{
Name: "podinfo",
APIVersion: "apps/v1",
Kind: "Deployment",
},
AutoscalerRef: &flaggerv1.AutoscalerReference{
Name: "podinfo",
APIVersion: "autoscaling/v2",
Kind: "HorizontalPodAutoscaler",
}, Service: flaggerv1.CanaryService{
Port: 9898,
}, Analysis: &flaggerv1.CanaryAnalysis{
Threshold: 10,
Iterations: 10,
Match: []istiov1beta1.HTTPMatchRequest{
{
Headers: map[string]istiov1alpha1.StringMatch{
"x-user-type": {
Exact: "test",
},
},
},
},
Metrics: []flaggerv1.CanaryMetric{
{
Name: "request-success-rate",
ThresholdRange: &flaggerv1.CanaryThresholdRange{
Min: toFloatPtr(99),
Max: toFloatPtr(100),
},
Interval: "1m",
},
{
Name: "request-duration",
Threshold: 500000,
Interval: "1m",
},
{
Name: "custom",
ThresholdRange: &flaggerv1.CanaryThresholdRange{
Min: toFloatPtr(0),
Max: toFloatPtr(500000),
},
TemplateRef: &flaggerv1.CrossNamespaceObjectReference{
Name: "envoy",
Namespace: "default",
},
Interval: "1m",
Query: "fake",
},
},
},
},
}
return cd
}
func newDeploymentTestDeployment() *appsv1.Deployment {
d := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "podinfo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "podinfo",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "podinfo",
Image: "quay.io/stefanprodan/podinfo:1.2.0",
Command: []string{
"./podinfo",
"--port=9898",
},
Args: nil,
WorkingDir: "",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 9898,
Protocol: corev1.ProtocolTCP,
},
{
Name: "http-metrics",
ContainerPort: 8080,
Protocol: corev1.ProtocolTCP,
},
{
ContainerPort: 8888,
},
},
Env: []corev1.EnvVar{
{
Name: "PODINFO_UI_COLOR",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-env",
},
Key: "color",
},
},
},
{
Name: "API_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-env",
},
Key: "apiKey",
},
},
},
},
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-all-env",
},
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-all-env",
},
},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "config",
MountPath: "/etc/podinfo/config",
ReadOnly: true,
},
{
Name: "secret",
MountPath: "/etc/podinfo/secret",
ReadOnly: true,
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-vol",
},
},
},
},
{
Name: "secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "podinfo-secret-vol",
},
},
},
},
},
},
},
}
return d
}
func newDeploymentTestDeploymentV2() *appsv1.Deployment {
d := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "podinfo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "podinfo",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "podinfo",
Image: "quay.io/stefanprodan/podinfo:1.2.1",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 9898,
Protocol: corev1.ProtocolTCP,
},
},
Command: []string{
"./podinfo",
"--port=9898",
},
Env: []corev1.EnvVar{
{
Name: "PODINFO_UI_COLOR",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-env",
},
Key: "color",
},
},
},
{
Name: "API_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-secret-env",
},
Key: "apiKey",
},
},
},
},
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-all-env",
},
},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "config",
MountPath: "/etc/podinfo/config",
ReadOnly: true,
},
{
Name: "secret",
MountPath: "/etc/podinfo/secret",
ReadOnly: true,
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "podinfo-config-vol",
},
},
},
},
{
Name: "secret",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "podinfo-secret-vol",
},
},
},
},
},
},
},
}
return d
}
func newDeploymentTestService() *corev1.Service {
d := &corev1.Service{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": "podinfo",
},
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 9898,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromString("http"),
},
},
},
}
return d
}
func newDeploymentTestServiceV2() *corev1.Service {
d := &corev1.Service{
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": "podinfo-v2",
},
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 9898,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromString("http"),
},
},
},
}
return d
}
func newDeploymentTestHPA() *hpav2.HorizontalPodAutoscaler {
h := &hpav2.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{APIVersion: hpav2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: hpav2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: hpav2.CrossVersionObjectReference{
Name: "podinfo",
APIVersion: "apps/v1",
Kind: "Deployment",
},
Metrics: []hpav2.MetricSpec{
{
Type: "Resource",
Resource: &hpav2.ResourceMetricSource{
Name: "cpu",
Target: hpav2.MetricTarget{
AverageUtilization: int32p(99),
},
},
},
},
},
}
return h
}
func newDeploymentTestMetricTemplate() *flaggerv1.MetricTemplate {
provider := flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: testMetricsServerURL,
SecretRef: &corev1.LocalObjectReference{
Name: "podinfo-secret-env",
},
}
template := &flaggerv1.MetricTemplate{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "envoy",
},
Spec: flaggerv1.MetricTemplateSpec{
Provider: provider,
Query: `sum(envoy_cluster_upstream_rq{envoy_cluster_name=~"{{ namespace }}_{{ target }}"})`,
},
}
return template
}
func newDeploymentTestMetricTemplateCustomVars() *flaggerv1.MetricTemplate {
provider := flaggerv1.MetricTemplateProvider{
Type: "prometheus",
Address: testMetricsServerURL,
SecretRef: &corev1.LocalObjectReference{
Name: "podinfo-secret-env",
},
}
template := &flaggerv1.MetricTemplate{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "custom-vars",
},
Spec: flaggerv1.MetricTemplateSpec{
Provider: provider,
Query: `sum(envoy_cluster_upstream_rq{envoy_cluster_name=~"{{ namespace }}_{{ target }},custom_label!={{ variables.second }}"})`,
},
}
return template
}
func newDeploymentTestAlertProviderSecret() *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "alert-secret",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"address": []byte("http://mock.slack"),
},
}
}
func newDeploymentTestAlertProvider() *flaggerv1.AlertProvider {
return &flaggerv1.AlertProvider{
TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "slack",
},
Spec: flaggerv1.AlertProviderSpec{
Type: "slack",
Address: "http://fake.slack",
SecretRef: &corev1.LocalObjectReference{
Name: "alert-secret",
},
},
}
}