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

zalando-incubator / stackset-controller / 7062472472

01 Dec 2023 03:53PM UTC coverage: 73.254% (+0.07%) from 73.186%
7062472472

push

github

web-flow
Reference ConfigMap for per stack versioning (#520)

Currently Stacks share the same configuration resources, which may lead to confusion and incidents. As a first step on having one configuration resource dedicated to each stack, this Pull Request implements ConfigMaps versioned by reference. Other resources will follow in future Pull Requests.

The expected behaviour is that given the a ConfigMap named in the <stack-name>-<configmap-name> pattern is referenced under ConfigurationResources on the Stack definition, Stackset Controller identifies the deployed ConfigMap and updates it with the Stack info on ownerReferences.

81 of 109 new or added lines in 4 files covered. (74.31%)

2 existing lines in 1 file now uncovered.

2328 of 3178 relevant lines covered (73.25%)

0.83 hits per line

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

82.09
/controller/stack_resources.go
1
package controller
2

3
import (
4
        "context"
5
        "fmt"
6
        "reflect"
7
        "sort"
8

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

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

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

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

42
                existingValue, ok := existing.Annotations[k]
1✔
43
                if ok && existingValue == v {
2✔
44
                        continue
1✔
45
                }
46

47
                return false
1✔
48
        }
49

50
        return true
1✔
51
}
52

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

59
// equalResourceList compares existing Resources with list of
60
// ConfigurationResources to be created for Stack
61
func equalResourceList(
62
        existing []*apiv1.ConfigMap,
63
        defined []zv1.ConfigurationResourcesSpec,
64
) bool {
1✔
65
        var existingName []string
1✔
66
        for _, e := range existing {
2✔
67
                existingName = append(existingName, e.Name)
1✔
68
        }
1✔
69
        var crName []string
1✔
70
        for _, cr := range defined {
2✔
71
                crName = append(crName, cr.ConfigMapRef.Name)
1✔
72
        }
1✔
73

74
        sort.Strings(existingName)
1✔
75
        sort.Strings(crName)
1✔
76

1✔
77
        return reflect.DeepEqual(existingName, crName)
1✔
78
}
79

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

1✔
83
        // Create new deployment
1✔
84
        if existing == nil {
2✔
85
                _, err := c.client.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
1✔
86
                if err != nil {
1✔
87
                        return err
×
88
                }
×
89
                c.recorder.Eventf(
1✔
90
                        stack,
1✔
91
                        apiv1.EventTypeNormal,
1✔
92
                        "CreatedDeployment",
1✔
93
                        "Created Deployment %s",
1✔
94
                        deployment.Name)
1✔
95
                return nil
1✔
96
        }
97

98
        // Check if we need to update the deployment
99
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) && pint32Equal(existing.Spec.Replicas, deployment.Spec.Replicas) {
2✔
100
                return nil
1✔
101
        }
1✔
102

103
        updated := existing.DeepCopy()
1✔
104
        syncObjectMeta(updated, deployment)
1✔
105
        updated.Spec = deployment.Spec
1✔
106
        updated.Spec.Selector = existing.Spec.Selector
1✔
107

1✔
108
        _, err := c.client.AppsV1().Deployments(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
109
        if err != nil {
1✔
110
                return err
×
111
        }
×
112
        c.recorder.Eventf(
1✔
113
                stack,
1✔
114
                apiv1.EventTypeNormal,
1✔
115
                "UpdatedDeployment",
1✔
116
                "Updated Deployment %s",
1✔
117
                deployment.Name)
1✔
118
        return nil
1✔
119
}
120

