2d4d7e588a
Signed-off-by: Matt Moyer <moyerm@vmware.com>
148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package controller
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
"k8s.io/client-go/tools/events"
|
|
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
"go.pinniped.dev/internal/controllerlib/test/integration/examplecontroller/api"
|
|
)
|
|
|
|
func NewExampleUpdatingController(
|
|
services corev1informers.ServiceInformer,
|
|
secrets corev1informers.SecretInformer,
|
|
secretClient corev1client.SecretsGetter,
|
|
recorder events.EventRecorder,
|
|
secretData string,
|
|
) controllerlib.Controller {
|
|
serviceLister := services.Lister()
|
|
secretLister := secrets.Lister()
|
|
|
|
// note that these functions do not need to be inlined
|
|
// this just demonstrates that for simple Syncer implementations, everything can be in one place
|
|
|
|
toServiceName := func(secret *corev1.Secret) (string, bool) {
|
|
serviceName := secret.Annotations[api.ServiceNameAnnotation]
|
|
return serviceName, len(serviceName) != 0
|
|
}
|
|
|
|
ensureSecretData := func(service *corev1.Service, secretCopy *corev1.Secret) bool {
|
|
var needsUpdate bool
|
|
|
|
expectedData := map[string][]byte{
|
|
api.SecretDataKey: []byte(secretData),
|
|
}
|
|
if !reflect.DeepEqual(secretCopy.Data, expectedData) {
|
|
secretCopy.Data = expectedData
|
|
needsUpdate = true
|
|
}
|
|
|
|
expectedOwnerReferences := []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "v1",
|
|
Kind: "Service",
|
|
Name: service.Name,
|
|
UID: service.UID,
|
|
},
|
|
}
|
|
if !reflect.DeepEqual(secretCopy.OwnerReferences, expectedOwnerReferences) {
|
|
secretCopy.OwnerReferences = expectedOwnerReferences
|
|
needsUpdate = true
|
|
}
|
|
|
|
return needsUpdate
|
|
}
|
|
|
|
isSecretValidForService := func(service *corev1.Service, secret *corev1.Secret) bool {
|
|
if service.Annotations[api.SecretNameAnnotation] != secret.Name {
|
|
return false
|
|
}
|
|
if secret.Annotations[api.ServiceUIDAnnotation] != string(service.UID) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
getServiceForSecret := func(secret *corev1.Secret) (*corev1.Service, error) {
|
|
serviceName, ok := toServiceName(secret)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
service, err := serviceLister.Services(secret.Namespace).Get(serviceName)
|
|
if apierrors.IsNotFound(err) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get service %s/%s: %w", secret.Namespace, serviceName, err)
|
|
}
|
|
return service, nil
|
|
}
|
|
|
|
syncer := controllerlib.SyncFunc(func(ctx controllerlib.Context) error {
|
|
secret, err := secretLister.Secrets(ctx.Key.Namespace).Get(ctx.Key.Name)
|
|
if apierrors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get the secret %s/%s: %w", secret.Namespace, secret.Name, err)
|
|
}
|
|
|
|
service, err := getServiceForSecret(secret)
|
|
if err != nil || service == nil {
|
|
return err
|
|
}
|
|
|
|
if !isSecretValidForService(service, secret) {
|
|
//nolint: goerr113
|
|
utilruntime.HandleError(fmt.Errorf("secret %s/%s does not have corresponding service UID %v", secret.Namespace, secret.Name, service.UID))
|
|
return nil
|
|
}
|
|
|
|
// make a copy to avoid mutating cache state
|
|
secretCopy := secret.DeepCopy()
|
|
|
|
if needsUpdate := ensureSecretData(service, secretCopy); needsUpdate {
|
|
_, updateErr := secretClient.Secrets(secretCopy.Namespace).Update(context.TODO(), secretCopy, metav1.UpdateOptions{})
|
|
return updateErr
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
config := controllerlib.Config{
|
|
Name: "example-controller-updating",
|
|
Syncer: syncer,
|
|
}
|
|
|
|
addSecret := func(obj metav1.Object) bool {
|
|
secret := obj.(*corev1.Secret)
|
|
_, ok := toServiceName(secret)
|
|
return ok
|
|
}
|
|
|
|
return controllerlib.New(config,
|
|
controllerlib.WithInformer(services, controllerlib.FilterFuncs{}, controllerlib.InformerOption{SkipEvents: true}),
|
|
|
|
controllerlib.WithInformer(secrets, controllerlib.FilterFuncs{
|
|
AddFunc: addSecret,
|
|
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
|
return addSecret(newObj) || addSecret(oldObj)
|
|
},
|
|
}, controllerlib.InformerOption{}),
|
|
|
|
controllerlib.WithRecorder(recorder), // TODO actually use the recorder
|
|
)
|
|
}
|