mirror of
https://github.com/fluxcd/flagger.git
synced 2026-02-14 09:59:58 +00:00
Added a new field to canary spec to specify unmanaged metadata
These are labels and annotations that should be ignored by Flagger (i.e. not overwritten upon reconciliation). See: github.com/fluxcd/flagger/issues/1573 Signed-off-by: Brian Sonnenberg <bsonnenberg@google.com>
This commit is contained in:
@@ -909,6 +909,18 @@ spec:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
unmanagedMetadata:
|
||||
description: UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
|
||||
type: object
|
||||
properties:
|
||||
annotations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
skipAnalysis:
|
||||
description: Skip analysis and promote canary
|
||||
type: boolean
|
||||
|
||||
@@ -909,6 +909,18 @@ spec:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
unmanagedMetadata:
|
||||
description: UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
|
||||
type: object
|
||||
properties:
|
||||
annotations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
skipAnalysis:
|
||||
description: Skip analysis and promote canary
|
||||
type: boolean
|
||||
|
||||
@@ -206,6 +206,10 @@ Note that the `apex` annotations are added to both the generated Kubernetes Serv
|
||||
generated service mesh/ingress object. This allows using external-dns with Istio `VirtualServices`
|
||||
and `TraefikServices`. Beware of configuration conflicts [here](../faq.md#ExternalDNS).
|
||||
|
||||
Note that if any annotations or labels are added that are not specified here,
|
||||
Flagger will remove them during reconciliation. To specify metadata
|
||||
that should be ignored by Flagger, configure `unmanagedMetadata`.
|
||||
|
||||
If you want for the generated Kubernetes ClusterIP services to be [headless](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services),
|
||||
then set `service.headless` to true.
|
||||
|
||||
|
||||
@@ -909,6 +909,18 @@ spec:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
unmanagedMetadata:
|
||||
description: UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
|
||||
type: object
|
||||
properties:
|
||||
annotations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
skipAnalysis:
|
||||
description: Skip analysis and promote canary
|
||||
type: boolean
|
||||
|
||||
@@ -223,6 +223,17 @@ type CanaryService struct {
|
||||
// Canary is the metadata to add to the canary service
|
||||
// +optional
|
||||
Canary *CustomMetadata `json:"canary,omitempty"`
|
||||
|
||||
// UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
|
||||
// Flagger will not add, remove or change the value of these annotations.
|
||||
// +optional
|
||||
UnmanagedMetadata *UnmanagedMetadata `json:"unmanagedMetadata,omitempty"`
|
||||
}
|
||||
|
||||
// UnmanagedMetadata is a list of metadata keys that should be ignored by Flagger.
|
||||
type UnmanagedMetadata struct {
|
||||
Annotations []string `json:"annotations,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// CanaryAnalysis is used to describe how the analysis should be done
|
||||
|
||||
@@ -452,6 +452,11 @@ func (in *CanaryService) DeepCopyInto(out *CanaryService) {
|
||||
*out = new(CustomMetadata)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.UnmanagedMetadata != nil {
|
||||
in, out := &in.UnmanagedMetadata, &out.UnmanagedMetadata
|
||||
*out = new(UnmanagedMetadata)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -926,3 +931,29 @@ func (in *SessionAffinity) DeepCopy() *SessionAffinity {
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UnmanagedMetadata) DeepCopyInto(out *UnmanagedMetadata) {
|
||||
*out = *in
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnmanagedMetadata.
|
||||
func (in *UnmanagedMetadata) DeepCopy() *UnmanagedMetadata {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UnmanagedMetadata)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -213,12 +213,42 @@ func (c *KubernetesDefaultRouter) reconcileService(canary *flaggerv1.Canary, nam
|
||||
if svc.ObjectMeta.Annotations == nil {
|
||||
svc.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
if diff := cmp.Diff(filterMetadata(metadata.Annotations), svc.ObjectMeta.Annotations); diff != "" {
|
||||
svcClone.ObjectMeta.Annotations = filterMetadata(metadata.Annotations)
|
||||
|
||||
// Preserve unmanaged metadata
|
||||
unmanagedAnnotations := make(map[string]string)
|
||||
if canary.Spec.Service.UnmanagedMetadata != nil {
|
||||
for _, key := range canary.Spec.Service.UnmanagedMetadata.Annotations {
|
||||
if value, ok := svc.ObjectMeta.Annotations[key]; ok {
|
||||
unmanagedAnnotations[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unmanagedLabels := make(map[string]string)
|
||||
if canary.Spec.Service.UnmanagedMetadata != nil {
|
||||
for _, key := range canary.Spec.Service.UnmanagedMetadata.Labels {
|
||||
if value, ok := svc.ObjectMeta.Labels[key]; ok {
|
||||
unmanagedLabels[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newAnnotations := filterMetadata(metadata.Annotations)
|
||||
for k, v := range unmanagedAnnotations {
|
||||
newAnnotations[k] = v
|
||||
}
|
||||
|
||||
newLabels := metadata.Labels
|
||||
for k, v := range unmanagedLabels {
|
||||
newLabels[k] = v
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(newAnnotations, svc.ObjectMeta.Annotations); diff != "" {
|
||||
svcClone.ObjectMeta.Annotations = newAnnotations
|
||||
updateService = true
|
||||
}
|
||||
if diff := cmp.Diff(metadata.Labels, svc.ObjectMeta.Labels); diff != "" {
|
||||
svcClone.ObjectMeta.Labels = metadata.Labels
|
||||
if diff := cmp.Diff(newLabels, svc.ObjectMeta.Labels); diff != "" {
|
||||
svcClone.ObjectMeta.Labels = newLabels
|
||||
updateService = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,3 +451,147 @@ func TestServiceRouter_ReconcileMetadata(t *testing.T) {
|
||||
assert.Equal(t, "test1", apexSvc.Labels["test"])
|
||||
assert.Equal(t, "podinfo", apexSvc.Labels["app"])
|
||||
}
|
||||
|
||||
func TestServiceRouter_UnmanagedAnnotations(t *testing.T) {
|
||||
mocks := newFixture(nil)
|
||||
router := &KubernetesDefaultRouter{
|
||||
kubeClient: mocks.kubeClient,
|
||||
flaggerClient: mocks.flaggerClient,
|
||||
logger: mocks.logger,
|
||||
labelSelector: "app",
|
||||
}
|
||||
|
||||
mocks.canary.Spec.Service.Apex = &flaggerv1.CustomMetadata{
|
||||
Annotations: map[string]string{"test": "expectedvalue"},
|
||||
}
|
||||
mocks.canary.Spec.Service.UnmanagedMetadata = &flaggerv1.UnmanagedMetadata{
|
||||
Annotations: []string{"unmanaged"},
|
||||
}
|
||||
|
||||
err := router.Initialize(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
apexSvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
clone := apexSvc.DeepCopy()
|
||||
clone.Annotations["unmanaged"] = "true"
|
||||
clone.Annotations["test"] = "newvalue"
|
||||
clone.Annotations["removable"] = "true"
|
||||
_, err = mocks.kubeClient.CoreV1().Services("default").Update(context.TODO(), clone, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
apexSvc, err = mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "expectedvalue", apexSvc.Annotations["test"])
|
||||
assert.Equal(t, "true", apexSvc.Annotations["unmanaged"])
|
||||
_, ok := apexSvc.Annotations["removable"]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestServiceRouter_UnmanagedLabels(t *testing.T) {
|
||||
mocks := newFixture(nil)
|
||||
router := &KubernetesDefaultRouter{
|
||||
kubeClient: mocks.kubeClient,
|
||||
flaggerClient: mocks.flaggerClient,
|
||||
logger: mocks.logger,
|
||||
labelSelector: "app",
|
||||
}
|
||||
|
||||
mocks.canary.Spec.Service.Apex = &flaggerv1.CustomMetadata{
|
||||
Labels: map[string]string{"test": "expectedvalue"},
|
||||
}
|
||||
mocks.canary.Spec.Service.UnmanagedMetadata = &flaggerv1.UnmanagedMetadata{
|
||||
Labels: []string{"unmanaged"},
|
||||
}
|
||||
|
||||
err := router.Initialize(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
apexSvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
clone := apexSvc.DeepCopy()
|
||||
clone.Labels["unmanaged"] = "true"
|
||||
clone.Labels["test"] = "newvalue"
|
||||
clone.Labels["removable"] = "true"
|
||||
_, err = mocks.kubeClient.CoreV1().Services("default").Update(context.TODO(), clone, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
apexSvc, err = mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "expectedvalue", apexSvc.Labels["test"])
|
||||
assert.Equal(t, "true", apexSvc.Labels["unmanaged"])
|
||||
_, ok := apexSvc.Labels["removable"]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestServiceRouter_UnmanagedMetadata_AnnotationsAndLabels(t *testing.T) {
|
||||
mocks := newFixture(nil)
|
||||
router := &KubernetesDefaultRouter{
|
||||
kubeClient: mocks.kubeClient,
|
||||
flaggerClient: mocks.flaggerClient,
|
||||
logger: mocks.logger,
|
||||
labelSelector: "app",
|
||||
}
|
||||
|
||||
mocks.canary.Spec.Service.Apex = &flaggerv1.CustomMetadata{
|
||||
Annotations: map[string]string{"test": "expectedvalue"},
|
||||
Labels: map[string]string{"test": "expectedvalue"},
|
||||
}
|
||||
mocks.canary.Spec.Service.UnmanagedMetadata = &flaggerv1.UnmanagedMetadata{
|
||||
Annotations: []string{"unmanaged"},
|
||||
Labels: []string{"unmanaged"},
|
||||
}
|
||||
|
||||
err := router.Initialize(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
apexSvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
clone := apexSvc.DeepCopy()
|
||||
clone.Annotations["unmanaged"] = "true"
|
||||
clone.Annotations["test"] = "newvalue"
|
||||
clone.Annotations["removable"] = "true"
|
||||
clone.Labels["unmanaged"] = "true"
|
||||
clone.Labels["test"] = "newvalue"
|
||||
clone.Labels["removable"] = "true"
|
||||
_, err = mocks.kubeClient.CoreV1().Services("default").Update(context.TODO(), clone, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
|
||||
apexSvc, err = mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// The result should be that the canary spec annotations should be changed back to configured canary value,
|
||||
// and the unmanaged annotation should remain unchanged.
|
||||
assert.Equal(t, "expectedvalue", apexSvc.Annotations["test"])
|
||||
assert.Equal(t, "true", apexSvc.Annotations["unmanaged"])
|
||||
_, ok := apexSvc.Annotations["removable"]
|
||||
assert.False(t, ok)
|
||||
|
||||
assert.Equal(t, "expectedvalue", apexSvc.Labels["test"])
|
||||
assert.Equal(t, "true", apexSvc.Labels["unmanaged"])
|
||||
_, ok = apexSvc.Labels["removable"]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user