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

zalando-incubator / stackset-controller / 6721439504

01 Nov 2023 03:09PM UTC coverage: 73.556%. Remained the same
6721439504

push

github

katyanna
Support dedicated ConfigMap created by other means

To give the user the possibility of creating the configMap to be attached to the
Stack by themselves, Stackset Controller checks if there's a reference in the
ConfigurationResources before creating the versioned ConfigMap.
If there isn't, the existing resource named on ConfigurationResources.Name is
updated with the Stack info on ownerReferences.

17 of 17 new or added lines in 1 file covered. (100.0%)

2356 of 3203 relevant lines covered (73.56%)

0.83 hits per line

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

83.84
/controller/stack_resources.go
1
package controller
2

3
import (
4
        "context"
5

6
        rgv1 "github.com/szuecs/routegroup-client/apis/zalando.org/v1"
7
        zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
8
        "github.com/zalando-incubator/stackset-controller/pkg/core"
9
        apps "k8s.io/api/apps/v1"
10
        v2 "k8s.io/api/autoscaling/v2"
11
        apiv1 "k8s.io/api/core/v1"
12
        networking "k8s.io/api/networking/v1"
13
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
)
15

16
func pint32Equal(p1, p2 *int32) bool {
1✔
17
        if p1 == nil && p2 == nil {
1✔
18
                return true
×
19
        }
×
20
        if p1 != nil && p2 != nil {
2✔
21
                return *p1 == *p2
1✔
22
        }
1✔
23
        return false
×
24
}
25

26
// There are HPA metrics that depend on annotations to work properly,
27
// e.g. External RPS metric, this verification provides a way to verify
28
// all relevant annotations are actually up to date.
29
func areHPAAnnotationsUpToDate(updated, existing *v2.HorizontalPodAutoscaler) bool {
1✔
30
        if len(updated.Annotations) != len(existing.Annotations) {
1✔
31
                return false
×
32
        }
×
33

34
        for k, v := range updated.Annotations {
2✔
35
                if k == "stackset-controller.zalando.org/stack-generation" {
2✔
36
                        continue
1✔
37
                }
38

39
                existingValue, ok := existing.Annotations[k]
1✔
40
                if ok && existingValue == v {
2✔
41
                        continue
1✔
42
                }
43

44
                return false
1✔
45
        }
46

47
        return true
1✔
48
}
49

50
// syncObjectMeta copies metadata elements such as labels or annotations from source to target
51
func syncObjectMeta(target, source metav1.Object) {
1✔
52
        target.SetLabels(source.GetLabels())
1✔
53
        target.SetAnnotations(source.GetAnnotations())
1✔
54
}
1✔
55

56
func generateConfigMapName(stack *zv1.Stack, templateName string) string {
1✔
57
        return stack.Name + "-" + templateName
1✔
58
}
1✔
59

60
func (c *StackSetController) ReconcileStackDeployment(ctx context.Context, stack *zv1.Stack, existing *apps.Deployment, generateUpdated func() *apps.Deployment) error {
1✔
61
        deployment := generateUpdated()
1✔
62

1✔
63
        // Create new deployment
1✔
64
        if existing == nil {
2✔
65
                _, err := c.client.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
1✔
66
                if err != nil {
1✔
67
                        return err
×
68
                }
×
69
                c.recorder.Eventf(
1✔
70
                        stack,
1✔
71
                        apiv1.EventTypeNormal,
1✔
72
                        "CreatedDeployment",
1✔
73
                        "Created Deployment %s",
1✔
74
                        deployment.Name)
1✔
75
                return nil
1✔
76
        }
77

78
        // Check if we need to update the deployment
79
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) && pint32Equal(existing.Spec.Replicas, deployment.Spec.Replicas) {
2✔
80
                return nil
1✔
81
        }
1✔
82

83
        updated := existing.DeepCopy()
1✔
84
        syncObjectMeta(updated, deployment)
