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

zalando-incubator / stackset-controller / 5968491680

24 Aug 2023 08:16PM UTC coverage: 74.335% (-0.7%) from 75.025%
5968491680

Pull #518

github

gargravarr
Remove debug printfs.

Signed-off-by: Rodrigo Reis <rodrigo.gargravarr@gmail.com>
Pull Request #518: (WIP) Include ingress and/or routegroup definitions in Stack template.

138 of 138 new or added lines in 7 files covered. (100.0%)

2265 of 3047 relevant lines covered (74.34%)

0.83 hits per line

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

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

69
type stacksetEvent struct {
70
        Deleted  bool
71
        StackSet *zv1.StackSet
72
}
73

74
// eventedError wraps an error that was already exposed as an event to the user
75
type eventedError struct {
76
        err error
77
}
78

79
func (ee *eventedError) Error() string {
×
80
        return ee.err.Error()
×
81
}
×
82

83
func now() string {
×
84
        return time.Now().Format(time.RFC3339)
×
85
}
×
86

87
// NewStackSetController initializes a new StackSetController.
88
func NewStackSetController(client clientset.Interface, controllerID string, parallelWork int, backendWeightsAnnotationKey string, clusterDomains []string, registry prometheus.Registerer, interval time.Duration, routeGroupSupportEnabled bool, ingressSourceSwitchTTL time.Duration) (*StackSetController, error) {
1✔
89
        metricsReporter, err := core.NewMetricsReporter(registry)
1✔
90
        if err != nil {
1✔
91
                return nil, err
×
92
        }
×
93

94
        return &StackSetController{
1✔
95
                logger:                      log.WithFields(log.Fields{"controller": "stackset"}),
1✔
96
                client:                      client,
1✔
97
                controllerID:                controllerID,
1✔
98
                backendWeightsAnnotationKey: backendWeightsAnnotationKey,
1✔
99
                clusterDomains:              clusterDomains,
1✔
100
                interval:                    interval,
1✔
101
                stacksetEvents:              make(chan stacksetEvent, 1),
1✔
102
                stacksetStore:               make(map[types.UID]zv1.StackSet),
1✔
103
                recorder:                    recorder.CreateEventRecorder(client),
1✔
104
                metricsReporter:             metricsReporter,
1✔
105
                HealthReporter:              healthcheck.NewHandler(),
1✔
106
                routeGroupSupportEnabled:    routeGroupSupportEnabled,
1✔
107
                ingressSourceSwitchTTL:      ingressSourceSwitchTTL,
1✔
108
                now:                         now,
1✔
109
                reconcileWorkers:            parallelWork,
1✔
110
        }, nil
1✔
111
}
112

113
func (c *StackSetController) stacksetLogger(ssc *core.StackSetContainer) *log.Entry {
×
114
        return c.logger.WithFields(map[string]interface{}{
×
115
                "namespace": ssc.StackSet.Namespace,
×
116
                "stackset":  ssc.StackSet.Name,
×
117
        })
×
118
}
×
119

120
func (c *StackSetController) stackLogger(ssc *core.StackSetContainer, sc *core.StackContainer) *log.Entry {
×
121
        return c.logger.WithFields(map[string]interface{}{
×
122
                "namespace": ssc.StackSet.Namespace,
×
123
                "stackset":  ssc.StackSet.Name,
×
124
                "stack":     sc.Name(),
×
125
        })
×
126
}
×
127

128
// Run runs the main loop of the StackSetController. Before the loops it
129
// sets up a watcher to watch StackSet resources. The watch will send
130
// changes over a channel which is polled from the main loop.
131
func (c *StackSetController) Run(ctx context.Context) {
×
132
        var nextCheck time.Time
×
133

×
134
        // We're not alive if nextCheck is too far in the past
×
135
        c.HealthReporter.AddLivenessCheck("nextCheck", func() error {
×
136
                if time.Since(nextCheck) > 5*c.interval {
×
137
                        return fmt.Errorf("nextCheck too old")
×
138
                }
×
139
                return nil
×
140
        })
141

142
        c.startWatch(ctx)
×
143

×
144
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
145

×
146
        nextCheck = time.Now().Add(-c.interval)
×
147

×
148
        for {
×
149
                select {
×
150
                case <-time.After(time.Until(nextCheck)):
×
151

×
152
                        nextCheck = time.Now().Add(c.interval)
×
153

×
154
                        stackSetContainers, err := c.collectResources(ctx)
×
155
                        if err != nil {
×
156
                                c.logger.Errorf("Failed to collect resources: %v", err)
×
157
                                continue
×
158
                        }
159

160
                        var reconcileGroup errgroup.Group
×
161
                        reconcileGroup.SetLimit(c.reconcileWorkers)
×
162
                        for stackset, container := range stackSetContainers {
×
163
                                container := container
×
164
                                stackset := stackset
×
165

×
166
                                reconcileGroup.Go(func() error {
×
167
                                        if _, ok := c.stacksetStore[stackset]; ok {
×
168
                                                err := c.ReconcileStackSet(ctx, container)
×
169
                                                if err != nil {
×
170
                                                        c.stacksetLogger(container).Errorf("unable to reconcile a stackset: %v", err)
×
171
                                                        return c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
172
                                                }
×
173
                                        }
174
                                        return nil
×
175
                                })
176
                        }
177

178
                        err = reconcileGroup.Wait()
×
179
                        if err != nil {
×
180
                                c.logger.Errorf("Failed waiting for reconcilers: %v", err)
×
181
                        }
×
182
                        err = c.metricsReporter.Report(stackSetContainers)
×
183
                        if err != nil {
×
184
                                c.logger.Errorf("Failed reporting metrics: %v", err)
×
185
                        }
×
186
                case e := <-c.stacksetEvents:
×
187
                        stackset := *e.StackSet
×
188
                        fixupStackSetTypeMeta(&stackset)
×
189

×
190
                        // update/delete existing entry
×
191
                        if _, ok := c.stacksetStore[stackset.UID]; ok {
×
192
                                if e.Deleted || !c.hasOwnership(&stackset) {
×
193
                                        delete(c.stacksetStore, stackset.UID)
×
194
                                        continue
×
195
                                }
196

197
                                // update stackset entry
198
                                c.stacksetStore[stackset.UID] = stackset
×
199
                                continue
×
200
                        }
201

202
                        // check if stackset should be managed by the controller
203
                        if !c.hasOwnership(&stackset) {
×
204
                                continue
×
205
                        }
206

207
                        c.logger.Infof("Adding entry for StackSet %s/%s", stackset.Namespace, stackset.Name)
×
208
                        c.stacksetStore[stackset.UID] = stackset
×
209
                case <-ctx.Done():
×
210
                        c.logger.Info("Terminating main controller loop.")
×
211
                        return
×
212
                }
213
        }
214
}
215

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

