Files
flagger/pkg/router/kuma.go
jonny.langefeld e8aba087ac Enable updates for labels and annotations
Fix #1386

Signed-off-by: jonny.langefeld <jonnylangefeld@gmail.com>
Signed-off-by: Jonny Langefeld <jonnylangefeld@gmail.com>
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
2023-04-12 16:04:28 +05:30

214 lines
6.0 KiB
Go

package router
import (
"context"
"fmt"
"strings"
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
kumav1alpha1 "github.com/fluxcd/flagger/pkg/apis/kuma/v1alpha1"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.uber.org/zap"
"k8s.io/client-go/kubernetes"
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
)
// KumaRouter is managing TrafficRoute objects
type KumaRouter struct {
kubeClient kubernetes.Interface
kumaClient clientset.Interface
flaggerClient clientset.Interface
logger *zap.SugaredLogger
}
// Reconcile creates or updates the Kuma TrafficRoute
func (kr *KumaRouter) Reconcile(canary *flaggerv1.Canary) error {
apexName, primaryName, canaryName := canary.GetServiceNames()
trSpec := kumav1alpha1.TrafficRouteSpec{
Sources: []*kumav1alpha1.Selector{
{
Match: map[string]string{
"kuma.io/service": "*",
},
},
},
Destinations: []*kumav1alpha1.Selector{
{
Match: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", apexName, canary.Namespace, canary.Spec.Service.Port),
},
},
},
Conf: &kumav1alpha1.TrafficRouteConf{
Split: []*kumav1alpha1.TrafficRouteSplit{
{
Weight: uint32(100),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", primaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
{
Weight: uint32(0),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", canaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
},
},
}
newMetadata := canary.Spec.Service.Apex
if newMetadata == nil {
newMetadata = &flaggerv1.CustomMetadata{}
}
if newMetadata.Labels == nil {
newMetadata.Labels = make(map[string]string)
}
if newMetadata.Annotations == nil {
newMetadata.Annotations = make(map[string]string)
}
newMetadata.Annotations = filterMetadata(newMetadata.Annotations)
tr, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), apexName, metav1.GetOptions{})
// create TrafficRoute
if errors.IsNotFound(err) {
meshName, ok := canary.Annotations["kuma.io/mesh"]
if !ok {
meshName = "default"
}
// TrafficRoute is a cluster-scoped object, hence we don't set an owner reference.
t := &kumav1alpha1.TrafficRoute{
ObjectMeta: metav1.ObjectMeta{
Name: apexName,
Labels: newMetadata.Labels,
Annotations: newMetadata.Annotations,
},
Spec: trSpec,
Mesh: meshName,
}
_, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Create(context.TODO(), t, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s create error: %w", apexName, err)
}
kr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("TrafficRoute %s created", t.GetName())
return nil
} else if err != nil {
return fmt.Errorf("TrafficRoute %s get query error: %w", apexName, err)
}
// update TrafficRoute
if tr != nil {
specDiff := cmp.Diff(trSpec, tr.Spec, cmpopts.IgnoreFields(kumav1alpha1.TrafficRouteSplit{}, "Weight"))
labelsDiff := cmp.Diff(newMetadata.Labels, tr.Labels, cmpopts.EquateEmpty())
annotationsDiff := cmp.Diff(newMetadata.Annotations, tr.Annotations, cmpopts.EquateEmpty())
if specDiff != "" || labelsDiff != "" || annotationsDiff != "" {
trClone := tr.DeepCopy()
trClone.Spec = trSpec
trClone.ObjectMeta.Annotations = newMetadata.Annotations
trClone.ObjectMeta.Labels = newMetadata.Labels
_, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Update(context.TODO(), trClone, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s update error: %w", apexName, err)
}
kr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("TrafficRoute %s.%s updated", apexName, canary.Namespace)
return nil
}
}
return nil
}
// GetRoutes returns the destinations weight for primary and canary
func (kr *KumaRouter) GetRoutes(canary *flaggerv1.Canary) (
primaryWeight int,
canaryWeight int,
mirrored bool,
err error,
) {
apexName, primaryName, canaryName := canary.GetServiceNames()
tr, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), apexName, metav1.GetOptions{})
if err != nil {
err = fmt.Errorf("TrafficRoute %s get query error %v", apexName, err)
return
}
for _, split := range tr.Spec.Conf.Split {
if strings.Split(split.Destination["kuma.io/service"], "_")[0] == primaryName {
primaryWeight = int(split.Weight)
canaryWeight = 100 - primaryWeight
}
}
if primaryWeight == 0 && canaryWeight == 0 {
err = fmt.Errorf("TrafficRoute %s does not contain routes for %s and %s",
apexName, primaryName, canaryName)
}
mirrored = false
return
}
// SetRoutes updates the destinations weight for primary and canary
func (kr *KumaRouter) SetRoutes(
canary *flaggerv1.Canary,
primaryWeight int,
canaryWeight int,
_ bool,
) error {
apexName, primaryName, canaryName := canary.GetServiceNames()
tr, err := kr.kumaClient.KumaV1alpha1().TrafficRoutes().Get(context.TODO(), apexName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s get query error %v", apexName, err)
}
conf := &kumav1alpha1.TrafficRouteConf{
Split: []*kumav1alpha1.TrafficRouteSplit{
{
Weight: uint32(primaryWeight),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", primaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
{
Weight: uint32(canaryWeight),
Destination: map[string]string{
"kuma.io/service": fmt.Sprintf("%s_%s_svc_%d", canaryName, canary.Namespace, canary.Spec.Service.Port),
},
},
},
}
trClone := tr.DeepCopy()
trClone.Spec.Conf = conf
_, err = kr.kumaClient.KumaV1alpha1().TrafficRoutes().Update(context.TODO(), trClone, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("TrafficRoute %s update error %v", apexName, err)
}
return nil
}
func (kr *KumaRouter) Finalize(_ *flaggerv1.Canary) error {
return nil
}