1✔
85
        updated.Spec = deployment.Spec
1✔
86
        updated.Spec.Selector = existing.Spec.Selector
1✔
87

1✔
88
        _, err := c.client.AppsV1().Deployments(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
89
        if err != nil {
1✔
90
                return err
×
91
        }
×
92
        c.recorder.Eventf(
1✔
93
                stack,
1✔
94
                apiv1.EventTypeNormal,
1✔
95
                "UpdatedDeployment",
1✔
96
                "Updated Deployment %s",
1✔
97
                deployment.Name)
1✔
98
        return nil
1✔
99
}
100

101
func (c *StackSetController) ReconcileStackHPA(ctx context.Context, stack *zv1.Stack, existing *v2.HorizontalPodAutoscaler, generateUpdated func() (*v2.HorizontalPodAutoscaler, error)) error {
1✔
102
        hpa, err := generateUpdated()
1✔
103
        if err != nil {
1✔
104
                return err
×
105
        }
×
106

107
        // HPA removed
108
        if hpa == nil {
2✔
109
                if existing != nil {
2✔
110
                        err := c.client.AutoscalingV2().HorizontalPodAutoscalers(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
111
                        if err != nil {
1✔
112
                                return err
×
113
                        }
×
114
                        c.recorder.Eventf(
1✔
115
                                stack,
1✔
116
                                apiv1.EventTypeNormal,
1✔
117
                                "DeletedHPA",
1✔
118
                                "Deleted HPA %s",
1✔
119
                                existing.Namespace)
1✔
120
                }
121
                return nil
1✔
122
        }
123

124
        // Create new HPA
125
        if existing == nil {
2✔
126
                _, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Create(ctx, hpa, metav1.CreateOptions{})
1✔
127
                if err != nil {
1✔
128
                        return err
×
129
                }
×
130
                c.recorder.Eventf(
1✔
131
                        stack,
1✔
132
                        apiv1.EventTypeNormal,
1✔
133
                        "CreatedHPA",
1✔
134
                        "Created HPA %s",
1✔
135
                        hpa.Name)
1✔
136
                return nil
1✔
137
        }
138

139
        // Check if we need to update the HPA
140
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) &&
1✔
141
                pint32Equal(existing.Spec.MinReplicas, hpa.Spec.MinReplicas) &&
1✔
142
                areHPAAnnotationsUpToDate(hpa, existing) {
2✔
143
                return nil
1✔
144
        }
1✔
145

146
        updated := existing.DeepCopy()
1✔
147
        syncObjectMeta(updated, hpa)
1✔
148
        updated.Spec = hpa.Spec
1✔
149

1✔
150
        _, err = c.client.AutoscalingV2().HorizontalPodAutoscalers(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
151
        if err != nil {
1✔
152
                return err
×
153
        }
×
154
        c.recorder.Eventf(
1✔
155
                stack,
1✔
156
                apiv1.EventTypeNormal,
1✔
157
                "UpdatedHPA",
1✔
158
                "Updated HPA %s",
1✔
159
                hpa.Name)
1✔
160
        return nil
1✔
161
}
162

163
func (c *StackSetController) ReconcileStackService(ctx context.Context, stack *zv1.Stack, existing *apiv1.Service, generateUpdated func() (*apiv1.Service, error)) error {
1✔
164
        service, err := generateUpdated()
1✔
165
        if err != nil {
1✔
166
                return err
×
167
        }
×
168

169
        // Create new service
170
        if existing == nil {
2✔
171
                _, err := c.client.CoreV1().Services(service.Namespace).Create(ctx, service, metav1.CreateOptions{})
1✔
172
                if err != nil {
1✔
173
                        return err
×
174
                }
×
175
                c.recorder.Eventf(
1✔
176
                        stack,
1✔
177
                        apiv1.EventTypeNormal,
1✔
178
                        "CreatedService",
1✔
179
                        "Created Service %s",
1✔
180
                        service.Name)
1✔
181
                return nil
1✔
182
        }
183

184
        // Check if we need to update the service
185
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
186
                return nil
1✔
187
        }
1✔
188

189
        updated := existing.DeepCopy()
1✔
190
        syncObjectMeta(updated, service)
1✔
191
        updated.Spec = service.Spec
1✔
192
        updated.Spec.ClusterIP = existing.Spec.ClusterIP // ClusterIP is immutable
1✔
193

1✔
194
        _, err = c.client.CoreV1().Services(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
195
        if err != nil {
1✔
196
                return err
×
197
        }
×
198
        c.recorder.Eventf(
1✔
199
                stack,
1✔
200
                apiv1.EventTypeNormal,
1✔
201
                "UpdatedService",
1✔
202
                "Updated Service %s",
1✔
203
                service.Name)
1✔
204
        return nil
1✔
205
}
206

207
func (c *StackSetController) ReconcileStackIngress(ctx context.Context, stack *zv1.Stack, existing *networking.Ingress, generateUpdated func() (*networking.Ingress, error)) error {
1✔
208
        ingress, err := generateUpdated()
1✔
209
        if err != nil {
1✔
210
                return err
×
211
        }
×
212

213
        // Ingress removed
214
        if ingress == nil {
2✔
215
                if existing != nil {
2✔
216
                        err := c.client.NetworkingV1().Ingresses(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
217
                        if err != nil {
1✔
218
                                return err
×
219
                        }
×
220
                        c.recorder.Eventf(
1✔
221
                                stack,
1✔
222
                                apiv1.EventTypeNormal,
1✔
223
                                "DeletedIngress",
1✔
224
                                "Deleted Ingress %s",
1✔
225
                                existing.Namespace)
1✔
226
                }
227
                return nil
1✔
228
        }
229

230
        // Create new Ingress
231
        if existing == nil {
2✔
232
                _, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
1✔
233
                if err != nil {
1✔
234
                        return err
×
235
                }
×
236
                c.recorder.Eventf(
1✔
237
                        stack,
1✔
238
                        apiv1.EventTypeNormal,
1✔
239
                        "CreatedIngress",
1✔
240
                        "Created Ingress %s",
1✔
241
                        ingress.Name)
1✔
242
                return nil
1✔
243
        }
244

245
        // Check if we need to update the Ingress
246
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
247
                return nil
1✔
248
        }
1✔
249

250
        updated := existing.DeepCopy()
1✔
251
        syncObjectMeta(updated, ingress)
1✔
252
        updated.Spec = ingress.Spec
1✔
253

1✔
254
        _, err = c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
255
        if err != nil {
1✔
256
                return err
×
257
        }
×
258
        c.recorder.Eventf(
1✔
259
                stack,
1✔
260
                apiv1.EventTypeNormal,
1✔
261
                "UpdatedIngress",
1✔
262
                "Updated Ingress %s",
1✔
263
                ingress.Name)
1✔
264
        return nil
1✔
265
}
266

267
func (c *StackSetController) ReconcileStackRouteGroup(ctx context.Context, stack *zv1.Stack, existing *rgv1.RouteGroup, generateUpdated func() (*rgv1.RouteGroup, error)) error {
1✔
268
        routegroup, err := generateUpdated()
1✔
269
        if err != nil {
1✔
270
                return err
×
271
        }
×
272

273
        // RouteGroup removed
274
        if routegroup == nil {
2✔
275
                if existing != nil {
2✔
276
                        err := c.client.RouteGroupV1().RouteGroups(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
277
                        if err != nil {
1✔
278
                                return err
×
279
                        }
×
280
                        c.recorder.Eventf(
1✔
281
                                stack,
1✔
282
                                apiv1.EventTypeNormal,
1✔
283
                                "DeletedRouteGroup",
1✔
284
                                "Deleted RouteGroup %s",
1✔
285
                                existing.Namespace)
1✔
286
                }
287
                return nil
1✔
288
        }
289

290
        // Create new RouteGroup
291
        if existing == nil {
2✔
292
                _, err := c.client.RouteGroupV1().RouteGroups(routegroup.Namespace).Create(ctx, routegroup, metav1.CreateOptions{})
1✔
293
                if err != nil {
1✔
294
                        return err
×
295
                }
×
296
                c.recorder.Eventf(
1✔
297
                        stack,
1✔
298
                        apiv1.EventTypeNormal,
1✔
299
                        "CreatedRouteGroup",
1✔
300
                        "Created RouteGroup %s",
1✔
301
                        routegroup.Name)
1✔
302
                return nil
1✔
303
        }
304

305
        // Check if we need to update the RouteGroup
306
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
307
                return nil
1✔
308
        }
1✔
309

310
        updated := existing.DeepCopy()
1✔
311
        syncObjectMeta(updated, routegroup)
1✔
312
        updated.Spec = routegroup.Spec
1✔
313

1✔
314
        _, err = c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
315
        if err != nil {
1✔
316
                return err
×
317
        }
×
318
        c.recorder.Eventf(
1✔
319
                stack,
1✔
320
                apiv1.EventTypeNormal,
1✔
321
                "UpdatedRouteGroup",
1✔
322
                "Updated RouteGroup %s",
1✔
323
                routegroup.Name)
1✔
324
        return nil
1✔
325
}
326

327
// Update referencings of the ConfigMap on the PodTemplate
328
// The ConfigMap name is updated to the expected versioned name in the Stack.PodTemplate
329
// to ensure Pods rely on the Stack owned resource.
330
func (c *StackSetController) updateStackConfigMap(
331
        ctx context.Context,
332
        stack *zv1.Stack,
333
        configMapNames map[string]string,
334
) error {
1✔
335
        for templateName, versionedName := range configMapNames {
2✔
336
                // Change ConfigMap reference on Stack's Volumes
1✔
337
                for _, volume := range stack.Spec.PodTemplate.Spec.Volumes {
2✔
338
                        if volume.ConfigMap != nil && volume.ConfigMap.Name == templateName {
2✔
339
                                volume.ConfigMap.Name = versionedName
1✔
340
                        }
1✔
341
                }
342

343
                // Change ConfigMap reference on Stack's EnvFrom
344
                for _, container := range stack.Spec.PodTemplate.Spec.Containers {
2✔
345
                        for _, envFrom := range container.EnvFrom {
2✔
346
                                if envFrom.ConfigMapRef != nil && envFrom.ConfigMapRef.Name == templateName {
2✔
347
                                        envFrom.ConfigMapRef.Name = versionedName
1✔
348
                                }
1✔
349
                        }
350
                }
351

352
                // Change ConfigMap reference on Stack's EnvValue
353
                for _, container := range stack.Spec.PodTemplate.Spec.Containers {
2✔
354
                        for _, env := range container.Env {
2✔
355
                                if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil {
2✔
356
                                        if env.ValueFrom.ConfigMapKeyRef.Name == templateName {
2✔
357
                                                env.ValueFrom.ConfigMapKeyRef.Name = versionedName
1✔
358
                                        }
1✔
359
                                }
360
                        }
361
                }
362
        }
363

364
        _, err := c.client.ZalandoV1().Stacks(stack.Namespace).Update(ctx, stack, metav1.UpdateOptions{})
1✔
365
        if err != nil {
1✔
366
                return err
×
367
        }
×
368
        c.recorder.Eventf(
1✔
369
                stack,
1✔
370
                apiv1.EventTypeNormal,
1✔
371
                "UpdatedStack",
1✔
372
                "Updated Stack %s",
1✔
373
                stack.Name,
1✔
374
        )
1✔
375

1✔
376
        return nil
1✔
377
}
378

379
// Given the definition of ConfigurationResources on the StackTemplate, the Stack has
380
// its mentions of ConfigMap names updated for the expected versioned ConfigMap names,
381
// ensuring it always points to the expected resources, even if they don't exist yet.
382
//
383
// Then the Stackset Controller will search the referenced ConfigMap templates and
384
// create their versions owned by the Stack, deleting the template used after each
385
// version creation.
386
//
387
// If configMapRef is not defined, the Stackset Controller expects the
388
// ConfigurationResources.Name to be the somehow else created ConfigMap to be attached
389
// to the Stack and just updates it with the ownerReferences.
390
//
391
// If the Stack already has the same amount of versioned ConfigMaps as defined in the
392
// StackTemplate, the Reconcile method is exited before the resource update/creation
393
// loop.
394
//
395
// Update of versioned ConfigMaps is not encouraged, but is allowed considering
396
// emergency needs.
397
func (c *StackSetController) ReconcileStackConfigMap(
398
        ctx context.Context,
399
        stack *zv1.Stack,
400
        existing []*apiv1.ConfigMap,
401
        generateUpdated func(*apiv1.ConfigMap, string) (*apiv1.ConfigMap, error),
402
) error {
1✔
403
        if stack.Spec.ConfigurationResources == nil {
1✔
404
                return nil
×
405
        }
×
406

407
        configMaps := make(map[string]string)
1✔
408
        for _, configMap := range stack.Spec.ConfigurationResources {
2✔
409
                if configMap.ConfigMapRef == nil {
2✔
410
                        configMaps[configMap.Name] = configMap.Name
1✔
411
                        continue
1✔
412
                }
413
                templateName := configMap.ConfigMapRef.Name
1✔
414
                configMaps[templateName] = generateConfigMapName(stack, templateName)
1✔
415
        }
416

417
        if len(existing) >= len(configMaps) {
2✔
418
                return nil
1✔
419
        }
1✔
420

421
        err := c.updateStackConfigMap(ctx, stack, configMaps)
1✔
422
        if err != nil {
1✔
423
                return err
×
424
        }
×
425

426
        for templateName, versionedName := range configMaps {
2✔
427
                template, err := c.client.CoreV1().ConfigMaps(stack.Namespace).Get(ctx, templateName, metav1.GetOptions{})
1✔
428
                if err != nil {
1✔
429
                        c.logger.Error(err)
×
430
                        continue
×
431
                }
432

433
                configMap, err := generateUpdated(template, versionedName)
1✔
434
                if err != nil {
1✔
435
                        return err
×
436
                }
×
437

438
                existingConfigMap, _ := c.client.CoreV1().ConfigMaps(stack.Namespace).Get(ctx, configMap.Name, metav1.GetOptions{})
1✔
439
                if existingConfigMap != nil {
2✔
440
                        _, err = c.client.CoreV1().ConfigMaps(configMap.Namespace).Update(ctx, configMap, metav1.UpdateOptions{})
1✔
441
                        if err != nil {
1✔
442
                                return err
×
443
                        }
×
444
                        c.recorder.Eventf(
1✔
445
                                stack,
1✔
446
                                apiv1.EventTypeNormal,
1✔
447
                                "UpdatedConfigMap",
1✔
448
                                "Updated ConfigMap %s",
1✔
449
                                configMap.Name,
1✔
450
                        )
1✔
451
                        continue
1✔
452
                }
453
                _, err = c.client.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, configMap, metav1.CreateOptions{})
1✔
454
                if err != nil {
1✔
455
                        return err
×
456
                }
×
457
                c.recorder.Eventf(
1✔
458
                        stack,
1✔
459
                        apiv1.EventTypeNormal,
1✔
460
                        "CreatedConfigMap",
1✔
461
                        "Created ConfigMap %s",
1✔
462
                        configMap.Name,
1✔
463
                )
1✔
464
        }
465

466
        return nil
1✔
467
}
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

© 2025 Coveralls, Inc