1✔
223
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
224

1✔
225
                // use prescaling logic if enabled with an annotation
1✔
226
                if _, ok := stackset.Annotations[PrescaleStacksAnnotationKey]; ok {
2✔
227
                        resetDelay := defaultResetMinReplicasDelay
1✔
228
                        if resetDelayValue, ok := getResetMinReplicasDelay(stackset.Annotations); ok {
2✔
229
                                resetDelay = resetDelayValue
1✔
230
                        }
1✔
231
                        reconciler = &core.PrescalingTrafficReconciler{
1✔
232
                                ResetHPAMinReplicasTimeout: resetDelay,
1✔
233
                        }
1✔
234
                }
235

236
                stacksetContainer := core.NewContainer(&stackset, reconciler, c.backendWeightsAnnotationKey, c.clusterDomains)
1✔
237
                stacksets[uid] = stacksetContainer
1✔
238
        }
239

240
        err := c.collectStacks(ctx, stacksets)
1✔
241
        if err != nil {
1✔
242
                return nil, err
×
243
        }
×
244

245
        err = c.collectIngresses(ctx, stacksets)
1✔
246
        if err != nil {
1✔
247
                return nil, err
×
248
        }
×
249

250
        if c.routeGroupSupportEnabled {
2✔
251
                err = c.collectRouteGroups(ctx, stacksets)
1✔
252
                if err != nil {
1✔
253
                        return nil, err
×
254
                }
×
255
        }
256

257
        err = c.collectDeployments(ctx, stacksets)
1✔
258
        if err != nil {
1✔
259
                return nil, err
×
260
        }
×
261

262
        err = c.collectServices(ctx, stacksets)
1✔
263
        if err != nil {
1✔
264
                return nil, err
×
265
        }
×
266

267
        err = c.collectHPAs(ctx, stacksets)
1✔
268
        if err != nil {
1✔
269
                return nil, err
×
270
        }
×
271

272
        return stacksets, nil
1✔
273
}
274

275
func (c *StackSetController) collectIngresses(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
276
        ingresses, err := c.client.NetworkingV1().Ingresses(v1.NamespaceAll).List(ctx, metav1.ListOptions{})
1✔
277
        if err != nil {
1✔
278
                return fmt.Errorf("failed to list Ingresses: %v", err)
×
279
        }
×
280

281
Items:
1✔
282
        for _, i := range ingresses.Items {
2✔
283
                ingress := i
1✔
284
                if uid, ok := getOwnerUID(ingress.ObjectMeta); ok {
2✔
285
                        // stackset ingress
1✔
286
                        if s, ok := stacksets[uid]; ok {
2✔
287
                                s.Ingress = &ingress
1✔
288
                                continue
1✔
289
                        }
290

291
                        // stack ingress
292
                        for _, stackset := range stacksets {
2✔
293
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
294
                                        s.Resources.Ingress = &ingress
1✔
295
                                        continue Items
1✔
296
                                }
297
                        }
298
                }
299
        }
300
        return nil
1✔
301
}
302

303
func (c *StackSetController) collectRouteGroups(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
304
        routegroups, err := c.client.RouteGroupV1().RouteGroups(v1.NamespaceAll).List(ctx, metav1.ListOptions{})
1✔
305
        if err != nil {
1✔
306
                return fmt.Errorf("failed to list RouteGroups: %v", err)
×
307
        }
×
308

309
Items:
1✔
310
        for _, rg := range routegroups.Items {
2✔
311
                routegroup := rg
1✔
312
                if uid, ok := getOwnerUID(routegroup.ObjectMeta); ok {
2✔
313
                        // stackset routegroups
1✔
314
                        if s, ok := stacksets[uid]; ok {
2✔
315
                                s.RouteGroup = &routegroup
1✔
316
                                continue
1✔
317
                        }
318

319
                        // stack routegroups
320
                        for _, stackset := range stacksets {
2✔
321
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
322
                                        s.Resources.RouteGroup = &routegroup
1✔
323
                                        continue Items
1✔
324
                                }
325
                        }
326
                }
327
        }