121
func (c *StackSetController) ReconcileStackHPA(ctx context.Context, stack *zv1.Stack, existing *v2.HorizontalPodAutoscaler, generateUpdated func() (*v2.HorizontalPodAutoscaler, error)) error {
1✔
122
        hpa, err := generateUpdated()
1✔
123
        if err != nil {
1✔
124
                return err
×
125
        }
×
126

127
        // HPA removed
128
        if hpa == nil {
2✔
129
                if existing != nil {
2✔
130
                        err := c.client.AutoscalingV2().HorizontalPodAutoscalers(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
131
                        if err != nil {
1✔
132
                                return err
×
133
                        }
×
134
                        c.recorder.Eventf(
1✔
135
                                stack,
1✔
136
                                apiv1.EventTypeNormal,
1✔
137
                                "DeletedHPA",
1✔
138
                                "Deleted HPA %s",
1✔
139
                                existing.Namespace)
1✔
140
                }
141
                return nil
1✔
142
        }
143

144
        // Create new HPA
145
        if existing == nil {
2✔
146
                _, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Create(ctx, hpa, metav1.CreateOptions{})
1✔
147
                if err != nil {
1✔
148
                        return err
×
149
                }
×
150
                c.recorder.Eventf(
1✔
151
                        stack,
1✔
152
                        apiv1.EventTypeNormal,
1✔
153
                        "CreatedHPA",
1✔
154
                        "Created HPA %s",
1✔
155
                        hpa.Name)
1✔
156
                return nil
1✔
157
        }
158

159
        // Check if we need to update the HPA
160
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) &&
1✔
161
                pint32Equal(existing.Spec.MinReplicas, hpa.Spec.MinReplicas) &&
1✔
162
                areHPAAnnotationsUpToDate(hpa, existing) {
2✔
163
                return nil
1✔
164
        }
1✔
165

166
        updated := existing.DeepCopy()
1✔
167
        syncObjectMeta(updated, hpa)
1✔
168
        updated.Spec = hpa.Spec
1✔
169

1✔
170
        _, err = c.client.AutoscalingV2().HorizontalPodAutoscalers(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
171
        if err != nil {
1✔
172
                return err
×
173
        }
×
174
        c.recorder.Eventf(
1✔
175
                stack,
1✔
176
                apiv1.EventTypeNormal,
1✔
177
                "UpdatedHPA",
1✔
178
                "Updated HPA %s",
1✔
179
                hpa.Name)
1✔
180
        return nil
1✔
181
}
182

183
func (c *StackSetController) ReconcileStackService(ctx context.Context, stack *zv1.Stack, existing *apiv1.Service, generateUpdated func() (*apiv1.Service, error)) error {
1✔
184
        service, err := generateUpdated()
1✔
185
        if err != nil {
1✔
186
                return err
×
187
        }
×
188

189
        // Create new service
190
        if existing == nil {
2✔
191
                _, err := c.client.CoreV1().Services(service.Namespace).Create(ctx, service, metav1.CreateOptions{})
1✔
192
                if err != nil {
1✔
193
                        return err
×
194
                }
×
195
                c.recorder.Eventf(
1✔
196
                        stack,
1✔
197
                        apiv1.EventTypeNormal,
1✔
198
                        "CreatedService",
1✔
199
                        "Created Service %s",
1✔
200
                        service.Name)
1✔
201
                return nil
1✔
202
        }
203

204
        // Check if we need to update the service
205
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
206
                return nil
1✔
207
        }
1✔
208

209
        updated := existing.DeepCopy()
1✔
210
        syncObjectMeta(updated, service)
1✔
211
        updated.Spec = service.Spec
1✔
212
        updated.Spec.ClusterIP = existing.Spec.ClusterIP // ClusterIP is immutable
1✔
213

1✔
214
        _, err = c.client.CoreV1().Services(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
215
        if err != nil {
1✔
216
                return err
×
217
        }
×
218
        c.recorder.Eventf(
1✔
219
                stack,
1✔
220
                apiv1.EventTypeNormal,
1✔
221
                "UpdatedService",
1✔
222
                "Updated Service %s",
1✔
223
                service.Name)
1✔
224
        return nil
1✔
225
}
226

227
func (c *StackSetController) ReconcileStackIngress(ctx context.Context, stack *zv1.Stack, existing *networking.Ingress, generateUpdated func() (*networking.Ingress, error)) error {
1✔
228
        ingress, err := generateUpdated()
1✔
229
        if err != nil {
1✔
230
                return err
×
231
        }
×
232

233
        // Ingress removed
234
        if ingress == nil {
2✔
235
                if existing != nil {
2✔
236
                        err := c.client.NetworkingV1().Ingresses(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
237
                        if err != nil {
1✔
238
                                return err
×
239
                        }
×
240
                        c.recorder.Eventf(
1✔
241
                                stack,
1✔
242
                                apiv1.EventTypeNormal,
1✔
243
                                "DeletedIngress",
1✔
244
                                "Deleted Ingress %s",
1✔
245
                                existing.Namespace)
1✔
246
                }
247
                return nil
1✔
248
        }
249

250
        // Create new Ingress
251
        if existing == nil {
2✔
252
                _, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
1✔
253
                if err != nil {
1✔
254
                        return err
×
255
                }
×
256
                c.recorder.Eventf(
1✔
257
                        stack,
1✔
258
                        apiv1.EventTypeNormal,
1✔
259
                        "CreatedIngress",
1✔
260
                        "Created Ingress %s",
1✔
261
                        ingress.Name)
1✔
262
                return nil
1✔
263
        }
264

265
        // Check if we need to update the Ingress
266
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
267
                return nil
1✔
268
        }
1✔
269

270
        updated := existing.DeepCopy()
1✔
271
        syncObjectMeta(updated, ingress)
1✔
272
        updated.Spec = ingress.Spec
1✔
273

1✔
274
        _, err = c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
275
        if err != nil {
1✔
276
                return err
×
277
        }
×
278
        c.recorder.Eventf(
1✔
279
                stack,
1✔
280
                apiv1.EventTypeNormal,
1✔
281
                "UpdatedIngress",
1✔
282
                "Updated Ingress %s",
1✔
283
                ingress.Name)
1✔
284
        return nil
1✔
285
}
286

287
func (c *StackSetController) ReconcileStackRouteGroup(ctx context.Context, stack *zv1.Stack, existing *rgv1.RouteGroup, generateUpdated func() (*rgv1.RouteGroup, error)) error {
1✔
288
        routegroup, err := generateUpdated()
1✔
289
        if err != nil {
1✔
290
                return err
×
291
        }
×
292

293
        // RouteGroup removed
294
        if routegroup == nil {
2✔
295
                if existing != nil {
2✔
296
                        err := c.client.RouteGroupV1().RouteGroups(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
297
                        if err != nil {
1✔
298
                                return err
×
299
                        }
×
300
                        c.recorder.Eventf(
1✔
301
                                stack,
1✔
302
                                apiv1.EventTypeNormal,
1✔
303
                                "DeletedRouteGroup",
1✔
304
                                "Deleted RouteGroup %s",
1✔
305
                                existing.Namespace)
1✔
306
                }
307
                return nil
1✔
308
        }
309

310
        // Create new RouteGroup
311
        if existing == nil {
2✔
312
                _, err := c.client.RouteGroupV1().RouteGroups(routegroup.Namespace).Create(ctx, routegroup, metav1.CreateOptions{})
1✔
313
                if err != nil {
1✔
314
                        return err
×
315
                }
×
316
                c.recorder.Eventf(
1✔
317
                        stack,
1✔
318
                        apiv1.EventTypeNormal,
1✔
319
                        "CreatedRouteGroup",
1✔
320
                        "Created RouteGroup %s",
1✔
321
                        routegroup.Name)
1✔
322
                return nil
1✔
323
        }
324

325
        // Check if we need to update the RouteGroup
326
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
327
                return nil
1✔
328
        }
1✔
329

330
        updated := existing.DeepCopy()
1✔
331
        syncObjectMeta(updated, routegroup)
1✔
332
        updated.Spec = routegroup.Spec
1✔
333

1✔
334
        _, err = c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
335
        if err != nil {
1✔
336
                return err
×
337
        }
×
338
        c.recorder.Eventf(
1✔
339
                stack,
1✔
340
                apiv1.EventTypeNormal,
1✔
341
                "UpdatedRouteGroup",
1✔
342
                "Updated RouteGroup %s",
1✔
343
                routegroup.Name)
1✔
344
        return nil
1✔
345
}
346

347
// ReconcileStackConfigMap will update the named user-provided ConfigMap to be
348
// attached to the Stack by ownerReferences, when a list of Configuration
349
// Resources are defined on the Stack template.
350
//
351
// The provided ConfigMap name must be prefixed by the Stack name.
352
// eg: Stack: myapp-v1 ConfigMap: myapp-v1-my-config
353
//
354
// User update of running versioned ConfigMaps is not encouraged but is allowed
355
// on consideration of emergency needs. Similarly, addition of ConfigMaps to
356
// running resources is also allowed, so the method checks for changes on the
357
// ConfigurationResources to ensure all listed ConfigMaps are properly linked
358
// to the Stack.
359
func (c *StackSetController) ReconcileStackConfigMap(
360
        ctx context.Context,
361
        stack *zv1.Stack,
362
        existing []*apiv1.ConfigMap,
363
        updateObjMeta func(*metav1.ObjectMeta) *metav1.ObjectMeta,
364
) error {
1✔
365
        if stack.Spec.ConfigurationResources == nil {
1✔
NEW
366
                return nil
×
NEW
367
        }
×
368

369
        if equalResourceList(existing, stack.Spec.ConfigurationResources) {
2✔
370
                return nil
1✔
371
        }
1✔
372

373
        for _, rsc := range stack.Spec.ConfigurationResources {
2✔
374
                rscName := rsc.ConfigMapRef.Name
1✔
375

1✔
376
                // ensure that ConfigurationResources are prefixed by Stack name.
1✔
377
                if err := validateConfigurationResourceNames(stack); err != nil {
1✔
NEW
378
                        return err
×
NEW
379
                }
×
380

381
                configMap, err := c.client.CoreV1().ConfigMaps(stack.Namespace).
1✔
382
                        Get(ctx, rscName, metav1.GetOptions{})
1✔
383
                if err != nil {
1✔
NEW
384
                        return err
×
NEW
385
                }
×
386

387
                if configMap.OwnerReferences != nil {
1✔
NEW
388
                        for _, owner := range configMap.OwnerReferences {
×
NEW
389
                                if owner.UID != stack.UID {
×
NEW
390
                                        return fmt.Errorf("ConfigMap already owned by other resource. "+
×
NEW
391
                                                "ConfigMap: %s, Stack: %s", rscName, stack.Name)
×
NEW
392
                                }
×
393
                        }
NEW
394
                        continue
×
395
                }
396

397
                objectMeta := updateObjMeta(&configMap.ObjectMeta)
1✔
398
                configMap.ObjectMeta = *objectMeta
1✔
399

1✔
400
                _, err = c.client.CoreV1().ConfigMaps(configMap.Namespace).
1✔
401
                        Update(ctx, configMap, metav1.UpdateOptions{})
1✔
402
                if err != nil {
1✔
NEW
403
                        return err
×
NEW
404
                }
×
405
                c.recorder.Eventf(
1✔
406
                        stack,
1✔
407
                        apiv1.EventTypeNormal,
1✔
408
                        "UpdatedConfigMap",
1✔
409
                        "Updated ConfigMap %s",
1✔
410
                        configMap.Name,
1✔
411
                )
1✔
412
        }
413
        return nil
1✔
414
}
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