Skip to content

Commit 6accdc0

Browse files
authored
Merge pull request #3008 from vmware/rr/impersonator-service-labels
impersonator config controller ignores Service labels added by others
2 parents b53b30e + 20b9a8a commit 6accdc0

3 files changed

Lines changed: 218 additions & 97 deletions

File tree

internal/controller/impersonatorconfig/impersonator_config.go

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
1+
// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

44
package impersonatorconfig
@@ -11,8 +11,9 @@ import (
1111
"encoding/json"
1212
"encoding/pem"
1313
"fmt"
14+
"maps"
1415
"net"
15-
"sort"
16+
"slices"
1617
"strings"
1718
"time"
1819

@@ -56,6 +57,7 @@ const (
5657
caKeyKey = "ca.key"
5758
appLabelKey = "app"
5859
annotationKeysKey = "credentialissuer.pinniped.dev/annotation-keys"
60+
labelKeysKey = "credentialissuer.pinniped.dev/label-keys"
5961
)
6062

6163
type impersonatorConfigController struct {
@@ -626,6 +628,62 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx conte
626628
return utilerrors.FilterOut(err, apierrors.IsNotFound)
627629
}
628630

631+
// recordDesiredKeysAsBookkeepingAnnotation adds a bookkeeping annotation to the given service that records which
632+
// keys were explicitly desired by this controller. This allows future runs to detect which keys have been removed
633+
// from the desired state (so they can be cleaned up) vs. which keys belong to other actors (which should
634+
// be left alone). The bookkeepingKey is the annotation key under which the JSON-encoded list of desired keys will
635+
// be stored. If the desiredMap is empty, no bookkeeping entry is added.
636+
func recordDesiredKeysAsBookkeepingAnnotation(service *corev1.Service, desiredMap map[string]string, bookkeepingKey string) error {
637+
desiredKeys := slices.Sorted(maps.Keys(desiredMap))
638+
639+
if len(desiredKeys) > 0 {
640+
keysJSONArray, err := json.Marshal(desiredKeys)
641+
if err != nil {
642+
return err // This shouldn't really happen. We should always be able to marshal an array of strings.
643+
}
644+
645+
// Save the desired keys to a bookkeeping annotation on the service.
646+
if service.Annotations == nil {
647+
service.Annotations = map[string]string{}
648+
}
649+
service.Annotations[bookkeepingKey] = string(keysJSONArray)
650+
}
651+
652+
return nil
653+
}
654+
655+
func mergeMap(existingMap map[string]string, desiredMap map[string]string, existingBookkeepingSource map[string]string, bookkeepingKey string) map[string]string {
656+
resultMap := maps.Clone(existingMap)
657+
if resultMap == nil {
658+
resultMap = map[string]string{}
659+
}
660+
661+
// Merge desired into existing, with desired overwriting when there are conflicts.
662+
for k, v := range desiredMap {
663+
resultMap[k] = v
664+
}
665+
666+
// Check if the existing Service contains a record of previous keys that were added by this controller.
667+
// Note that in an upgrade, older versions of Pinniped might have created the Service without this bookkeeping entry.
668+
oldDesiredKeysJSON, foundOldDesiredKeysJSON := existingBookkeepingSource[bookkeepingKey]
669+
oldDesiredKeys := []string{}
670+
if foundOldDesiredKeysJSON {
671+
_ = json.Unmarshal([]byte(oldDesiredKeysJSON), &oldDesiredKeys)
672+
// In the unlikely event that we cannot parse the value of our bookkeeping entry, just act like it
673+
// wasn't present and update it to the new value that it should have based on the current desired state.
674+
}
675+
676+
// Check if any keys which were previously managed by this controller are now gone from the desired state,
677+
// which means that those now-missing keys should get deleted.
678+
for _, oldKey := range oldDesiredKeys {
679+
if _, existsInDesired := desiredMap[oldKey]; !existsInDesired {
680+
delete(resultMap, oldKey)
681+
}
682+
}
683+
684+
return resultMap
685+
}
686+
629687
func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context, desiredService *corev1.Service) error {
630688
log := c.log.WithValues("serviceType", desiredService.Spec.Type, "service", klog.KObj(desiredService))
631689

@@ -634,19 +692,15 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context
634692
// to be able to detect that the missing key means that we should remove the key. This is needed to
635693
// differentiate it from a key that was added by another actor, which we should not remove.
636694
// But don't bother recording the requested annotations if there were no annotations requested.
637-
desiredAnnotationKeys := make([]string, 0, len(desiredService.Annotations))
638-
for k := range desiredService.Annotations {
639-
desiredAnnotationKeys = append(desiredAnnotationKeys, k)
640-
}
641-
if len(desiredAnnotationKeys) > 0 {
642-
// Sort them since they come out of the map in no particular order.
643-
sort.Strings(desiredAnnotationKeys)
644-
keysJSONArray, err := json.Marshal(desiredAnnotationKeys)
645-
if err != nil {
646-
return err // This shouldn't really happen. We should always be able to marshal an array of strings.
647-
}
648-
// Save the desired annotations to a bookkeeping annotation.
649-
desiredService.Annotations[annotationKeysKey] = string(keysJSONArray)
695+
err := recordDesiredKeysAsBookkeepingAnnotation(desiredService, desiredService.Annotations, annotationKeysKey)
696+
if err != nil {
697+
return err
698+
}
699+
700+
// Similarly, prepare to remember which label keys were added by this controller.
701+
err = recordDesiredKeysAsBookkeepingAnnotation(desiredService, desiredService.Labels, labelKeysKey)
702+
if err != nil {
703+
return err
650704
}
651705

652706
// Get the Service from the informer, and create it if it does not already exist.
@@ -662,49 +716,35 @@ func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context
662716

663717
// The Service already exists, so update only the specific fields that are meaningfully part of our desired state.
664718
updatedService := existingService.DeepCopy()
665-
updatedService.Labels = desiredService.Labels
666719
updatedService.Spec.LoadBalancerIP = desiredService.Spec.LoadBalancerIP
667720
updatedService.Spec.Type = desiredService.Spec.Type
668721
updatedService.Spec.Selector = desiredService.Spec.Selector
669722

670723
// Do not simply overwrite the existing annotations with the desired annotations. Instead, merge-overwrite.
671724
// Another actor in the system, like a human user or a non-Pinniped controller, might have updated the
672-
// existing Service's annotations. If they did, then we do not want to overwrite those keys expect for
725+
// existing Service's annotations. If they did, then we do not want to overwrite those keys except for
673726
// the specific keys that are from the CredentialIssuer's spec, because if we overwrite keys belonging
674727
// to another controller then we could end up infinitely flapping back and forth with the other controller,
675728
// both updating that annotation on the Service.
676-
if updatedService.Annotations == nil {
677-
updatedService.Annotations = map[string]string{}
678-
}
679-
for k, v := range desiredService.Annotations {
680-
updatedService.Annotations[k] = v
681-
}
682-
683-
// Check if the the existing Service contains a record of previous annotations that were added by this controller.
684-
// Note that in an upgrade, older versions of Pinniped might have created the Service without this bookkeeping annotation.
685-
oldDesiredAnnotationKeysJSON, foundOldDesiredAnnotationKeysJSON := existingService.Annotations[annotationKeysKey]
686-
oldDesiredAnnotationKeys := []string{}
687-
if foundOldDesiredAnnotationKeysJSON {
688-
_ = json.Unmarshal([]byte(oldDesiredAnnotationKeysJSON), &oldDesiredAnnotationKeys)
689-
// In the unlikely event that we cannot parse the value of our bookkeeping annotation, just act like it
690-
// wasn't present and update it to the new value that it should have based on the current desired state.
691-
}
692-
693-
// Check if any annotations which were previously in the CredentialIssuer spec are now gone from the spec,
694-
// which means that those now-missing annotations should get deleted.
695-
for _, oldKey := range oldDesiredAnnotationKeys {
696-
if _, existsInDesired := desiredService.Annotations[oldKey]; !existsInDesired {
697-
delete(updatedService.Annotations, oldKey)
698-
}
699-
}
729+
updatedService.Annotations = mergeMap(existingService.Annotations, desiredService.Annotations, existingService.Annotations, annotationKeysKey)
700730

701731
// If no annotations were requested, then remove the special bookkeeping annotation which might be
702732
// leftover from a previous update. During the next update, non-existence will be taken to mean
703733
// that no annotations were previously requested by the CredentialIssuer spec.
704-
if len(desiredAnnotationKeys) == 0 {
734+
if len(desiredService.Annotations) == 0 || mapHasExactlyOneKey(desiredService.Annotations, labelKeysKey) {
705735
delete(updatedService.Annotations, annotationKeysKey)
706736
}
707737

738+
// Same merge strategy as above, but for labels this time.
739+
updatedService.Labels = mergeMap(existingService.Labels, desiredService.Labels, existingService.Annotations, labelKeysKey)
740+
741+
// If no labels were requested, then remove the special bookkeeping annotation which might be
742+
// leftover from a previous update. During the next update, non-existence will be taken to mean
743+
// that no labels were previously requested by this controller.
744+
if len(desiredService.Labels) == 0 {
745+
delete(updatedService.Annotations, labelKeysKey)
746+
}
747+
708748
// If our updates didn't change anything, we're done.
709749
if equality.Semantic.DeepEqual(existingService, updatedService) {
710750
return nil
@@ -1222,3 +1262,11 @@ func validateCredentialIssuerSpec(spec *conciergeconfigv1alpha1.ImpersonationPro
12221262

12231263
return nil
12241264
}
1265+
1266+
func mapHasExactlyOneKey(m map[string]string, key string) bool {
1267+
if len(m) != 1 {
1268+
return false
1269+
}
1270+
_, ok := m[key]
1271+
return ok
1272+
}

0 commit comments

Comments
 (0)