328
        return nil
1✔
329
}
330

331
func (c *StackSetController) collectStacks(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
332
        stacks, err := c.client.ZalandoV1().Stacks(v1.NamespaceAll).List(ctx, metav1.ListOptions{})
1✔
333
        if err != nil {
1✔
334
                return fmt.Errorf("failed to list Stacks: %v", err)
×
335
        }
×
336

337
        for _, stack := range stacks.Items {
2✔
338
                if uid, ok := getOwnerUID(stack.ObjectMeta); ok {
2✔
339
                        if s, ok := stacksets[uid]; ok {
2✔
340
                                stack := stack
1✔
341
                                fixupStackTypeMeta(&stack)
1✔
342

1✔
343
                                s.StackContainers[stack.UID] = &core.StackContainer{
1✔
344
                                        Stack: &stack,
1✔
345
                                }
1✔
346
                                continue
1✔
347
                        }
348
                }
349
        }
350
        return nil
1✔
351
}
352

353
func (c *StackSetController) collectDeployments(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
354
        deployments, err := c.client.AppsV1().Deployments(v1.NamespaceAll).List(ctx, metav1.ListOptions{})
1✔
355
        if err != nil {
1✔
356
                return fmt.Errorf("failed to list Deployments: %v", err)
×
357
        }
×
358

359
        for _, d := range deployments.Items {
2✔
360
                deployment := d
1✔
361
                if uid, ok := getOwnerUID(deployment.ObjectMeta); ok {
2✔
362
                        for _, stackset := range stacksets {
2✔
363
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
364
                                        s.Resources.Deployment = &deployment
1✔
365
                                        break
1✔
366
                                }
367
                        }
368
                }
369
        }
370
        return nil
1✔
371
}
372

373
func (c *StackSetController) collectServices(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
374
        services, err := c.client.CoreV1().Services(v1.NamespaceAll).List(ctx, metav1.ListOptions{})
1✔
375
        if err != nil {
1✔
376
                return fmt.Errorf("failed to list Services: %v", err)
×
377
        }
×
378

379
Items:
1✔
380
        for _, s := range services.Items {
2✔
381
                service := s
1✔
382
                if uid, ok := getOwnerUID(service.ObjectMeta); ok {
2✔
383
                        for _, stackset := range stacksets {
2✔
384
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
385
                                        s.Resources.Service = &service
1✔
386
                                        continue Items
1✔
387
                                }
388

389
                                // service/HPA used to be owned by the deployment for some reason
390
                                for _, stack := range stackset.StackContainers {
2✔
391
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
392
                                                stack.Resources.Service = &service
1✔
393
                                                continue Items
1✔
394
                                        }
395
                                }
396
                        }
397
                }
398
        }
399
        return nil
1✔
400
}
401

402
func (c *StackSetController) collectHPAs(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
403
        hpas, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(v1.NamespaceAll).List(ctx, metav1.ListOptions{})
1✔
404
        if err != nil {
1✔
405
                return fmt.Errorf("failed to list HPAs: %v", err)
×
406
        }
×
407

408
Items:
1✔
409
        for _, h := range hpas.Items {
2✔
410
                hpa := h
1✔
411
                if uid, ok := getOwnerUID(hpa.ObjectMeta); ok {
2✔
412
                        for _, stackset := range stacksets {
2✔
413
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
414
                                        s.Resources.HPA = &hpa
1✔
415
                                        continue Items
1✔
416
                                }
417

418
                                // service/HPA used to be owned by the deployment for some reason
419
                                for _, stack := range stackset.StackContainers {
2✔
420
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
421
                                                stack.Resources.HPA = &hpa
1✔
422
                                                continue Items
1✔
423
                                        }
424
                                }
425
                        }
426
                }
427
        }
428
        return nil
1✔
429
}
430

431
func getOwnerUID(objectMeta metav1.ObjectMeta) (types.UID, bool) {
1✔
432
        if len(objectMeta.OwnerReferences) == 1 {
2✔
433
                return objectMeta.OwnerReferences[0].UID, true
1✔
434
        }
1✔
435
        return "", false
1✔
436
}
437

438
func (c *StackSetController) errorEventf(object runtime.Object, reason string, err error) error {
×
439
        switch err.(type) {
×
440
        case *eventedError:
×
441
                // already notified
×
442
                return err
×
443
        default:
×
444
                c.recorder.Eventf(
×
445
                        object,
×
446
                        v1.EventTypeWarning,
×
447
                        reason,
×
448
                        err.Error())
×
449
                return &eventedError{err: err}
×
450
        }
451
}
452

453
// hasOwnership returns true if the controller is the "owner" of the stackset.
454
// Whether it's owner is determined by the value of the
455
// 'stackset-controller.zalando.org/controller' annotation. If the value
456
// matches the controllerID then it owns it, or if the controllerID is
457
// "" and there's no annotation set.
458
func (c *StackSetController) hasOwnership(stackset *zv1.StackSet) bool {
×
459
        if stackset.Annotations != nil {
×
460
                if owner, ok := stackset.Annotations[StacksetControllerControllerAnnotationKey]; ok {
×
461
                        return owner == c.controllerID
×
462
                }
×
463
        }
464
        return c.controllerID == ""
×
465
}
466

467
func (c *StackSetController) startWatch(ctx context.Context) {
×
468
        informer := cache.NewSharedIndexInformer(
×
469
                cache.NewListWatchFromClient(c.client.ZalandoV1().RESTClient(), "stacksets", v1.NamespaceAll, fields.Everything()),
×
470
                &zv1.StackSet{},
×
471
                0, // skip resync
×
472
                cache.Indexers{},
×
473
        )
×
474

×
475
        informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
476
                AddFunc:    c.add,
×
477
                UpdateFunc: c.update,
×
478
                DeleteFunc: c.del,
×
479
        })
×
480
        go informer.Run(ctx.Done())
×
481
        if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
×
482
                c.logger.Errorf("Timed out waiting for caches to sync")
×
483
                return
×
484
        }
×
485
        c.logger.Info("Synced StackSet watcher")
×
486
}
487

488
func (c *StackSetController) add(obj interface{}) {
×
489
        stackset, ok := obj.(*zv1.StackSet)
×
490
        if !ok {
×
491
                return
×
492
        }
×
493

494
        c.logger.Infof("New StackSet added %s/%s", stackset.Namespace, stackset.Name)
×
495
        c.stacksetEvents <- stacksetEvent{
×
496
                StackSet: stackset.DeepCopy(),
×
497
        }
×
498
}
499

500
func (c *StackSetController) update(oldObj, newObj interface{}) {
×
501
        newStackset, ok := newObj.(*zv1.StackSet)
×
502
        if !ok {
×
503
                return
×
504
        }
×
505

506
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
507
        if !ok {
×
508
                return
×
509
        }
×
510

511
        c.logger.Debugf("StackSet %s/%s changed: %s",
×
512
                newStackset.Namespace,
×
513
                newStackset.Name,
×
514
                cmp.Diff(oldStackset, newStackset, cmpopts.IgnoreUnexported(resource.Quantity{})),
×
515
        )
×
516

×
517
        c.logger.Infof("StackSet updated %s/%s", newStackset.Namespace, newStackset.Name)
×
518
        c.stacksetEvents <- stacksetEvent{
×
519
                StackSet: newStackset.DeepCopy(),
×
520
        }
×
521
}
522

523
func (c *StackSetController) del(obj interface{}) {
×
524
        stackset, ok := obj.(*zv1.StackSet)
×
525
        if !ok {
×
526
                return
×
527
        }
×
528

529
        c.logger.Infof("StackSet deleted %s/%s", stackset.Namespace, stackset.Name)
×
530
        c.stacksetEvents <- stacksetEvent{
×
531
                StackSet: stackset.DeepCopy(),
×
532
                Deleted:  true,
×
533
        }
×
534
}
535

536
func retryUpdate(updateFn func(retry bool) error) error {
×
537
        retry := false
×
538
        for {
×
539
                err := updateFn(retry)
×
540
                if err != nil {
×
541
                        if errors.IsConflict(err) {
×
542
                                retry = true
×
543
                                continue
×
544
                        }
545
                        return err
×
546
                }
547
                return nil
×
548
        }
549
}
550

551
// ReconcileStatuses reconciles the statuses of StackSets and Stacks.
552
func (c *StackSetController) ReconcileStatuses(ctx context.Context, ssc *core.StackSetContainer) error {
×
553
        for _, sc := range ssc.StackContainers {
×
554
                stack := sc.Stack.DeepCopy()
×
555
                status := *sc.GenerateStackStatus()
×
556
                err := retryUpdate(func(retry bool) error {
×
557
                        if retry {
×
558
                                updated, err := c.client.ZalandoV1().Stacks(sc.Namespace()).Get(ctx, stack.Name, metav1.GetOptions{})
×
559
                                if err != nil {
×
560
                                        return err
×
561
                                }
×
562
                                stack = updated
×
563
                        }
564
                        if !equality.Semantic.DeepEqual(status, stack.Status) {
×
565
                                stack.Status = status
×
566
                                _, err := c.client.ZalandoV1().Stacks(sc.Namespace()).UpdateStatus(ctx, stack, metav1.UpdateOptions{})
×
567
                                return err
×
568
                        }
×
569
                        return nil
×
570
                })
571
                if err != nil {
×
572
                        return c.errorEventf(sc.Stack, "FailedUpdateStackStatus", err)
×
573
                }
×
574
        }
575

576
        stackset := ssc.StackSet.DeepCopy()
×
577
        status := *ssc.GenerateStackSetStatus()
×
578
        err := retryUpdate(func(retry bool) error {
×
579
                if retry {
×
580
                        updated, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).Get(ctx, ssc.StackSet.Name, metav1.GetOptions{})
×
581
                        if err != nil {
×
582
                                return err
×
583
                        }
×
584
                        stackset = updated
×
585
                }
586
                if !equality.Semantic.DeepEqual(status, stackset.Status) {
×
587
                        stackset.Status = status
×
588
                        _, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, stackset, metav1.UpdateOptions{})
×
589
                        return err
×
590
                }
×
591
                return nil
×
592
        })
593

594
        if err != nil {
×
595
                return c.errorEventf(ssc.StackSet, "FailedUpdateStackSetStatus", err)
×
596
        }
×
597
        return nil
×
598
}
599

