-
Notifications
You must be signed in to change notification settings - Fork 64
/
federation_domain_secrets.go
229 lines (204 loc) · 8.87 KB
/
federation_domain_secrets.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package generator
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"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
supervisorconfigv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
supervisorclientset "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
configinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/config/v1alpha1"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/plog"
)
type federationDomainSecretsController struct {
secretHelper SecretHelper
secretRefFunc func(domain *supervisorconfigv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference
kubeClient kubernetes.Interface
pinnipedClient supervisorclientset.Interface
federationDomainInformer configinformers.FederationDomainInformer
secretInformer corev1informers.SecretInformer
}
// NewFederationDomainSecretsController returns a controllerlib.Controller that ensures a child Secret
// always exists for a parent FederationDomain. It does this using the provided secretHelper, which
// provides the parent/child mapping logic.
func NewFederationDomainSecretsController(
secretHelper SecretHelper,
secretRefFunc func(domain *supervisorconfigv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference,
kubeClient kubernetes.Interface,
pinnipedClient supervisorclientset.Interface,
secretInformer corev1informers.SecretInformer,
federationDomainInformer configinformers.FederationDomainInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller {
return controllerlib.New(
controllerlib.Config{
Name: fmt.Sprintf("%s%s", secretHelper.NamePrefix(), "controller"),
Syncer: &federationDomainSecretsController{
secretHelper: secretHelper,
secretRefFunc: secretRefFunc,
kubeClient: kubeClient,
pinnipedClient: pinnipedClient,
secretInformer: secretInformer,
federationDomainInformer: federationDomainInformer,
},
},
// We want to be notified when a FederationDomain's secret gets updated or deleted. When this happens, we
// should get notified via the corresponding FederationDomain key.
withInformer(
secretInformer,
pinnipedcontroller.SimpleFilter(secretHelper.Handles, pinnipedcontroller.SecretIsControlledByParentFunc(secretHelper.Handles)),
controllerlib.InformerOption{},
),
// We want to be notified when anything happens to an FederationDomain.
withInformer(
federationDomainInformer,
pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct
controllerlib.InformerOption{},
),
)
}
func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) error {
federationDomain, err := c.federationDomainInformer.Lister().FederationDomains(ctx.Key.Namespace).Get(ctx.Key.Name)
notFound := apierrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf(
"failed to get %s/%s FederationDomain: %w",
ctx.Key.Namespace,
ctx.Key.Name,
err,
)
}
if notFound {
// The corresponding secret to this FederationDomain should have been garbage collected since it should have
// had this FederationDomain as its owner.
plog.Debug(
"federationdomain deleted",
"federationdomain",
klog.KRef(ctx.Key.Namespace, ctx.Key.Name),
)
return nil
}
federationDomain = federationDomain.DeepCopy()
newSecret, err := c.secretHelper.Generate(federationDomain)
if err != nil {
return fmt.Errorf("failed to generate secret: %w", err)
}
secretNeedsUpdate, existingSecret, err := c.secretNeedsUpdate(federationDomain, newSecret.Name)
if err != nil {
return fmt.Errorf("failed to determine secret status: %w", err)
}
if !secretNeedsUpdate {
// Secret is up to date - we are good to go.
plog.Debug(
"secret is up to date",
"federationdomain",
klog.KObj(federationDomain),
"secret",
klog.KObj(existingSecret),
)
federationDomain = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(federationDomain, existingSecret)
if err := c.updateFederationDomainStatus(ctx.Context, federationDomain); err != nil {
return fmt.Errorf("failed to update federationdomain: %w", err)
}
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(federationDomain), "secret", klog.KObj(newSecret))
return nil
}
// If the FederationDomain does not have a secret associated with it, that secret does not exist, or the secret
// is invalid, we will create a new secret.
if err := c.createOrUpdateSecret(ctx.Context, federationDomain, &newSecret); err != nil {
return fmt.Errorf("failed to create or update secret: %w", err)
}
plog.Debug("created/updated secret", "federationdomain", klog.KObj(federationDomain), "secret", klog.KObj(newSecret))
federationDomain = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(federationDomain, newSecret)
if err := c.updateFederationDomainStatus(ctx.Context, federationDomain); err != nil {
return fmt.Errorf("failed to update federationdomain: %w", err)
}
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(federationDomain), "secret", klog.KObj(newSecret))
return nil
}
// secretNeedsUpdate returns whether or not the Secret, with name secretName, for the federationDomain param
// needs to be updated. It returns the existing secret as its second argument.
func (c *federationDomainSecretsController) secretNeedsUpdate(
federationDomain *supervisorconfigv1alpha1.FederationDomain,
secretName string,
) (bool, *corev1.Secret, error) {
// This FederationDomain says it has a secret associated with it. Let's try to get it from the cache.
secret, err := c.secretInformer.Lister().Secrets(federationDomain.Namespace).Get(secretName)
notFound := apierrors.IsNotFound(err)
if err != nil && !notFound {
return false, nil, fmt.Errorf("cannot get secret: %w", err)
}
if notFound {
// If we can't find the secret, let's assume we need to create it.
return true, nil, nil
}
if !c.secretHelper.IsValid(federationDomain, secret) {
// If this secret is invalid, we need to generate a new one.
return true, secret, nil
}
return false, secret, nil
}
func (c *federationDomainSecretsController) createOrUpdateSecret(
ctx context.Context,
federationDomain *supervisorconfigv1alpha1.FederationDomain,
newSecret **corev1.Secret,
) error {
secretClient := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace)
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
oldSecret, err := secretClient.Get(ctx, (*newSecret).Name, metav1.GetOptions{})
notFound := apierrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf("failed to get secret %s/%s: %w", (*newSecret).Namespace, (*newSecret).Name, err)
}
if notFound {
// New secret doesn't exist, so create it.
_, err := secretClient.Create(ctx, *newSecret, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("failed to create secret %s/%s: %w", (*newSecret).Namespace, (*newSecret).Name, err)
}
return nil
}
// New secret already exists, so ensure it is up to date.
if c.secretHelper.IsValid(federationDomain, oldSecret) {
// If the secret already has valid a valid Secret, then we are good to go and we don't need an
// update.
*newSecret = oldSecret
return nil
}
oldSecret.Labels = (*newSecret).Labels
oldSecret.Type = (*newSecret).Type
oldSecret.Data = (*newSecret).Data
*newSecret = oldSecret
_, err = secretClient.Update(ctx, oldSecret, metav1.UpdateOptions{})
return err
})
}
func (c *federationDomainSecretsController) updateFederationDomainStatus(
ctx context.Context,
newFederationDomain *supervisorconfigv1alpha1.FederationDomain,
) error {
federationDomainClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newFederationDomain.Namespace)
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
oldFederationDomain, err := federationDomainClient.Get(ctx, newFederationDomain.Name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get federationdomain %s/%s: %w", newFederationDomain.Namespace, newFederationDomain.Name, err)
}
oldFederationDomainSecretRef := c.secretRefFunc(&oldFederationDomain.Status)
newFederationDomainSecretRef := c.secretRefFunc(&newFederationDomain.Status)
if reflect.DeepEqual(oldFederationDomainSecretRef, newFederationDomainSecretRef) {
return nil
}
*oldFederationDomainSecretRef = *newFederationDomainSecretRef
_, err = federationDomainClient.UpdateStatus(ctx, oldFederationDomain, metav1.UpdateOptions{})
return err
})
}