• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / containerized-data-importer / #5186

13 Mar 2025 09:17AM UTC coverage: 59.411% (-0.04%) from 59.452%
#5186

Pull #3673

travis-ci

akalenyu
fix certrotation unit tests

one is not supposed to run informers on a fake client
https://github.com/kubernetes/kubernetes/pull/95897
https://github.com/kubernetes-csi/external-attacher/blob/e32b64308/pkg/controller/framework_test.go#L160-L162

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
Pull Request #3673: don't start informer in cert rotation unit tests

16817 of 28306 relevant lines covered (59.41%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.73
/pkg/operator/controller/certrotation.go
1
/*
2
Copyright 2020 The CDI Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controller
18

19
import (
20
        "context"
21
        "crypto/x509"
22
        "encoding/json"
23
        "fmt"
24
        "time"
25

26
        "github.com/openshift/library-go/pkg/crypto"
27
        "github.com/openshift/library-go/pkg/operator/certrotation"
28
        "github.com/openshift/library-go/pkg/operator/events"
29
        "github.com/openshift/library-go/pkg/operator/v1helpers"
30

31
        corev1 "k8s.io/api/core/v1"
32
        "k8s.io/apimachinery/pkg/api/errors"
33
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34
        "k8s.io/apiserver/pkg/authentication/user"
35
        "k8s.io/client-go/kubernetes"
36
        listerscorev1 "k8s.io/client-go/listers/core/v1"
37
        toolscache "k8s.io/client-go/tools/cache"
38
        "k8s.io/utils/clock"
39

40
        "sigs.k8s.io/controller-runtime/pkg/manager"
41

42
        cdicerts "kubevirt.io/containerized-data-importer/pkg/operator/resources/cert"
43
)
44

45
const (
46
        annCertConfig = "operator.cdi.kubevirt.io/certConfig"
47
)
48

49
// CertManager is the client interface to the certificate manager/refresher
50
type CertManager interface {
51
        Sync(certs []cdicerts.CertificateDefinition) error
52
}
53

54
type certListers struct {
55
        secretLister    listerscorev1.SecretLister
56
        configMapLister listerscorev1.ConfigMapLister
57
}
58

59
type certManager struct {
60
        namespaces []string
61
        listerMap  map[string]*certListers
62

63
        k8sClient      kubernetes.Interface
64
        informers      v1helpers.KubeInformersForNamespaces
65
        eventRecorder  events.Recorder
66
        startInformers bool
67
}
68

69
type serializedCertConfig struct {
70
        Lifetime string `json:"lifetime,omitempty"`
71
        Refresh  string `json:"refresh,omitempty"`
72
}
73

74
// NewCertManager creates a new certificate manager/refresher
75
func NewCertManager(mgr manager.Manager, installNamespace string, additionalNamespaces ...string) (CertManager, error) {
×
76
        k8sClient, err := kubernetes.NewForConfig(mgr.GetConfig())
×
77
        if err != nil {
×
78
                return nil, err
×
79
        }
×
80

81
        startInformers := true
×
82
        cm := newCertManager(k8sClient, installNamespace, clock.RealClock{}, startInformers, additionalNamespaces...)
×
83

×
84
        // so we can start caches
×
85
        if err = mgr.Add(cm); err != nil {
×
86
                return nil, err
×
87
        }
×
88

89
        return cm, nil
×
90
}
91

92
func newCertManager(client kubernetes.Interface, installNamespace string, clock clock.PassiveClock, startInformers bool, additionalNamespaces ...string) *certManager {
1✔
93
        namespaces := append(additionalNamespaces, installNamespace)
1✔
94
        informers := v1helpers.NewKubeInformersForNamespaces(client, namespaces...)
1✔
95

1✔
96
        controllerRef, err := events.GetControllerReferenceForCurrentPod(context.TODO(), client, installNamespace, nil)
1✔
97
        if err != nil {
2✔
98
                log.Info("Unable to get controller reference, using namespace")
1✔
99
        }
1✔
100

101
        eventRecorder := events.NewRecorder(client.CoreV1().Events(installNamespace), installNamespace, controllerRef, clock)
1✔
102

1✔
103
        return &certManager{
1✔
104
                namespaces:     namespaces,
1✔
105
                k8sClient:      client,
1✔
106
                informers:      informers,
1✔
107
                eventRecorder:  eventRecorder,
1✔
108
                startInformers: startInformers,
1✔
109
        }
1✔
110
}
111

112
func (cm *certManager) Start(ctx context.Context) error {
1✔
113
        if cm.startInformers {
1✔
114
                cm.informers.Start(ctx.Done())
×
115
        }
×
116

117
        for _, ns := range cm.namespaces {
2✔
118
                secretInformer := cm.informers.InformersFor(ns).Core().V1().Secrets().Informer()
1✔
119
                if cm.startInformers {
1✔
120
                        go secretInformer.Run(ctx.Done())
×
121
                }
×
122

123
                configMapInformer := cm.informers.InformersFor(ns).Core().V1().ConfigMaps().Informer()
1✔
124
                if cm.startInformers {
1✔
125
                        go configMapInformer.Run(ctx.Done())
×
126
                }
×
127

128
                if cm.startInformers {
1✔
129
                        if !toolscache.WaitForCacheSync(ctx.Done(), secretInformer.HasSynced, configMapInformer.HasSynced) {
×
130
                                return fmt.Errorf("could not sync informer cache")
×
131
                        }
×
132
                }
133

134
                if cm.listerMap == nil {
2✔
135
                        cm.listerMap = make(map[string]*certListers)
1✔
136
                }
1✔
137

138
                cm.listerMap[ns] = &certListers{
1✔
139
                        secretLister:    cm.informers.InformersFor(ns).Core().V1().Secrets().Lister(),
1✔
140
                        configMapLister: cm.informers.InformersFor(ns).Core().V1().ConfigMaps().Lister(),
1✔
141
                }
1✔
142
        }
143

144
        return nil
1✔
145
}
146

147
func (cm *certManager) Sync(certs []cdicerts.CertificateDefinition) error {
1✔
148
        for _, cd := range certs {
2✔
149
                ca, err := cm.ensureSigner(cd)
1✔
150
                if err != nil {
1✔
151
                        return err
×
152
                }
×
153

154
                if cd.CertBundleConfigmap == nil {
1✔
155
                        continue
×
156
                }
157

158
                bundle, err := cm.ensureCertBundle(cd, ca)
1✔
159
                if err != nil {
1✔
160
                        return err
×
161
                }
×
162

163
                if cd.TargetSecret == nil {
2✔
164
                        continue
1✔
165
                }
166

167
                if err := cm.ensureTarget(cd, ca, bundle); err != nil {
1✔
168
                        return err
×
169
                }
×
170
        }
171

172
        return nil
1✔
173
}
174

175
func (cm *certManager) ensureCertConfig(secret *corev1.Secret, certConfig cdicerts.CertificateConfig) (*corev1.Secret, error) {
1✔
176
        scc := &serializedCertConfig{
1✔
177
                Lifetime: certConfig.Lifetime.String(),
1✔
178
                Refresh:  certConfig.Refresh.String(),
1✔
179
        }
1✔
180

1✔
181
        configBytes, err := json.Marshal(scc)
1✔
182
        if err != nil {
1✔
183
                return nil, err
×
184
        }
×
185

186
        configString := string(configBytes)
1✔
187
        currentConfig := secret.Annotations[annCertConfig]
1✔
188
        if currentConfig == configString {
2✔
189
                return secret, nil
1✔
190
        }
1✔
191

192
        secretCpy := secret.DeepCopy()
1✔
193

1✔
194
        if secretCpy.Annotations == nil {
2✔
195
                secretCpy.Annotations = make(map[string]string)
1✔
196
        }
1✔
197

198
        // force refresh
199
        if _, ok := secretCpy.Annotations[certrotation.CertificateNotAfterAnnotation]; ok {
2✔
200
                secretCpy.Annotations[certrotation.CertificateNotAfterAnnotation] = time.Now().UTC().Format(time.RFC3339)
1✔
201
        }
1✔
202
        secretCpy.Annotations[annCertConfig] = configString
1✔
203

1✔
204
        if secret, err = cm.k8sClient.CoreV1().Secrets(secretCpy.Namespace).Update(context.TODO(), secretCpy, metav1.UpdateOptions{}); err != nil {
1✔
205
                return nil, err
×
206
        }
×
207

208
        return secret, nil
1✔
209
}
210

211
func (cm *certManager) createSecret(namespace, name string) (*corev1.Secret, error) {
×
212
        secret := &corev1.Secret{
×
213
                ObjectMeta: metav1.ObjectMeta{
×
214
                        Name: name,
×
215
                },
×
216
                Type: corev1.SecretTypeTLS,
×
217
        }
×
218

×
219
        return cm.k8sClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
×
220
}
×
221

222
func (cm *certManager) ensureSigner(cd cdicerts.CertificateDefinition) (*crypto.CA, error) {
1✔
223
        listers, ok := cm.listerMap[cd.SignerSecret.Namespace]
1✔
224
        if !ok {
1✔
225
                return nil, fmt.Errorf("no lister for namespace %s", cd.SignerSecret.Namespace)
×
226
        }
×
227
        lister := listers.secretLister
1✔
228
        secret, err := lister.Secrets(cd.SignerSecret.Namespace).Get(cd.SignerSecret.Name)
1✔
229
        if err != nil {
1✔
230
                if !errors.IsNotFound(err) {
×
231
                        return nil, err
×
232
                }
×
233

234
                secret, err = cm.createSecret(cd.SignerSecret.Namespace, cd.SignerSecret.Name)
×
235
                if err != nil {
×
236
                        return nil, err
×
237
                }
×
238
        }
239

240
        if secret, err = cm.ensureCertConfig(secret, cd.SignerConfig); err != nil {
1✔
241
                return nil, err
×
242
        }
×
243

244
        sr := certrotation.RotatedSigningCASecret{
1✔
245
                Name:          secret.Name,
1✔
246
                Namespace:     secret.Namespace,
1✔
247
                Validity:      cd.SignerConfig.Lifetime,
1✔
248
                Refresh:       cd.SignerConfig.Refresh,
1✔
249
                Lister:        lister,
1✔
250
                Client:        cm.k8sClient.CoreV1(),
1✔
251
                EventRecorder: cm.eventRecorder,
1✔
252
        }
1✔
253

1✔
254
        ca, _, err := sr.EnsureSigningCertKeyPair(context.TODO())
1✔
255
        if err != nil {
1✔
256
                return nil, err
×
257
        }
×
258

259
        return ca, nil
1✔
260
}
261

262
func (cm *certManager) ensureCertBundle(cd cdicerts.CertificateDefinition, ca *crypto.CA) ([]*x509.Certificate, error) {
1✔
263
        configMap := cd.CertBundleConfigmap
1✔
264
        listers, ok := cm.listerMap[configMap.Namespace]
1✔
265
        if !ok {
1✔
266
                return nil, fmt.Errorf("no lister for namespace %s", configMap.Namespace)
×
267
        }
×
268
        lister := listers.configMapLister
1✔
269
        br := certrotation.CABundleConfigMap{
1✔
270
                Name:          configMap.Name,
1✔
271
                Namespace:     configMap.Namespace,
1✔
272
                Lister:        lister,
1✔
273
                Client:        cm.k8sClient.CoreV1(),
1✔
274
                EventRecorder: cm.eventRecorder,
1✔
275
        }
1✔
276

1✔
277
        certs, err := br.EnsureConfigMapCABundle(context.TODO(), ca, fmt.Sprintf("%s/%s", cd.SignerSecret.Namespace, cd.SignerSecret.Name))
1✔
278
        if err != nil {
1✔
279
                return nil, err
×
280
        }
×
281

282
        return certs, nil
1✔
283
}
284

285
func (cm *certManager) ensureTarget(cd cdicerts.CertificateDefinition, ca *crypto.CA, bundle []*x509.Certificate) error {
1✔
286
        listers, ok := cm.listerMap[cd.SignerSecret.Namespace]
1✔
287
        if !ok {
1✔
288
                return fmt.Errorf("no lister for namespace %s", cd.SignerSecret.Namespace)
×
289
        }
×
290
        lister := listers.secretLister
1✔
291
        secret, err := lister.Secrets(cd.TargetSecret.Namespace).Get(cd.TargetSecret.Name)
1✔
292
        if err != nil {
1✔
293
                if !errors.IsNotFound(err) {
×
294
                        return err
×
295
                }
×
296

297
                secret, err = cm.createSecret(cd.TargetSecret.Namespace, cd.TargetSecret.Name)
×
298
                if err != nil {
×
299
                        return err
×
300
                }
×
301
        }
302

303
        if secret, err = cm.ensureCertConfig(secret, cd.TargetConfig); err != nil {
1✔
304
                return err
×
305
        }
×
306

307
        var targetCreator certrotation.TargetCertCreator
1✔
308
        if cd.TargetService != nil {
2✔
309
                targetCreator = &certrotation.ServingRotation{
1✔
310
                        Hostnames: func() []string {
2✔
311
                                return []string{
1✔
312
                                        *cd.TargetService,
1✔
313
                                        fmt.Sprintf("%s.%s", *cd.TargetService, secret.Namespace),
1✔
314
                                        fmt.Sprintf("%s.%s.svc", *cd.TargetService, secret.Namespace),
1✔
315
                                }
1✔
316
                        },
1✔
317
                }
318
        } else {
1✔
319
                targetCreator = &certrotation.ClientRotation{
1✔
320
                        UserInfo: &user.DefaultInfo{Name: *cd.TargetUser},
1✔
321
                }
1✔
322
        }
1✔
323

324
        tr := certrotation.RotatedSelfSignedCertKeySecret{
1✔
325
                Name:          secret.Name,
1✔
326
                Namespace:     secret.Namespace,
1✔
327
                Validity:      cd.TargetConfig.Lifetime,
1✔
328
                Refresh:       cd.TargetConfig.Refresh,
1✔
329
                CertCreator:   targetCreator,
1✔
330
                Lister:        lister,
1✔
331
                Client:        cm.k8sClient.CoreV1(),
1✔
332
                EventRecorder: cm.eventRecorder,
1✔
333
        }
1✔
334

1✔
335
        if _, err := tr.EnsureTargetCertKeyPair(context.TODO(), ca, bundle); err != nil {
1✔
336
                return err
×
337
        }
×
338

339
        return nil
1✔
340
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc