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

zalando-incubator / stackset-controller / 8738009463

11 Apr 2024 03:07PM UTC coverage: 49.355%. First build
8738009463

push

github

linki
feature flag

3 of 8 new or added lines in 3 files covered. (37.5%)

2833 of 5740 relevant lines covered (49.36%)

0.55 hits per line

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

35.54
/controller/stackset.go
1
package controller
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/http"
7
        "runtime/debug"
8
        "strings"
9
        "sync"
10
        "time"
11

12
        "github.com/google/go-cmp/cmp"
13
        "github.com/google/go-cmp/cmp/cmpopts"
14
        "github.com/heptiolabs/healthcheck"
15
        "github.com/prometheus/client_golang/prometheus"
16
        log "github.com/sirupsen/logrus"
17
        rgv1 "github.com/szuecs/routegroup-client/apis/zalando.org/v1"
18
        zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
19
        "github.com/zalando-incubator/stackset-controller/pkg/clientset"
20
        "github.com/zalando-incubator/stackset-controller/pkg/core"
21
        "github.com/zalando-incubator/stackset-controller/pkg/recorder"
22
        "golang.org/x/sync/errgroup"
23
        v1 "k8s.io/api/core/v1"
24
        networking "k8s.io/api/networking/v1"
25
        "k8s.io/apimachinery/pkg/api/equality"
26
        "k8s.io/apimachinery/pkg/api/errors"
27
        "k8s.io/apimachinery/pkg/api/resource"
28
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
        "k8s.io/apimachinery/pkg/fields"
30
        "k8s.io/apimachinery/pkg/runtime"
31
        "k8s.io/apimachinery/pkg/types"
32
        "k8s.io/client-go/tools/cache"
33
        kube_record "k8s.io/client-go/tools/record"
34
)
35

36
const (
37
        PrescaleStacksAnnotationKey               = "alpha.stackset-controller.zalando.org/prescale-stacks"
38
        ResetHPAMinReplicasDelayAnnotationKey     = "alpha.stackset-controller.zalando.org/reset-hpa-min-replicas-delay"
39
        StacksetControllerControllerAnnotationKey = "stackset-controller.zalando.org/controller"
40
        ControllerLastUpdatedAnnotationKey        = "stackset-controller.zalando.org/updated-timestamp"
41

42
        reasonFailedManageStackSet = "FailedManageStackSet"
43

44
        defaultResetMinReplicasDelay = 10 * time.Minute
45
)
46

47
var configurationResourceNameError = "ConfigurationResource name must be prefixed by Stack name. ConfigurationResource: %s, Stack: %s"
48

49
// StackSetController is the main controller. It watches for changes to
50
// stackset resources and starts and maintains other controllers per
51
// stackset resource.
52
type StackSetController struct {
53
        logger                      *log.Entry
54
        client                      clientset.Interface
55
        namespace                   string
56
        syncIngressAnnotations      []string
57
        controllerID                string
58
        backendWeightsAnnotationKey string
59
        clusterDomains              []string
60
        interval                    time.Duration
61
        stacksetEvents              chan stacksetEvent
62
        stacksetStore               map[types.UID]zv1.StackSet
63
        recorder                    kube_record.EventRecorder
64
        metricsReporter             *core.MetricsReporter
65
        HealthReporter              healthcheck.Handler
66
        routeGroupSupportEnabled    bool
67
        now                         func() string
68
        reconcileWorkers            int
69
        inlineConfigMapEnabled      bool
70
        configMapSupportEnabled     bool
71
        secretSupportEnabled        bool
72
        sync.Mutex
73
}
74

75
type stacksetEvent struct {
76
        Deleted  bool
77
        StackSet *zv1.StackSet
78
}
79

80
// eventedError wraps an error that was already exposed as an event to the user
81
type eventedError struct {
82
        err error
83
}
84

85
func (ee *eventedError) Error() string {
×
86
        return ee.err.Error()
×
87
}
×
88

89
func now() string {
×
90
        return time.Now().Format(time.RFC3339)
×
91
}
×
92

93
// NewStackSetController initializes a new StackSetController.
94
func NewStackSetController(
95
        client clientset.Interface,
96
        namespace string,
97
        controllerID string,
98
        parallelWork int,
99
        backendWeightsAnnotationKey string,
100
        clusterDomains []string,
101
        registry prometheus.Registerer,
102
        interval time.Duration,
103
        routeGroupSupportEnabled bool,
104
        syncIngressAnnotations []string,
105
        inlineConfigMapEnabled bool,
106
        configMapSupportEnabled bool,
107
        secretSupportEnabled bool,
108
) (*StackSetController, error) {
1✔
109
        metricsReporter, err := core.NewMetricsReporter(registry)
1✔
110
        if err != nil {
1✔
111
                return nil, err
×
112
        }
×
113

114
        return &StackSetController{
1✔
115
                logger:                      log.WithFields(log.Fields{"controller": "stackset"}),
1✔
116
                client:                      client,
1✔
117
                namespace:                   namespace,
1✔
118
                controllerID:                controllerID,
1✔
119
                backendWeightsAnnotationKey: backendWeightsAnnotationKey,
1✔
120
                clusterDomains:              clusterDomains,
1✔
121
                interval:                    interval,
1✔
122
                stacksetEvents:              make(chan stacksetEvent, 1),
1✔
123
                stacksetStore:               make(map[types.UID]zv1.StackSet),
1✔
124
                recorder:                    recorder.CreateEventRecorder(client),
1✔
125
                metricsReporter:             metricsReporter,
1✔
126
                HealthReporter:              healthcheck.NewHandler(),
1✔
127
                routeGroupSupportEnabled:    routeGroupSupportEnabled,
1✔
128
                syncIngressAnnotations:      syncIngressAnnotations,
1✔
129
                inlineConfigMapEnabled:      inlineConfigMapEnabled,
1✔
130
                configMapSupportEnabled:     configMapSupportEnabled,
1✔
131
                secretSupportEnabled:        secretSupportEnabled,
1✔
132
                now:                         now,
1✔
133
                reconcileWorkers:            parallelWork,
1✔
134
        }, nil
1✔
135
}
136

137
func (c *StackSetController) stacksetLogger(ssc *core.StackSetContainer) *log.Entry {
×
138
        return c.logger.WithFields(map[string]interface{}{
×
139
                "namespace": ssc.StackSet.Namespace,
×
140
                "stackset":  ssc.StackSet.Name,
×
141
        })
×
142
}
×
143

144
func (c *StackSetController) stackLogger(ssc *core.StackSetContainer, sc *core.StackContainer) *log.Entry {
×
145
        return c.logger.WithFields(map[string]interface{}{
×
146
                "namespace": ssc.StackSet.Namespace,
×
147
                "stackset":  ssc.StackSet.Name,
×
148
                "stack":     sc.Name(),
×
149
        })
×
150
}
×
151

152
// Run runs the main loop of the StackSetController. Before the loops it
153
// sets up a watcher to watch StackSet resources. The watch will send
154
// changes over a channel which is polled from the main loop.
155
func (c *StackSetController) Run(ctx context.Context) error {
×
156
        var nextCheck time.Time
×
157

×
158
        // We're not alive if nextCheck is too far in the past
×
159
        c.HealthReporter.AddLivenessCheck("nextCheck", func() error {
×
160
                if time.Since(nextCheck) > 5*c.interval {
×
161
                        return fmt.Errorf("nextCheck too old")
×
162
                }
×
163
                return nil
×
164
        })
165

166
        err := c.startWatch(ctx)
×
167
        if err != nil {
×
168
                return err
×
169
        }
×
170

171
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
172

×
173
        nextCheck = time.Now().Add(-c.interval)
×
174

×
175
        for {
×
176
                select {
×
177
                case <-time.After(time.Until(nextCheck)):
×
178

×
179
                        nextCheck = time.Now().Add(c.interval)
×
180

×
181
                        stackSetContainers, err := c.collectResources(ctx)
×
182
                        if err != nil {
×
183
                                c.logger.Errorf("Failed to collect resources: %v", err)
×
184
                                continue
×
185
                        }
186

187
                        var reconcileGroup errgroup.Group
×
188
                        reconcileGroup.SetLimit(c.reconcileWorkers)
×
189
                        for stackset, container := range stackSetContainers {
×
190
                                container := container
×
191
                                stackset := stackset
×
192

×
193
                                reconcileGroup.Go(func() error {
×
194
                                        if _, ok := c.stacksetStore[stackset]; ok {
×
195
                                                err := c.ReconcileStackSet(ctx, container)
×
196
                                                if err != nil {
×
197
                                                        c.stacksetLogger(container).Errorf("unable to reconcile a stackset: %v", err)
×
198
                                                        return c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
199
                                                }
×
200
                                        }
201
                                        return nil
×
202
                                })
203
                        }
204

205
                        err = reconcileGroup.Wait()
×
206
                        if err != nil {
×
207
                                c.logger.Errorf("Failed waiting for reconcilers: %v", err)
×
208
                        }
×
209
                        err = c.metricsReporter.Report(stackSetContainers)
×
210
                        if err != nil {
×
211
                                c.logger.Errorf("Failed reporting metrics: %v", err)
×
212
                        }
×
213
                case e := <-c.stacksetEvents:
×
214
                        stackset := *e.StackSet
×
215
                        fixupStackSetTypeMeta(&stackset)
×
216

×
217
                        // update/delete existing entry
×
218
                        if _, ok := c.stacksetStore[stackset.UID]; ok {
×
219
                                if e.Deleted || !c.hasOwnership(&stackset) {
×
220
                                        delete(c.stacksetStore, stackset.UID)
×
221
                                        continue
×
222
                                }
223

224
                                // update stackset entry
225
                                c.stacksetStore[stackset.UID] = stackset
×
226
                                continue
×
227
                        }
228

229
                        // check if stackset should be managed by the controller
230
                        if !c.hasOwnership(&stackset) {
×
231
                                continue
×
232
                        }
233

234
                        c.logger.Infof("Adding entry for StackSet %s/%s", stackset.Namespace, stackset.Name)
×
235
                        c.stacksetStore[stackset.UID] = stackset
×
236
                case <-ctx.Done():
×
237
                        c.logger.Info("Terminating main controller loop.")
×
238
                        return nil
×
239
                }
240
        }
241
}
242

243
// collectResources collects resources for all stacksets at once and stores them per StackSet/Stack so that we don't
244
// overload the API requests with unnecessary requests
245
func (c *StackSetController) collectResources(ctx context.Context) (map[types.UID]*core.StackSetContainer, error) {
1✔
246
        stacksets := make(map[types.UID]*core.StackSetContainer, len(c.stacksetStore))
1✔
247
        for uid, stackset := range c.stacksetStore {
2✔
248
                stackset := stackset
1✔
249

1✔
250
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
251

1✔
252
                // use prescaling logic if enabled with an annotation
1✔
253
                if _, ok := stackset.Annotations[PrescaleStacksAnnotationKey]; ok {
2✔
254
                        resetDelay := defaultResetMinReplicasDelay
1✔
255
                        if resetDelayValue, ok := getResetMinReplicasDelay(stackset.Annotations); ok {
2✔
256
                                resetDelay = resetDelayValue
1✔
257
                        }
1✔
258
                        reconciler = &core.PrescalingTrafficReconciler{
1✔
259
                                ResetHPAMinReplicasTimeout: resetDelay,
1✔
260
                        }
1✔
261
                }
262

263
                stacksetContainer := core.NewContainer(
1✔
264
                        &stackset,
1✔
265
                        reconciler,
1✔
266
                        c.backendWeightsAnnotationKey,
1✔
267
                        c.clusterDomains,
1✔
268
                        c.syncIngressAnnotations,
1✔
269
                )
1✔
270
                stacksets[uid] = stacksetContainer
1✔
271
        }
272

273
        err := c.collectStacks(ctx, stacksets)
1✔
274
        if err != nil {
1✔
275
                return nil, err
×
276
        }
×
277

278
        err = c.collectIngresses(ctx, stacksets)
1✔
279
        if err != nil {
1✔
280
                return nil, err
×
281
        }
×
282

283
        if c.routeGroupSupportEnabled {
2✔
284
                err = c.collectRouteGroups(ctx, stacksets)
1✔
285
                if err != nil {
1✔
286
                        return nil, err
×
287
                }
×
288
        }
289

290
        err = c.collectDeployments(ctx, stacksets)
1✔
291
        if err != nil {
1✔
292
                return nil, err
×
293
        }
×
294

295
        err = c.collectServices(ctx, stacksets)
1✔
296
        if err != nil {
1✔
297
                return nil, err
×
298
        }
×
299

300
        err = c.collectHPAs(ctx, stacksets)
1✔
301
        if err != nil {
1✔
302
                return nil, err
×
303
        }
×
304

305
        if c.inlineConfigMapEnabled || c.configMapSupportEnabled {
2✔
306
                err = c.collectConfigMaps(ctx, stacksets)
1✔
307
                if err != nil {
1✔
308
                        return nil, err
×
309
                }
×
310
        }
311

312
        if c.secretSupportEnabled {
2✔
313
                err = c.collectSecrets(ctx, stacksets)
1✔
314
                if err != nil {
1✔
315
                        return nil, err
×
316
                }
×
317
        }
318

319
        return stacksets, nil
1✔
320
}
321

322
func (c *StackSetController) collectIngresses(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
323
        ingresses, err := c.client.NetworkingV1().Ingresses(c.namespace).List(ctx, metav1.ListOptions{})
1✔
324

1✔
325
        if err != nil {
1✔
326
                return fmt.Errorf("failed to list Ingresses: %v", err)
×
327
        }
×
328

329
        for _, i := range ingresses.Items {
2✔
330
                ingress := i
1✔
331
                if uid, ok := getOwnerUID(ingress.ObjectMeta); ok {
2✔
332
                        // stackset ingress
1✔
333
                        if s, ok := stacksets[uid]; ok {
2✔
334
                                s.Ingress = &ingress
1✔
335
                                continue
1✔
336
                        }
337

338
                        // stack ingress
339
                        for _, stackset := range stacksets {
2✔
340
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
341
                                        if strings.HasSuffix(
1✔
342
                                                ingress.ObjectMeta.Name,
1✔
343
                                                core.SegmentSuffix,
1✔
344
                                        ) {
2✔
345
                                                // Traffic Segment
1✔
346
                                                s.Resources.IngressSegment = &ingress
1✔
347
                                        } else {
2✔
348
                                                s.Resources.Ingress = &ingress
1✔
349
                                        }
1✔
350
                                        break
1✔
351
                                }
352
                        }
353
                }
354
        }
355
        return nil
1✔
356
}
357

358
func (c *StackSetController) collectRouteGroups(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
359
        rgs, err := c.client.RouteGroupV1().RouteGroups(c.namespace).List(
1✔
360
                ctx,
1✔
361
                metav1.ListOptions{},
1✔
362
        )
1✔
363
        if err != nil {
1✔
364
                return fmt.Errorf("failed to list RouteGroups: %v", err)
×
365
        }
×
366

367
        for _, rg := range rgs.Items {
2✔
368
                routegroup := rg
1✔
369
                if uid, ok := getOwnerUID(routegroup.ObjectMeta); ok {
2✔
370
                        // stackset routegroups
1✔
371
                        if s, ok := stacksets[uid]; ok {
2✔
372
                                s.RouteGroup = &routegroup
1✔
373
                                continue
1✔
374
                        }
375

376
                        // stack routegroups
377
                        for _, stackset := range stacksets {
2✔
378
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
379
                                        if strings.HasSuffix(
1✔
380
                                                routegroup.ObjectMeta.Name,
1✔
381
                                                core.SegmentSuffix,
1✔
382
                                        ) {
2✔
383
                                                // Traffic Segment
1✔
384
                                                s.Resources.RouteGroupSegment = &routegroup
1✔
385
                                        } else {
2✔
386
                                                s.Resources.RouteGroup = &routegroup
1✔
387
                                        }
1✔
388
                                        break
1✔
389
                                }
390
                        }
391
                }
392
        }
393
        return nil
1✔
394
}
395

396
func (c *StackSetController) collectStacks(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
397
        stacks, err := c.client.ZalandoV1().Stacks(c.namespace).List(ctx, metav1.ListOptions{})
1✔
398
        if err != nil {
1✔
399
                return fmt.Errorf("failed to list Stacks: %v", err)
×
400
        }
×
401

402
        for _, stack := range stacks.Items {
2✔
403
                if uid, ok := getOwnerUID(stack.ObjectMeta); ok {
2✔
404
                        if s, ok := stacksets[uid]; ok {
2✔
405
                                stack := stack
1✔
406
                                fixupStackTypeMeta(&stack)
1✔
407

1✔
408
                                s.StackContainers[stack.UID] = &core.StackContainer{
1✔
409
                                        Stack: &stack,
1✔
410
                                }
1✔
411
                                continue
1✔
412
                        }
413
                }
414
        }
415
        return nil
1✔
416
}
417

418
func (c *StackSetController) collectDeployments(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
419
        deployments, err := c.client.AppsV1().Deployments(c.namespace).List(ctx, metav1.ListOptions{})
1✔
420
        if err != nil {
1✔
421
                return fmt.Errorf("failed to list Deployments: %v", err)
×
422
        }
×
423

424
        for _, d := range deployments.Items {
2✔
425
                deployment := d
1✔
426
                if uid, ok := getOwnerUID(deployment.ObjectMeta); ok {
2✔
427
                        for _, stackset := range stacksets {
2✔
428
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
429
                                        s.Resources.Deployment = &deployment
1✔
430
                                        break
1✔
431
                                }
432
                        }
433
                }
434
        }
435
        return nil
1✔
436
}
437

438
func (c *StackSetController) collectServices(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
439
        services, err := c.client.CoreV1().Services(c.namespace).List(ctx, metav1.ListOptions{})
1✔
440
        if err != nil {
1✔
441
                return fmt.Errorf("failed to list Services: %v", err)
×
442
        }
×
443

444
Items:
1✔
445
        for _, s := range services.Items {
2✔
446
                service := s
1✔
447
                if uid, ok := getOwnerUID(service.ObjectMeta); ok {
2✔
448
                        for _, stackset := range stacksets {
2✔
449
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
450
                                        s.Resources.Service = &service
1✔
451
                                        continue Items
1✔
452
                                }
453

454
                                // service/HPA used to be owned by the deployment for some reason
455
                                for _, stack := range stackset.StackContainers {
2✔
456
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
457
                                                stack.Resources.Service = &service
1✔
458
                                                continue Items
1✔
459
                                        }
460
                                }
461
                        }
462
                }
463
        }
464
        return nil
1✔
465
}
466

467
func (c *StackSetController) collectHPAs(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
468
        hpas, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(c.namespace).List(ctx, metav1.ListOptions{})
1✔
469
        if err != nil {
1✔
470
                return fmt.Errorf("failed to list HPAs: %v", err)
×
471
        }
×
472

473
Items:
1✔
474
        for _, h := range hpas.Items {
2✔
475
                hpa := h
1✔
476
                if uid, ok := getOwnerUID(hpa.ObjectMeta); ok {
2✔
477
                        for _, stackset := range stacksets {
2✔
478
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
479
                                        s.Resources.HPA = &hpa
1✔
480
                                        continue Items
1✔
481
                                }
482

483
                                // service/HPA used to be owned by the deployment for some reason
484
                                for _, stack := range stackset.StackContainers {
2✔
485
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
486
                                                stack.Resources.HPA = &hpa
1✔
487
                                                continue Items
1✔
488
                                        }
489
                                }
490
                        }
491
                }
492
        }
493
        return nil
1✔
494
}
495

496
func (c *StackSetController) collectConfigMaps(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
497
        configMaps, err := c.client.CoreV1().ConfigMaps(c.namespace).List(ctx, metav1.ListOptions{})
1✔
498
        if err != nil {
1✔
499
                return fmt.Errorf("failed to list ConfigMaps: %v", err)
×
500
        }
×
501

502
        for _, cm := range configMaps.Items {
2✔
503
                configMap := cm
1✔
504
                if uid, ok := getOwnerUID(configMap.ObjectMeta); ok {
2✔
505
                        for _, stackset := range stacksets {
2✔
506
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
507
                                        s.Resources.ConfigMaps = append(s.Resources.ConfigMaps, &configMap)
1✔
508
                                        break
1✔
509
                                }
510
                        }
511
                }
512
        }
513
        return nil
1✔
514
}
515

516
func (c *StackSetController) collectSecrets(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
517
        secrets, err := c.client.CoreV1().Secrets(c.namespace).List(ctx, metav1.ListOptions{})
1✔
518
        if err != nil {
1✔
519
                return fmt.Errorf("failed to list Secrets: %v", err)
×
520
        }
×
521

522
        for _, sct := range secrets.Items {
2✔
523
                secret := sct
1✔
524
                if uid, ok := getOwnerUID(secret.ObjectMeta); ok {
2✔
525
                        for _, stackset := range stacksets {
2✔
526
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
527
                                        s.Resources.Secrets = append(s.Resources.Secrets, &secret)
1✔
528
                                        break
1✔
529
                                }
530
                        }
531
                }
532
        }
533
        return nil
1✔
534
}
535

536
func getOwnerUID(objectMeta metav1.ObjectMeta) (types.UID, bool) {
1✔
537
        if len(objectMeta.OwnerReferences) == 1 {
2✔
538
                return objectMeta.OwnerReferences[0].UID, true
1✔
539
        }
1✔
540
        return "", false
1✔
541
}
542

543
func (c *StackSetController) errorEventf(object runtime.Object, reason string, err error) error {
×
544
        switch err.(type) {
×
545
        case *eventedError:
×
546
                // already notified
×
547
                return err
×
548
        default:
×
549
                c.recorder.Eventf(
×
550
                        object,
×
551
                        v1.EventTypeWarning,
×
552
                        reason,
×
553
                        err.Error())
×
554
                return &eventedError{err: err}
×
555
        }
556
}
557

558
// hasOwnership returns true if the controller is the "owner" of the stackset.
559
// Whether it's owner is determined by the value of the
560
// 'stackset-controller.zalando.org/controller' annotation. If the value
561
// matches the controllerID then it owns it, or if the controllerID is
562
// "" and there's no annotation set.
563
func (c *StackSetController) hasOwnership(stackset *zv1.StackSet) bool {
×
564
        if stackset.Annotations != nil {
×
565
                if owner, ok := stackset.Annotations[StacksetControllerControllerAnnotationKey]; ok {
×
566
                        return owner == c.controllerID
×
567
                }
×
568
        }
569
        return c.controllerID == ""
×
570
}
571

572
func (c *StackSetController) startWatch(ctx context.Context) error {
×
573
        informer := cache.NewSharedIndexInformer(
×
574
                cache.NewListWatchFromClient(c.client.ZalandoV1().RESTClient(), "stacksets", c.namespace, fields.Everything()),
×
575
                &zv1.StackSet{},
×
576
                0, // skip resync
×
577
                cache.Indexers{},
×
578
        )
×
579

×
580
        _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
581
                AddFunc:    c.add,
×
582
                UpdateFunc: c.update,
×
583
                DeleteFunc: c.del,
×
584
        })
×
585
        if err != nil {
×
586
                return fmt.Errorf("failed to add event handler: %w", err)
×
587
        }
×
588

589
        go informer.Run(ctx.Done())
×
590
        if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
×
591
                return fmt.Errorf("timed out waiting for caches to sync")
×
592
        }
×
593
        c.logger.Info("Synced StackSet watcher")
×
594

×
595
        return nil
×
596
}
597

598
func (c *StackSetController) add(obj interface{}) {
×
599
        stackset, ok := obj.(*zv1.StackSet)
×
600
        if !ok {
×
601
                return
×
602
        }
×
603

604
        c.logger.Infof("New StackSet added %s/%s", stackset.Namespace, stackset.Name)
×
605
        c.stacksetEvents <- stacksetEvent{
×
606
                StackSet: stackset.DeepCopy(),
×
607
        }
×
608
}
609