600
// CreateCurrentStack creates a new Stack object for the current stack, if needed
601
func (c *StackSetController) CreateCurrentStack(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
602
        newStack, newStackVersion := ssc.NewStack()
1✔
603
        if newStack == nil {
2✔
604
                return nil
1✔
605
        }
1✔
606

607
        created, err := c.client.ZalandoV1().Stacks(newStack.Namespace()).Create(ctx, newStack.Stack, metav1.CreateOptions{})
1✔
608
        if err != nil {
1✔
609
                return err
×
610
        }
×
611
        fixupStackTypeMeta(created)
1✔
612

1✔
613
        c.recorder.Eventf(
1✔
614
                ssc.StackSet,
1✔
615
                v1.EventTypeNormal,
1✔
616
                "CreatedStack",
1✔
617
                "Created stack %s",
1✔
618
                newStack.Name())
1✔
619

1✔
620
        // Persist ObservedStackVersion in the status
1✔
621
        updated := ssc.StackSet.DeepCopy()
1✔
622
        updated.Status.ObservedStackVersion = newStackVersion
1✔
623

1✔
624
        result, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, updated, metav1.UpdateOptions{})
1✔
625
        if err != nil {
1✔
626
                return err
×
627
        }
×
628
        fixupStackSetTypeMeta(result)
1✔
629
        ssc.StackSet = result
1✔
630

1✔
631
        ssc.StackContainers[created.UID] = &core.StackContainer{
1✔
632
                Stack:          created,
1✔
633
                PendingRemoval: false,
1✔
634
                Resources:      core.StackResources{},
1✔
635
        }
1✔
636
        return nil
1✔
637
}
638

639
// CleanupOldStacks deletes stacks that are no longer needed.
640
func (c *StackSetController) CleanupOldStacks(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
641
        for _, sc := range ssc.StackContainers {
2✔
642
                if !sc.PendingRemoval {
2✔
643
                        continue
1✔
644
                }
645

646
                stack := sc.Stack
1✔
647
                err := c.client.ZalandoV1().Stacks(stack.Namespace).Delete(ctx, stack.Name, metav1.DeleteOptions{})
1✔
648
                if err != nil {
1✔
649
                        return c.errorEventf(ssc.StackSet, "FailedDeleteStack", err)
×
650
                }
×
651
                c.recorder.Eventf(
1✔
652
                        ssc.StackSet,
1✔
653
                        v1.EventTypeNormal,
1✔
654
                        "DeletedExcessStack",
1✔
655
                        "Deleted excess stack %s",
1✔
656
                        stack.Name)
1✔
657
        }
658

659
        return nil
1✔
660
}
661

662
// AddUpdateStackSetIngress reconciles the Ingress but never deletes it, it returns the existing/new Ingress
663
func (c *StackSetController) AddUpdateStackSetIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup, ingress *networking.Ingress) (*networking.Ingress, error) {
1✔
664

1✔
665
        // Ingress removed, handled outside
1✔
666
        if ingress == nil {
2✔
667
                return existing, nil
1✔
668
        }
1✔
669

670
        // Create new Ingress
671
        if existing == nil {
2✔
672
                if ingress.Annotations == nil {
2✔
673
                        ingress.Annotations = make(map[string]string)
1✔
674
                }
1✔
675
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
1✔
676

1✔
677
                createdIng, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
1✔
678
                if err != nil {
1✔
679
                        return nil, err
×
680
                }
×
681
                c.recorder.Eventf(
1✔
682
                        stackset,
1✔
683
                        v1.EventTypeNormal,
1✔
684
                        "CreatedIngress",
1✔
685
                        "Created Ingress %s",
1✔
686
                        ingress.Name)
1✔
687
                return createdIng, nil
1✔
688
        }
689

690
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
1✔
691
        if existingHaveUpdateTimeStamp {
2✔
692
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
1✔
693
        }
1✔
694

695
        // Check if we need to update the Ingress
696
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(ingress.Spec, existing.Spec) &&
1✔
697
                equality.Semantic.DeepEqual(ingress.Annotations, existing.Annotations) &&
1✔
698
                equality.Semantic.DeepEqual(ingress.Labels, existing.Labels) {
2✔
699
                // add the annotation back after comparing
1✔
700
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
1✔
701
                return existing, nil
1✔
702
        }
1✔
703

704
        updated := existing.DeepCopy()
1✔
705
        updated.Spec = ingress.Spec
1✔
706
        if ingress.Annotations != nil {
2✔
707
                updated.Annotations = ingress.Annotations
1✔
708
        } else {
2✔
709
                updated.Annotations = make(map[string]string)
1✔
710
        }
1✔
711
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
1✔
712

1✔
713
        updated.Labels = ingress.Labels
1✔
714

1✔
715
        createdIngress, err := c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
716
        if err != nil {
1✔
717
                return nil, err
×
718
        }
×
719
        c.recorder.Eventf(
1✔
720
                stackset,
1✔
721
                v1.EventTypeNormal,
1✔
722
                "UpdatedIngress",
1✔
723
                "Updated Ingress %s",
1✔
724
                ingress.Name)
1✔
725
        return createdIngress, nil
1✔
726
}
727

