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

kubevirt / containerized-data-importer / #5194

18 Mar 2025 07:39PM UTC coverage: 59.371% (-0.08%) from 59.452%
#5194

push

travis-ci

web-flow
fix certrotation unit tests (#3673)

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>

16802 of 28300 relevant lines covered (59.37%)

0.66 hits per line

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

59.42
/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
}
67

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

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

80
        cm := newCertManager(k8sClient, installNamespace, clock.RealClock{}, additionalNamespaces...)
×
81

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

87
        return cm, nil
×
88
}
89

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

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

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

1✔
101
        return &certManager{
1✔
102
                namespaces:    namespaces,
1✔
103
                k8sClient:     client,
1✔
104
                informers:     informers,
1✔
105
                eventRecorder: eventRecorder,
1✔
106
        }
1✔
107
}
108

109
func (cm *certManager) Start(ctx context.Context) error {
×
110
        cm.informers.Start(ctx.Done())
×
111

×
112
        for _, ns := range cm.namespaces {
×
113
                secretInformer := cm.informers.InformersFor(ns).Core().V1().Secrets().Informer()
×
114
                go secretInformer.Run(ctx.Done())
×
115

×
116
                configMapInformer := cm.informers.InformersFor(ns).Core().V1().ConfigMaps().Informer()
×
117
                go configMapInformer.Run(ctx.Done())
×
118

×
119
                if !toolscache.WaitForCacheSync(ctx.Done(), secretInformer.HasSynced, configMapInformer.HasSynced) {
×
120
                        return fmt.Errorf("could not sync informer cache")
×
121
                }
×
122

123
                if cm.listerMap == nil {
×
124
                        cm.listerMap = make(map[string]*certListers)
×
125
                }
×
126

127
                cm.listerMap[ns] = &certListers{
×
128
                        secretLister:    cm.informers.InformersFor(ns).Core().V1().Secrets().Lister(),
×
129
                        configMapLister: cm.informers.InformersFor(ns).Core().V1().ConfigMaps().Lister(),
×
130
                }
×
131
        }
132

133
        return nil
×
134
}
135

136
func (cm *certManager) Sync(certs []cdicerts.CertificateDefinition) error {
1✔
137
        for _, cd := range certs {
2✔
138
                ca, err := cm.ensureSigner(cd)
1✔
139
                if err != nil {
1✔
140
                        return err
×
141
                }
×
142

143
                if cd.CertBundleConfigmap == nil {
1✔
144
                        continue
×
145
                }
146

147
                bundle, err := cm.ensureCertBundle(cd, ca)
1✔
148
                if err != nil {
1✔
149
                        return err
×
150
                }
×
151

152
                if cd.TargetSecret == nil {
2✔
153
                        continue
1✔
154
                }
155

156
                if err := cm.ensureTarget(cd, ca, bundle); err != nil {
1✔
157
                        return err
×
158
                }
×
159
        }
160

161
        return nil
1✔
162
}
163

164
func (cm *certManager) ensureCertConfig(secret *corev1.Secret, certConfig cdicerts.CertificateConfig) (*corev1.Secret, error) {
1✔
165
        scc := &serializedCertConfig{
1✔
166
                Lifetime: certConfig.Lifetime.String(),
1✔
167
                Refresh:  certConfig.Refresh.String(),
1✔
168
        }
1✔
169

1✔
170
        configBytes, err := json.Marshal(scc)
1✔
171
        if err != nil {
1✔
172
                return nil, err
×
173
        }
×
174

175
        configString := string(configBytes)
1✔
176
        currentConfig := secret.Annotations[annCertConfig]
1✔
177
        if currentConfig == configString {
2✔
178
                return secret, nil
1✔
179
        }
1✔
180

181
        secretCpy := secret.DeepCopy()
1✔
182

1✔
183
        if secretCpy.Annotations == nil {
2✔
184
                secretCpy.Annotations = make(map[string]string)
1✔
185
        }
1✔
186

187
        // force refresh
188
        if _, ok := secretCpy.Annotations[certrotation.CertificateNotAfterAnnotation]; ok {
2✔
189
                secretCpy.Annotations[certrotation.CertificateNotAfterAnnotation] = time.Now().UTC().Format(time.RFC3339)
1✔
190
        }
1✔
191
        secretCpy.Annotations[annCertConfig] = configString
1✔
192

1✔
193
        if secret, err = cm.k8sClient.CoreV1().Secrets(secretCpy.Namespace).Update(context.TODO(), secretCpy, metav1.UpdateOptions{}); err != nil {
1✔
194
                return nil, err
×
195
        }
×
196

197
        return secret, nil
1✔
198
}
199

200
func (cm *certManager) createSecret(namespace, name string) (*corev1.Secret, error) {
×
201
        secret := &corev1.Secret{
×
202
                ObjectMeta: metav1.ObjectMeta{
×
203
                        Name: name,
×
204
                },
×
205
                Type: corev1.SecretTypeTLS,
×
206
        }
×
207

×
208
        return cm.k8sClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
×
209
}
×
210