610
func (c *StackSetController) update(oldObj, newObj interface{}) {
×
611
        newStackset, ok := newObj.(*zv1.StackSet)
×
612
        if !ok {
×
613
                return
×
614
        }
×
615

616
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
617
        if !ok {
×
618
                return
×
619
        }
×
620

621
        c.logger.Debugf("StackSet %s/%s changed: %s",
×
622
                newStackset.Namespace,
×
623
                newStackset.Name,
×
624
                cmp.Diff(oldStackset, newStackset, cmpopts.IgnoreUnexported(resource.Quantity{})),
×
625
        )
×
626

×
627
        c.logger.Infof("StackSet updated %s/%s", newStackset.Namespace, newStackset.Name)
×
628
        c.stacksetEvents <- stacksetEvent{
×
629
                StackSet: newStackset.DeepCopy(),
×
630
        }
×
631
}
632

633
func (c *StackSetController) del(obj interface{}) {
×
634
        stackset, ok := obj.(*zv1.StackSet)
×
635
        if !ok {
×
636
                return
×
637
        }
×
638

639
        c.logger.Infof("StackSet deleted %s/%s", stackset.Namespace, stackset.Name)
×
640
        c.stacksetEvents <- stacksetEvent{
×
641
                StackSet: stackset.DeepCopy(),
×
642
                Deleted:  true,
×
643
        }
×
644
}
645

646
func retryUpdate(updateFn func(retry bool) error) error {
×
647
        retry := false
×
648
        for {
×
649
                err := updateFn(retry)
×
650
                if err != nil {
×
651
                        if errors.IsConflict(err) {
×
652
                                retry = true
×
653
                                continue
×
654
                        }
655
                        return err
×
656
                }
657
                return nil
×
658
        }
659
}
660

661
// ReconcileStatuses reconciles the statuses of StackSets and Stacks.
662
func (c *StackSetController) ReconcileStatuses(ctx context.Context, ssc *core.StackSetContainer) error {
×
663
        for _, sc := range ssc.StackContainers {
×
664
                stack := sc.Stack.DeepCopy()
×
665
                status := *sc.GenerateStackStatus()
×
666
                err := retryUpdate(func(retry bool) error {
×
667
                        if retry {
×
668
                                updated, err := c.client.ZalandoV1().Stacks(sc.Namespace()).Get(ctx, stack.Name, metav1.GetOptions{})
×
669
                                if err != nil {
×
670
                                        return err
×
671
                                }
×
672
                                stack = updated
×
673
                        }
674
                        if !equality.Semantic.DeepEqual(status, stack.Status) {
×
675
                                stack.Status = status
×
676
                                _, err := c.client.ZalandoV1().Stacks(sc.Namespace()).UpdateStatus(ctx, stack, metav1.UpdateOptions{})
×
677
                                return err
×
678
                        }
×
679
                        return nil
×
680
                })
681
                if err != nil {
×
682
                        return c.errorEventf(sc.Stack, "FailedUpdateStackStatus", err)
×
683
                }
×
684
        }
685

686
        stackset := ssc.StackSet.DeepCopy()
×
687
        status := *ssc.GenerateStackSetStatus()
×
688
        err := retryUpdate(func(retry bool) error {
×
689
                if retry {
×
690
                        updated, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).Get(ctx, ssc.StackSet.Name, metav1.GetOptions{})
×
691
                        if err != nil {
×
692
                                return err
×
693
                        }
×
694
                        stackset = updated
×
695
                }
696
                if !equality.Semantic.DeepEqual(status, stackset.Status) {
×
697
                        stackset.Status = status
×
698
                        _, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, stackset, metav1.UpdateOptions{})
×
699
                        return err
×
700
                }
×
701
                return nil
×
702
        })
703
        if err != nil {
×
704
                return c.errorEventf(ssc.StackSet, "FailedUpdateStackSetStatus", err)
×
705
        }
×
706
        return nil
×
707
}
708

709
// ReconcileTrafficSegments updates the traffic segments according to the actual
710
// traffic weight of each stack.
711
//
712
// Returns the ordered list of Trafic Segments that need to be updated.
713
func (c *StackSetController) ReconcileTrafficSegments(
714
        ctx context.Context,
715
        ssc *core.StackSetContainer,
716
) ([]types.UID, error) {
×
717
        // Compute segments
×
718
        toUpdate, err := ssc.ComputeTrafficSegments()
×
719
        if err != nil {
×
720
                return nil, c.errorEventf(ssc.StackSet, "FailedManageSegments", err)
×
721
        }
×
722

723
        return toUpdate, nil
×
724
}
725

726
// CreateCurrentStack creates a new Stack object for the current stack, if needed
727
func (c *StackSetController) CreateCurrentStack(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
728
        newStack, newStackVersion := ssc.NewStack()
1✔
729
        if newStack == nil {
2✔
730
                return nil
1✔
731
        }
1✔
732

733
        if c.configMapSupportEnabled || c.secretSupportEnabled {
2✔
734
                // ensure that ConfigurationResources are prefixed by Stack name.
1✔
735
                if err := validateAllConfigurationResourcesNames(newStack.Stack); err != nil {
1✔
736
                        return err
×
737
                }
×
738
        }
739

740
        created, err := c.client.ZalandoV1().Stacks(newStack.Namespace()).Create(ctx, newStack.Stack, metav1.CreateOptions{})
1✔
741
        if err != nil {
1✔
742
                return err
×
743
        }
×
744
        fixupStackTypeMeta(created)
1✔
745

1✔
746
        c.recorder.Eventf(
1✔
747
                ssc.StackSet,
1✔
748
                v1.EventTypeNormal,
1✔
749
                "CreatedStack",
1✔
750
                "Created stack %s",
1✔
751
                newStack.Name(),
1✔
752
        )
1✔
753

1✔
754
        // Persist ObservedStackVersion in the status
1✔
755
        updated := ssc.StackSet.DeepCopy()
1✔
756
        updated.Status.ObservedStackVersion = newStackVersion
1✔
757

1✔
758
        result, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, updated, metav1.UpdateOptions{})
1✔
759
        if err != nil {
1✔
760
                return err
×
761
        }
×
762
        fixupStackSetTypeMeta(result)