728
func (c *StackSetController) deleteIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup) error {
1✔
729
        // Check if a routegroup exists and if so only delete if it has existed for more than ingressSourceWithTTL time.
1✔
730
        if stackset.Spec.RouteGroup != nil && c.routeGroupSupportEnabled {
2✔
731
                if routegroup == nil {
2✔
732
                        c.logger.Infof("Not deleting Ingress %s yet, RouteGroup missing", existing.Name)
1✔
733
                        return nil
1✔
734
                }
1✔
735
                timestamp, ok := routegroup.Annotations[ControllerLastUpdatedAnnotationKey]
1✔
736
                // The only scenario version we could think of for this is
1✔
737
                //  if the RouteGroup was created by an older version of StackSet Controller
1✔
738
                //  in that case, just wait until the RouteGroup has the annotation
1✔
739
                if !ok {
2✔
740
                        c.logger.Infof("Not deleting Ingress %s yet, RouteGroup %s does not have the %s annotation yet", existing.Name, routegroup.Name, ControllerLastUpdatedAnnotationKey)
1✔
741
                        return nil
1✔
742
                }
1✔
743

744
                if ready, err := resourceReady(timestamp, c.ingressSourceSwitchTTL); err != nil {
2✔
745
                        c.logger.Infof("Not deleting Ingress %s yet, RouteGroup %s does not have a valid %s annotation yet", existing.Name, routegroup.Name, ControllerLastUpdatedAnnotationKey)
1✔
746
                        return nil
1✔
747
                } else if !ready {
3✔
748
                        c.logger.Infof("Not deleting Ingress %s yet, RouteGroup %s updated less than %s ago", existing.Name, routegroup.Name, c.ingressSourceSwitchTTL)
1✔
749
                        return nil
1✔
750
                }
1✔
751
        }
752
        err := c.client.NetworkingV1().Ingresses(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
753
        if err != nil {
1✔
754
                return err
×
755
        }
×
756
        c.recorder.Eventf(
1✔
757
                stackset,
1✔
758
                v1.EventTypeNormal,
1✔
759
                "DeletedIngress",
1✔
760
                "Deleted Ingress %s",
1✔
761
                existing.Namespace)
1✔
762
        return nil
1✔
763
}
764

765
// AddUpdateStackSetRouteGroup reconciles the RouteGroup but never deletes it, it returns the existing/new RouteGroup
766
func (c *StackSetController) AddUpdateStackSetRouteGroup(ctx context.Context, stackset *zv1.StackSet, existing *rgv1.RouteGroup, ingress *networking.Ingress, rg *rgv1.RouteGroup) (*rgv1.RouteGroup, error) {
1✔
767
        // RouteGroup removed, handled outside
1✔
768
        if rg == nil {
2✔
769
                return existing, nil
1✔
770
        }
1✔
771

772
        // Create new RouteGroup
773
        if existing == nil {
2✔
774
                if rg.Annotations == nil {
2✔
775
                        rg.Annotations = make(map[string]string)
1✔
776
                }
1✔
777
                rg.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
1✔
778

1✔
779
                createdRg, err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Create(ctx, rg, metav1.CreateOptions{})
1✔
780
                if err != nil {
1✔
781
                        return nil, err
×
782
                }
×
783
                c.recorder.Eventf(
1✔
784
                        stackset,
1✔
785
                        v1.EventTypeNormal,
1✔
786
                        "CreatedRouteGroup",
1✔
787
                        "Created RouteGroup %s",
1✔
788
                        rg.Name)
1✔
789
                return createdRg, nil
1✔
790
        }
791

792
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
1✔
793
        if existingHaveUpdateTimeStamp {
2✔
794
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
1✔
795
        }
1✔
796

797
        // Check if we need to update the RouteGroup
798
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(rg.Spec, existing.Spec) &&
1✔
799
                equality.Semantic.DeepEqual(rg.Annotations, existing.Annotations) &&
1✔
800
                equality.Semantic.DeepEqual(rg.Labels, existing.Labels) {
2✔
801
                // add the annotation back after comparing
1✔
802
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
1✔
803
                return existing, nil
1✔
804
        }
1✔
805

806
        updated := existing.DeepCopy()
1✔
807
        updated.Spec = rg.Spec
1✔
808
        if rg.Annotations != nil {
1✔
809
                updated.Annotations = rg.Annotations
×
810
        } else {
1✔
811
                updated.Annotations = make(map[string]string)
1✔
812
        }
1✔
813
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
1✔
814

1✔
815
        updated.Labels = rg.Labels
1✔
816

1✔
817
        createdRg, err := c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
818
        if err != nil {
1✔
819
                return nil, err
×
820
        }
×
821
        c.recorder.Eventf(
1✔
822
                stackset,
1✔
823
                v1.EventTypeNormal,
1✔
824
                "UpdatedRouteGroup",
1✔
825
                "Updated RouteGroup %s",
1✔
826
                rg.Name)
1✔
827
        return createdRg, nil
1✔
828
}
829

830
func (c *StackSetController) deleteRouteGroup(ctx context.Context, stackset *zv1.StackSet, rg *rgv1.RouteGroup, ingress *networking.Ingress) error {
1✔
831
        // Check if an ingress exists and if so only delete if it has existed for more than ingressSourceWithTTL time.
1✔
832
        if stackset.Spec.Ingress != nil {
2✔
833
                if ingress == nil {
2✔
834
                        c.logger.Infof("Not deleting RouteGroup %s yet, Ingress missing", rg.Name)
1✔
835
                        return nil
1✔
836
                }
1✔
837
                timestamp, ok := ingress.Annotations[ControllerLastUpdatedAnnotationKey]
1✔
838
                // The only scenario version we could think of for this is
1✔
839
                //  if the RouteGroup was created by an older version of StackSet Controller
1✔
840
                //  in that case, just wait until the RouteGroup has the annotation
1✔
841
                if !ok {
2✔
842
                        c.logger.Infof("Not deleting RouteGroup %s yet, Ingress %s does not have the %s annotation yet", rg.Name, ingress.Name, ControllerLastUpdatedAnnotationKey)
1✔
843
                        return nil
1✔
844
                }
1✔
845

846
                if ready, err := resourceReady(timestamp, c.ingressSourceSwitchTTL); err != nil {
2✔
847
                        c.logger.Infof("Not deleting RouteGroup %s yet, Ingress %s does not have a valid %s annotation yet", rg.Name, ingress.Name, ControllerLastUpdatedAnnotationKey)
1✔
848
                        return nil
1✔
849
                } else if !ready {
3✔
850
                        c.logger.Infof("Not deleting RouteGroup %s yet, Ingress %s updated less than %s ago", rg.Name, ingress.Name, c.ingressSourceSwitchTTL)
1✔
851
                        return nil
1✔
852
                }
1✔
853
        }
854
        err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Delete(ctx, rg.Name, metav1.DeleteOptions{})
1✔
855
        if err != nil {
1✔
856
                return err
×
857
        }
×
858
        c.recorder.Eventf(
1✔
859
                stackset,
1✔
860
                v1.EventTypeNormal,
1✔
861
                "DeletedRouteGroup",
1✔
862
                "Deleted RouteGroup %s",
1✔
863
                rg.Namespace)
1✔
864
        return nil
1✔
865
}
866

867
func (c *StackSetController) ReconcileStackSetIngressSources(
868
        ctx context.Context,
869
        stackset *zv1.StackSet,
870
        existingIng *networking.Ingress,
871
        existingRg *rgv1.RouteGroup,
872
        generateIng func() (*networking.Ingress, error),
873
        generateRg func() (*rgv1.RouteGroup, error),
874
) error {
1✔
875
        ingress, err := generateIng()
1✔
876
        if err != nil {
1✔
877
                return c.errorEventf(stackset, "FailedManageIngress", err)
×
878
        }
×
879

880
        // opt-out existingIng creation in case we have an external entity creating existingIng
881
        appliedIng, err := c.AddUpdateStackSetIngress(ctx, stackset, existingIng, existingRg, ingress)
1✔
882
        if err != nil {
1✔
883
                return c.errorEventf(stackset, "FailedManageIngress", err)
×
884
        }
×
885

886
        rg, err := generateRg()
1✔
887
        if err != nil {
1✔
888
                return c.errorEventf(stackset, "FailedManageRouteGroup", err)
×
889
        }
×
890

891
        var appliedRg *rgv1.RouteGroup
1✔
892
        if c.routeGroupSupportEnabled {
2✔
893
                appliedRg, err = c.AddUpdateStackSetRouteGroup(ctx, stackset, existingRg, appliedIng, rg)
1✔
894
                if err != nil {
1✔
895
                        return c.errorEventf(stackset, "FailedManageRouteGroup", err)
×
896
                }
×
897
        }
898

899
        // Ingress removed
900
        if ingress == nil {
2✔
901
                if existingIng != nil {
2✔
902
                        err := c.deleteIngress(ctx, stackset, existingIng, appliedRg)
1✔
903
                        if err != nil {
1✔
904
                                return c.errorEventf(stackset, "FailedManageIngress", err)
×
905
                        }
×
906
                }
907
        }
908

909
        // RouteGroup removed
910
        if rg == nil {
2✔
911
                if existingRg != nil {
2✔
912
                        err := c.deleteRouteGroup(ctx, stackset, existingRg, appliedIng)
1✔
913
                        if err != nil {
1✔
914
                                return c.errorEventf(stackset, "FailedManageRouteGroup", err)
×
915
                        }
×
916
                }
917
        }
918

919
        return nil
1✔
920
}
921

922
func (c *StackSetController) ReconcileStackSetResources(ctx context.Context, ssc *core.StackSetContainer) error {
×
923
        err := c.ReconcileStackSetIngressSources(ctx, ssc.StackSet, ssc.Ingress, ssc.RouteGroup, ssc.GenerateIngress, ssc.GenerateRouteGroup)
×
924
        if err != nil {
×
925
                return err
×
926
        }
×
927

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

×
971
        err := c.ReconcileStackDeployment(ctx, sc.Stack, sc.Resources.Deployment, sc.GenerateDeployment)
×
972
        if err != nil {
×
973
                return c.errorEventf(sc.Stack, "FailedManageDeployment", err)
×
974
        }
×
975
        err = c.ReconcileStackHPA(ctx, sc.Stack, sc.Resources.HPA, sc.GenerateHPA)
×
976
        if err != nil {
×
977
                return c.errorEventf(sc.Stack, "FailedManageHPA", err)
×
978
        }
×
979

980
        err = c.ReconcileStackService(ctx, sc.Stack, sc.Resources.Service, sc.GenerateService)
×
981
        if err != nil {
×
982
                return c.errorEventf(sc.Stack, "FailedManageService", err)
×
983
        }
×
984

985
        err = c.ReconcileStackIngress(ctx, sc.Stack, sc.Resources.Ingress, sc.GenerateIngress)
×
986
        if err != nil {
×
987
                return c.errorEventf(sc.Stack, "FailedManageIngress", err)
×
988
        }
×
989

990
        if c.routeGroupSupportEnabled {
×
991
                err = c.ReconcileStackRouteGroup(ctx, sc.Stack, sc.Resources.RouteGroup, sc.GenerateRouteGroup)
×
992
                if err != nil {
×
993
                        return c.errorEventf(sc.Stack, "FailedManageRouteGroup", err)
×
994
                }
×
995
        }
996

997
        return nil
×
998
}
999

1000
// ReconcileStackSet reconciles all the things from a stackset
1001
func (c *StackSetController) ReconcileStackSet(ctx context.Context, container *core.StackSetContainer) (err error) {
×
1002
        defer func() {
×
1003
                if r := recover(); r != nil {
×
1004
                        c.metricsReporter.ReportPanic()
×
1005
                        c.stacksetLogger(container).Errorf("Encountered a panic while processing a stackset: %v\n%s", r, debug.Stack())
×
1006
                        err = fmt.Errorf("panic: %v", r)
×
1007
                }
×
1008
        }()
1009

1010
        // Create current stack, if needed. Proceed on errors.
1011
        err = c.CreateCurrentStack(ctx, container)
×
1012
        if err != nil {
×
1013
                err = c.errorEventf(container.StackSet, "FailedCreateStack", err)
×
1014
                c.stacksetLogger(container).Errorf("Unable to create stack: %v", err)
×
1015
        }
×
1016

1017
        // Update statuses from external resources (ingresses, deployments, etc). Abort on errors.
1018
        err = container.UpdateFromResources()
×
1019
        if err != nil {
×
1020
                return err
×
1021
        }
×
1022

1023
        // Update the stacks with the currently selected traffic reconciler. Proceed on errors.
1024
        err = container.ManageTraffic(time.Now())
×
1025
        if err != nil {
×
1026
                c.stacksetLogger(container).Errorf("Traffic reconciliation failed: %v", err)
×
1027
                c.recorder.Eventf(
×
1028
                        container.StackSet,
×
1029
                        v1.EventTypeWarning,
×
1030
                        "TrafficNotSwitched",
×
1031
                        "Failed to switch traffic: "+err.Error())
×
1032
        }
×
1033

1034
        // Mark stacks that should be removed
1035
        container.MarkExpiredStacks()
×
1036

×
1037
        // Reconcile stack resources. Proceed on errors.
×
1038
        for _, sc := range container.StackContainers {
×
1039
                err = c.ReconcileStackResources(ctx, container, sc)
×
1040
                if err != nil {
×
1041
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1042
                        c.stackLogger(container, sc).Errorf("Unable to reconcile stack resources: %v", err)
×
1043
                }
×
1044
        }
1045

1046
        // Reconcile stackset resources (update ingress and/or routegroups). Proceed on errors.
1047
        err = c.ReconcileStackSetResources(ctx, container)
×
1048
        if err != nil {
×
1049
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1050
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset resources: %v", err)
×
1051
        }
×
1052

1053
        // Reconcile desired traffic in the stackset. Proceed on errors.
1054
        err = c.ReconcileStackSetDesiredTraffic(ctx, container.StackSet, container.GenerateStackSetTraffic)
×
1055
        if err != nil {
×
1056
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1057
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset traffic: %v", err)
×
1058
        }
×
1059

1060
        // Delete old stacks. Proceed on errors.
1061
        err = c.CleanupOldStacks(ctx, container)
×
1062
        if err != nil {
×
1063
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1064
                c.stacksetLogger(container).Errorf("Unable to delete old stacks: %v", err)
×
1065
        }
×
1066

1067
        // Update statuses.
1068
        err = c.ReconcileStatuses(ctx, container)
×
1069
        if err != nil {
×
1070
                return err
×
1071
        }
×
1072

1073
        return nil
×
1074
}
1075

1076
// getResetMinReplicasDelay parses and returns the reset delay if set in the
1077
// stackset annotation.
1078
func getResetMinReplicasDelay(annotations map[string]string) (time.Duration, bool) {
1✔
1079
        resetDelayStr, ok := annotations[ResetHPAMinReplicasDelayAnnotationKey]
1✔
1080
        if !ok {
2✔
1081
                return 0, false
1✔
1082
        }
1✔
1083
        resetDelay, err := time.ParseDuration(resetDelayStr)
1✔
1084
        if err != nil {
1✔
1085
                return 0, false
×
1086
        }
×
1087
        return resetDelay, true
1✔
1088
}
1089

1090
func fixupStackSetTypeMeta(stackset *zv1.StackSet) {
1✔
1091
        // set TypeMeta manually because of this bug:
1✔
1092
        // https://github.com/kubernetes/client-go/issues/308
1✔
1093
        stackset.APIVersion = core.APIVersion
1✔
1094
        stackset.Kind = core.KindStackSet
1✔
1095
}
1✔
1096

1097
func fixupStackTypeMeta(stack *zv1.Stack) {
1✔
1098
        // set TypeMeta manually because of this bug:
1✔
1099
        // https://github.com/kubernetes/client-go/issues/308
1✔
1100
        stack.APIVersion = core.APIVersion
1✔
1101
        stack.Kind = core.KindStack
1✔
1102
}
1✔
1103

1104
func resourceReady(timestamp string, ttl time.Duration) (bool, error) {
1✔
1105
        resourceLastUpdated, err := time.Parse(time.RFC3339, timestamp)
1✔
1106
        if err != nil {
2✔
1107
                // wait until there's a valid timestamp on the annotation
1✔
1108
                return false, err
1✔
1109
        }
1✔
1110

1111
        if !resourceLastUpdated.IsZero() && time.Since(resourceLastUpdated) > ttl {
2✔
1112
                return true, nil
1✔
1113
        }
1✔
1114

1115
        return false, nil
1✔
1116
}
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