211
func (cm *certManager) ensureSigner(cd cdicerts.CertificateDefinition) (*crypto.CA, error) {
1✔
212
        listers, ok := cm.listerMap[cd.SignerSecret.Namespace]
1✔
213
        if !ok {
1✔
214
                return nil, fmt.Errorf("no lister for namespace %s", cd.SignerSecret.Namespace)
×
215
        }
×
216
        lister := listers.secretLister
1✔
217
        secret, err := lister.Secrets(cd.SignerSecret.Namespace).Get(cd.SignerSecret.Name)
1✔
218
        if err != nil {
1✔
219
                if !errors.IsNotFound(err) {
×
220
                        return nil, err
×
221
                }
×
222

223
                secret, err = cm.createSecret(cd.SignerSecret.Namespace, cd.SignerSecret.Name)
×
224
                if err != nil {
×
225
                        return nil, err
×
226
                }
×
227
        }
228

229
        if secret, err = cm.ensureCertConfig(secret, cd.SignerConfig); err != nil {
1✔
230
                return nil, err
×
231
        }
×
232

233
        sr := certrotation.RotatedSigningCASecret{
1✔
234
                Name:          secret.Name,
1✔
235
                Namespace:     secret.Namespace,
1✔
236
                Validity:      cd.SignerConfig.Lifetime,
1✔
237
                Refresh:       cd.SignerConfig.Refresh,
1✔
238
                Lister:        lister,
1✔
239
                Client:        cm.k8sClient.CoreV1(),
1✔
240
                EventRecorder: cm.eventRecorder,
1✔
241
        }
1✔
242

1✔
243
        ca, _, err := sr.EnsureSigningCertKeyPair(context.TODO())
1✔
244
        if err != nil {
1✔
245
                return nil, err
×
246
        }
×
247

248
        return ca, nil
1✔
249
}
250

251
func (cm *certManager) ensureCertBundle(cd cdicerts.CertificateDefinition, ca *crypto.CA) ([]*x509.Certificate, error) {
1✔
252
        configMap := cd.CertBundleConfigmap
1✔
253
        listers, ok := cm.listerMap[configMap.Namespace]
1✔
254
        if !ok {
1✔
255
                return nil, fmt.Errorf("no lister for namespace %s", configMap.Namespace)
×
256
        }
×
257
        lister := listers.configMapLister
1✔
258
        br := certrotation.CABundleConfigMap{
1✔
259
                Name:          configMap.Name,
1✔
260
                Namespace:     configMap.Namespace,
1✔
261
                Lister:        lister,
1✔
262
                Client:        cm.k8sClient.CoreV1(),
1✔
263
                EventRecorder: cm.eventRecorder,
1✔
264
        }
1✔
265

1✔
266
        certs, err := br.EnsureConfigMapCABundle(context.TODO(), ca, fmt.Sprintf("%s/%s", cd.SignerSecret.Namespace, cd.SignerSecret.Name))
1✔
267
        if err != nil {
1✔
268
                return nil, err
×
269
        }
×
270

271
        return certs, nil
1✔
272
}
273

274
func (cm *certManager) ensureTarget(cd cdicerts.CertificateDefinition, ca *crypto.CA, bundle []*x509.Certificate) error {
1✔
275
        listers, ok := cm.listerMap[cd.SignerSecret.Namespace]
1✔
276
        if !ok {
1✔
277
                return fmt.Errorf("no lister for namespace %s", cd.SignerSecret.Namespace)
×
278
        }
×
279
        lister := listers.secretLister
1✔
280
        secret, err := lister.Secrets(cd.TargetSecret.Namespace).Get(cd.TargetSecret.Name)
1✔
281
        if err != nil {
1✔
282
                if !errors.IsNotFound(err) {
×
283
                        return err
×
284
                }
×
285

286
                secret, err = cm.createSecret(cd.TargetSecret.Namespace, cd.TargetSecret.Name)
×
287
                if err != nil {
×
288
                        return err
×
289
                }
×
290
        }
291

292
        if secret, err = cm.ensureCertConfig(secret, cd.TargetConfig); err != nil {
1✔
293
                return err
×
294
        }
×
295

296
        var targetCreator certrotation.TargetCertCreator
1✔
297
        if cd.TargetService != nil {
2✔
298
                targetCreator = &certrotation.ServingRotation{
1✔
299
                        Hostnames: func() []string {
2✔
300
                                return []string{
1✔
301
                                        *cd.TargetService,
1✔
302
                                        fmt.Sprintf("%s.%s", *cd.TargetService, secret.Namespace),
1✔
303
                                        fmt.Sprintf("%s.%s.svc", *cd.TargetService, secret.Namespace),
1✔
304
                                }
1✔
305
                        },
1✔
306
                }
307
        } else {
1✔
308
                targetCreator = &certrotation.ClientRotation{
1✔
309
                        UserInfo: &user.DefaultInfo{Name: *cd.TargetUser},
1✔
310
                }
1✔
311
        }
1✔
312

313
        tr := certrotation.RotatedSelfSignedCertKeySecret{
1✔
314
                Name:          secret.Name,
1✔
315
                Namespace:     secret.Namespace,
1✔
316
                Validity:      cd.TargetConfig.Lifetime,
1✔
317
                Refresh:       cd.TargetConfig.Refresh,
1✔
318
                CertCreator:   targetCreator,
1✔
319
                Lister:        lister,
1✔
320
                Client:        cm.k8sClient.CoreV1(),
1✔
321
                EventRecorder: cm.eventRecorder,
1✔
322
        }
1✔
323

1✔
324
        if _, err := tr.EnsureTargetCertKeyPair(context.TODO(), ca, bundle); err != nil {
1✔
325
                return err
×
326
        }
×
327

328
        return nil
1✔
329
}
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