1✔
763
        ssc.StackSet = result
1✔
764

1✔
765
        ssc.StackContainers[created.UID] = &core.StackContainer{
1✔
766
                Stack:          created,
1✔
767
                PendingRemoval: false,
1✔
768
                Resources:      core.StackResources{},
1✔
769
        }
1✔
770
        return nil
1✔
771
}
772

773
// CleanupOldStacks deletes stacks that are no longer needed.
774
func (c *StackSetController) CleanupOldStacks(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
775
        for _, sc := range ssc.StackContainers {
2✔
776
                if !sc.PendingRemoval {
2✔
777
                        continue
1✔
778
                }
779

780
                stack := sc.Stack
1✔
781
                err := c.client.ZalandoV1().Stacks(stack.Namespace).Delete(ctx, stack.Name, metav1.DeleteOptions{})
1✔
782
                if err != nil {
1✔
783
                        return c.errorEventf(ssc.StackSet, "FailedDeleteStack", err)
×
784
                }
×
785
                c.recorder.Eventf(
1✔
786
                        ssc.StackSet,
1✔
787
                        v1.EventTypeNormal,
1✔
788
                        "DeletedExcessStack",
1✔
789
                        "Deleted excess stack %s",
1✔
790
                        stack.Name)
1✔
791
        }
792

793
        return nil
1✔
794
}
795

796
// AddUpdateStackSetIngress reconciles the Ingress but never deletes it, it returns the existing/new Ingress
797
func (c *StackSetController) AddUpdateStackSetIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup, ingress *networking.Ingress) (*networking.Ingress, error) {
×
798
        // Ingress removed, handled outside
×
799
        if ingress == nil {
×
800
                return existing, nil
×
801
        }
×
802

803
        if existing == nil {
×
804
                if ingress.Annotations == nil {
×
805
                        ingress.Annotations = make(map[string]string)
×
806
                }
×
807
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
808

×
809
                createdIng, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
×
810
                if err != nil {
×
811
                        return nil, err
×
812
                }
×
813
                c.recorder.Eventf(
×
814
                        stackset,
×
815
                        v1.EventTypeNormal,
×
816
                        "CreatedIngress",
×
817
                        "Created Ingress %s",
×
818
                        ingress.Name)
×
819
                return createdIng, nil
×
820
        }
821

822
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
823
        if existingHaveUpdateTimeStamp {
×
824
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
825
        }
×
826

827
        // Check if we need to update the Ingress
828
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(ingress.Spec, existing.Spec) &&
×
829
                equality.Semantic.DeepEqual(ingress.Annotations, existing.Annotations) &&
×
830
                equality.Semantic.DeepEqual(ingress.Labels, existing.Labels) {
×
831
                // add the annotation back after comparing
×
832
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
833
                return existing, nil
×
834
        }
×
835

836
        updated := existing.DeepCopy()
×
837
        updated.Spec = ingress.Spec
×
838
        if ingress.Annotations != nil {
×
839
                updated.Annotations = ingress.Annotations
×
840
        } else {
×
841
                updated.Annotations = make(map[string]string)
×
842
        }
×
843
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
844

×
845
        updated.Labels = ingress.Labels
×
846

×
847
        createdIngress, err := c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
848
        if err != nil {
×
849
                return nil, err
×
850
        }
×
851
        c.recorder.Eventf(
×
852
                stackset,
×
853
                v1.EventTypeNormal,
×
854
                "UpdatedIngress",
×
855
                "Updated Ingress %s",
×
856
                ingress.Name)
×
857
        return createdIngress, nil
×
858
}
859

860
// AddUpdateStackSetRouteGroup reconciles the RouteGroup but never deletes it, it returns the existing/new RouteGroup
861
func (c *StackSetController) AddUpdateStackSetRouteGroup(ctx context.Context, stackset *zv1.StackSet, existing *rgv1.RouteGroup, ingress *networking.Ingress, rg *rgv1.RouteGroup) (*rgv1.RouteGroup, error) {
×
862
        // RouteGroup removed, handled outside
×
863
        if rg == nil {
×
864
                return existing, nil
×
865
        }
×
866

867
        // Create new RouteGroup
868
        if existing == nil {
×
869
                if rg.Annotations == nil {
×
870
                        rg.Annotations = make(map[string]string)
×
871
                }
×
872
                rg.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
873

×
874
                createdRg, err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Create(ctx, rg, metav1.CreateOptions{})
×
875
                if err != nil {
×
876
                        return nil, err
×
877
                }
×
878
                c.recorder.Eventf(
×
879
                        stackset,
×
880
                        v1.EventTypeNormal,
×
881
                        "CreatedRouteGroup",
×
882
                        "Created RouteGroup %s",
×
883
                        rg.Name)
×
884
                return createdRg, nil
×
885
        }
886

887
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
888
        if existingHaveUpdateTimeStamp {
×
889
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
890
        }
×
891

892
        // Check if we need to update the RouteGroup
893
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(rg.Spec, existing.Spec) &&
×
894
                equality.Semantic.DeepEqual(rg.Annotations, existing.Annotations) &&
×
895
                equality.Semantic.DeepEqual(rg.Labels, existing.Labels) {
×
896
                // add the annotation back after comparing
×
897
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
898
                return existing, nil
×
899
        }
×
900

901
        updated := existing.DeepCopy()
×
902
        updated.Spec = rg.Spec
×
903
        if rg.Annotations != nil {
×
904
                updated.Annotations = rg.Annotations
×
905
        } else {
×
906
                updated.Annotations = make(map[string]string)
×
907
        }
×
908
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
909

×
910
        updated.Labels = rg.Labels
×
911

×
912
        createdRg, err := c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
913
        if err != nil {
×
914
                return nil, err
×
915
        }
×
916
        c.recorder.Eventf(
×
917
                stackset,
×
918
                v1.EventTypeNormal,
×
919
                "UpdatedRouteGroup",
×
920
                "Updated RouteGroup %s",
×
921
                rg.Name)
×
922
        return createdRg, nil
×
923
}
924

925
// RecordTrafficSwitch records an event detailing when switches in traffic to
926
// Stacks, only when there are changes to record.
927
func (c *StackSetController) RecordTrafficSwitch(ctx context.Context, ssc *core.StackSetContainer) error {
×
928
        trafficChanges := ssc.TrafficChanges()
×
929
        if len(trafficChanges) != 0 {
×
930
                var changeMessages []string
×
931
                for _, change := range trafficChanges {
×
932
                        changeMessages = append(changeMessages, change.String())
×
933
                }
×
934

935
                c.recorder.Eventf(
×
936
                        ssc.StackSet,
×
937
                        v1.EventTypeNormal,
×
938
                        "TrafficSwitched",
×
939
                        "Switched traffic: %s",
×
940
                        strings.Join(changeMessages, ", "))
×
941
        }
942

943
        return nil
×
944
}
945

946
func (c *StackSetController) ReconcileStackSetDesiredTraffic(ctx context.Context, existing *zv1.StackSet, generateUpdated func() []*zv1.DesiredTraffic) error {
1✔
947
        updatedTraffic := generateUpdated()
1✔
948

1✔
949
        if equality.Semantic.DeepEqual(existing.Spec.Traffic, updatedTraffic) {
1✔
950
                return nil
×
951
        }
×
952

953
        updated := existing.DeepCopy()
1✔
954
        updated.Spec.Traffic = updatedTraffic
1✔
955

1✔
956
        _, err := c.client.ZalandoV1().StackSets(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
957
        if err != nil {
1✔
958
                return err
×
959
        }
×
960
        c.recorder.Eventf(
1✔
961
                updated,
1✔
962
                v1.EventTypeNormal,
1✔
963
                "UpdatedStackSet",
1✔
964
                "Updated StackSet %s",
1✔
965
                updated.Name)
1✔
966
        return nil
1✔
967
}
968

969
func (c *StackSetController) ReconcileStackResources(ctx context.Context, ssc *core.StackSetContainer, sc *core.StackContainer) error {
×
970
        err := c.ReconcileStackIngress(ctx, sc.Stack, sc.Resources.Ingress, sc.GenerateIngress)
×
971
        if err != nil {
×
972
                return c.errorEventf(sc.Stack, "FailedManageIngress", err)
×
973
        }
×
974

975
        err = c.ReconcileStackIngress(
×
976
                ctx,
×
977
                sc.Stack,
×
978
                sc.Resources.IngressSegment,
×
979
                sc.GenerateIngressSegment,
×
980
        )
×
981
        if err != nil {
×
982
                return c.errorEventf(sc.Stack, "FailedManageIngressSegment", err)
×
983
        }
×
984

985
        if c.routeGroupSupportEnabled {
×
986
                err = c.ReconcileStackRouteGroup(ctx, sc.Stack, sc.Resources.RouteGroup, sc.GenerateRouteGroup)
×
987
                if err != nil {
×
988
                        return c.errorEventf(sc.Stack, "FailedManageRouteGroup", err)
×
989
                }
×
990

991
                err = c.ReconcileStackRouteGroup(
×
992
                        ctx,
×
993
                        sc.Stack,
×
994
                        sc.Resources.RouteGroupSegment,
×
995
                        sc.GenerateRouteGroupSegment,
×
996
                )
×
997
                if err != nil {
×
998
                        return c.errorEventf(
×
999
                                sc.Stack,
×
1000
                                "FailedManageRouteGroupSegment",
×
1001
                                err,
×
1002
                        )
×
1003
                }
×
1004
        }
1005

NEW
1006
        if c.inlineConfigMapEnabled {
×
NEW
1007
                err := c.ReconcileStackConfigMaps(ctx, sc.Stack, sc.Resources.ConfigMaps, sc.GenerateConfigMaps)
×
1008
                if err != nil {
×
1009
                        return c.errorEventf(sc.Stack, "FailedManageConfigMaps", err)
×
1010
                }
×
1011
        }
1012

1013
        // if c.configMapSupportEnabled {
1014
        //         err = c.ReconcileStackConfigMapRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
1015
        //         if err != nil {
1016
        //                 return c.errorEventf(sc.Stack, "FailedManageConfigMapRefs", err)
1017
        //         }
1018
        // }
1019

1020
        if c.secretSupportEnabled {
×
1021
                err := c.ReconcileStackSecretRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1022
                if err != nil {
×
1023
                        return c.errorEventf(sc.Stack, "FailedManageSecretRefs", err)
×
1024
                }
×
1025
        }
1026

1027
        err = c.ReconcileStackDeployment(ctx, sc.Stack, sc.Resources.Deployment, sc.GenerateDeployment)
×
1028
        if err != nil {
×
1029
                return c.errorEventf(sc.Stack, "FailedManageDeployment", err)
×
1030
        }
×
1031

1032
        hpaGenerator := sc.GenerateHPA
×
1033
        err = c.ReconcileStackHPA(ctx, sc.Stack, sc.Resources.HPA, hpaGenerator)
×
1034
        if err != nil {
×
1035
                return c.errorEventf(sc.Stack, "FailedManageHPA", err)
×
1036
        }
×
1037

1038
        err = c.ReconcileStackService(ctx, sc.Stack, sc.Resources.Service, sc.GenerateService)
×
1039
        if err != nil {
×
1040
                return c.errorEventf(sc.Stack, "FailedManageService", err)
×
1041
        }
×
1042

1043
        return nil
×
1044
}
1045

1046
// ReconcileStackSet reconciles all the things from a stackset
1047
func (c *StackSetController) ReconcileStackSet(ctx context.Context, container *core.StackSetContainer) (err error) {
×
1048
        defer func() {
×
1049
                if r := recover(); r != nil {
×
1050
                        c.metricsReporter.ReportPanic()
×
1051
                        c.stacksetLogger(container).Errorf("Encountered a panic while processing a stackset: %v\n%s", r, debug.Stack())
×
1052
                        err = fmt.Errorf("panic: %v", r)
×
1053
                }
×
1054
        }()
1055

1056
        // Create current stack, if needed. Proceed on errors.
1057
        err = c.CreateCurrentStack(ctx, container)
×
1058
        if err != nil {
×
1059
                err = c.errorEventf(container.StackSet, "FailedCreateStack", err)
×
1060
                c.stacksetLogger(container).Errorf("Unable to create stack: %v", err)
×
1061
        }
×
1062

1063
        // Update statuses from external resources (ingresses, deployments, etc). Abort on errors.
1064
        err = container.UpdateFromResources()
×
1065
        if err != nil {
×
1066
                return err
×
1067
        }
×
1068

1069
        // Update the stacks with the currently selected traffic reconciler. Proceed on errors.
1070
        err = container.ManageTraffic(time.Now())
×
1071
        if err != nil {
×
1072
                c.stacksetLogger(container).Errorf("Traffic reconciliation failed: %v", err)
×
1073
                c.recorder.Eventf(
×
1074
                        container.StackSet,
×
1075
                        v1.EventTypeWarning,
×
1076
                        "TrafficNotSwitched",
×
1077
                        "Failed to switch traffic: "+err.Error())
×
1078
        }
×
1079

1080
        // Mark stacks that should be removed
1081
        container.MarkExpiredStacks()
×
1082

×
1083
        // Update traffic segments. Proceed on errors.
×
1084
        segsInOrder, err := c.ReconcileTrafficSegments(ctx, container)
×
1085
        if err != nil {
×
1086
                err = c.errorEventf(
×
1087
                        container.StackSet,
×
1088
                        reasonFailedManageStackSet,
×
1089
                        err,
×
1090
                )
×
1091
                c.stacksetLogger(container).Errorf(
×
1092
                        "Unable to reconcile traffic segments: %v",
×
1093
                        err,
×
1094
                )
×
1095
        }
×
1096

1097
        // Reconcile stack resources. Proceed on errors.
1098
        reconciledStacks := map[types.UID]bool{}
×
1099
        for _, id := range segsInOrder {
×
1100
                reconciledStacks[id] = true
×
1101
                sc := container.StackContainers[id]
×
1102
                err = c.ReconcileStackResources(ctx, container, sc)
×
1103
                if err != nil {
×
1104
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1105
                        c.stackLogger(container, sc).Errorf(
×
1106
                                "Unable to reconcile stack resources: %v",
×
1107
                                err,
×
1108
                        )
×
1109
                }
×
1110
        }
1111

1112
        for k, sc := range container.StackContainers {
×
1113
                if reconciledStacks[k] {
×
1114
                        continue
×
1115
                }
1116

1117
                err = c.ReconcileStackResources(ctx, container, sc)
×
1118
                if err != nil {
×
1119
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1120
                        c.stackLogger(container, sc).Errorf("Unable to reconcile stack resources: %v", err)
×
1121
                }
×
1122
        }
1123

1124
        // Reconcile stackset resources (update ingress and/or routegroups). Proceed on errors.
1125
        err = c.RecordTrafficSwitch(ctx, container)
×
1126
        if err != nil {
×
1127
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1128
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset resources: %v", err)
×
1129
        }
×
1130

1131
        // Reconcile desired traffic in the stackset. Proceed on errors.
1132
        err = c.ReconcileStackSetDesiredTraffic(ctx, container.StackSet, container.GenerateStackSetTraffic)
×
1133
        if err != nil {
×
1134
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1135
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset traffic: %v", err)
×
1136
        }
×
1137

1138
        // Delete old stacks. Proceed on errors.
1139
        err = c.CleanupOldStacks(ctx, container)
×
1140
        if err != nil {
×
1141
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1142
                c.stacksetLogger(container).Errorf("Unable to delete old stacks: %v", err)
×
1143
        }
×
1144

1145
        // Update statuses.
1146
        err = c.ReconcileStatuses(ctx, container)
×
1147
        if err != nil {
×
1148
                return err
×
1149
        }
×
1150

1151
        return nil
×
1152
}
1153

1154
// getResetMinReplicasDelay parses and returns the reset delay if set in the
1155
// stackset annotation.
1156
func getResetMinReplicasDelay(annotations map[string]string) (time.Duration, bool) {
1✔
1157
        resetDelayStr, ok := annotations[ResetHPAMinReplicasDelayAnnotationKey]
1✔
1158
        if !ok {
2✔
1159
                return 0, false
1✔
1160
        }
1✔
1161
        resetDelay, err := time.ParseDuration(resetDelayStr)
1✔
1162
        if err != nil {
1✔
1163
                return 0, false
×
1164
        }
×
1165
        return resetDelay, true
1✔
1166
}
1167

1168
func fixupStackSetTypeMeta(stackset *zv1.StackSet) {
1✔
1169
        // set TypeMeta manually because of this bug:
1✔
1170
        // https://github.com/kubernetes/client-go/issues/308
1✔
1171
        stackset.APIVersion = core.APIVersion
1✔
1172
        stackset.Kind = core.KindStackSet
1✔
1173
}
1✔
1174

1175
func fixupStackTypeMeta(stack *zv1.Stack) {
1✔
1176
        // set TypeMeta manually because of this bug:
1✔
1177
        // https://github.com/kubernetes/client-go/issues/308
1✔
1178
        stack.APIVersion = core.APIVersion
1✔
1179
        stack.Kind = core.KindStack
1✔
1180
}
1✔
1181

1182
// validateConfigurationResourcesNames returns an error if any ConfigurationResource
1183
// name is not prefixed by Stack name.
1184
func validateAllConfigurationResourcesNames(stack *zv1.Stack) error {
1✔
1185
        for _, rsc := range stack.Spec.ConfigurationResources {
1✔
NEW
1186
                // There's no need to enforce naming scheme for inline definitions.
×
1187
                if rsc.IsConfigMap() {
×
1188
                        continue
×
1189
                }
1190
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
×
1191
                        return err
×
1192
                }
×
1193
        }
1194
        return nil
1✔
1195
}
1196

1197
// validateConfigurationResourceName returns an error if specific resource
1198
// name is not prefixed by Stack name.
1199
func validateConfigurationResourceName(stack string, rsc string) error {
1✔
1200
        if !strings.HasPrefix(rsc, stack) {
2✔
1201
                return fmt.Errorf(configurationResourceNameError, rsc, stack)
1✔
1202
        }
1✔
1203
        return nil
1✔
1204
}
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