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

zalando-incubator / stackset-controller / 16171256539

09 Jul 2025 01:53PM UTC coverage: 49.554% (+0.05%) from 49.506%
16171256539

push

github

demonCoder95
add controller id to the logger correctly

7 of 8 new or added lines in 1 file covered. (87.5%)

2609 of 5265 relevant lines covered (49.55%)

0.56 hits per line

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

35.05
/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
        config          StackSetConfig
56
        stacksetEvents  chan stacksetEvent
57
        stacksetStore   map[types.UID]zv1.StackSet
58
        recorder        kube_record.EventRecorder
59
        metricsReporter *core.MetricsReporter
60
        HealthReporter  healthcheck.Handler
61
        now             func() string
62
        sync.Mutex
63
}
64

65
type StackSetConfig struct {
66
        Namespace    string
67
        ControllerID string
68

69
        ClusterDomains              []string
70
        BackendWeightsAnnotationKey string
71
        SyncIngressAnnotations      []string
72

73
        ReconcileWorkers int
74
        Interval         time.Duration
75

76
        RouteGroupSupportEnabled bool
77
        ConfigMapSupportEnabled  bool
78
        SecretSupportEnabled     bool
79
        PcsSupportEnabled        bool
80
}
81

82
type stacksetEvent struct {
83
        Deleted  bool
84
        StackSet *zv1.StackSet
85
}
86

87
// eventedError wraps an error that was already exposed as an event to the user
88
type eventedError struct {
89
        err error
90
}
91

92
func (ee *eventedError) Error() string {
×
93
        return ee.err.Error()
×
94
}
×
95

96
func now() string {
×
97
        return time.Now().Format(time.RFC3339)
×
98
}
×
99

100
// NewStackSetController initializes a new StackSetController.
101
func NewStackSetController(
102
        client clientset.Interface,
103
        registry prometheus.Registerer,
104
        config StackSetConfig,
105
) (*StackSetController, error) {
1✔
106
        metricsReporter, err := core.NewMetricsReporter(registry)
1✔
107
        if err != nil {
1✔
108
                return nil, err
×
109
        }
×
110

111
        var controllerID string
1✔
112

1✔
113
        if config.ControllerID != "" {
1✔
NEW
114
                controllerID = config.ControllerID
×
115
        } else {
1✔
116
                controllerID = "stackset"
1✔
117
        }
1✔
118

119
        return &StackSetController{
1✔
120
                logger:          log.WithFields(log.Fields{"controller": controllerID}),
1✔
121
                client:          client,
1✔
122
                config:          config,
1✔
123
                stacksetEvents:  make(chan stacksetEvent, 1),
1✔
124
                stacksetStore:   make(map[types.UID]zv1.StackSet),
1✔
125
                recorder:        recorder.CreateEventRecorder(client),
1✔
126
                metricsReporter: metricsReporter,
1✔
127
                HealthReporter:  healthcheck.NewHandler(),
1✔
128
                now:             now,
1✔
129
        }, nil
1✔
130
}
131

132
func (c *StackSetController) stacksetLogger(ssc *core.StackSetContainer) *log.Entry {
×
133
        return c.logger.WithFields(map[string]interface{}{
×
134
                "namespace": ssc.StackSet.Namespace,
×
135
                "stackset":  ssc.StackSet.Name,
×
136
        })
×
137
}
×
138

139
func (c *StackSetController) stackLogger(ssc *core.StackSetContainer, sc *core.StackContainer) *log.Entry {
×
140
        return c.logger.WithFields(map[string]interface{}{
×
141
                "namespace": ssc.StackSet.Namespace,
×
142
                "stackset":  ssc.StackSet.Name,
×
143
                "stack":     sc.Name(),
×
144
        })
×
145
}
×
146

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

×
153
        // We're not alive if nextCheck is too far in the past
×
154
        c.HealthReporter.AddLivenessCheck("nextCheck", func() error {
×
155
                if time.Since(nextCheck) > 5*c.config.Interval {
×
156
                        return fmt.Errorf("nextCheck too old")
×
157
                }
×
158
                return nil
×
159
        })
160

161
        err := c.startWatch(ctx)
×
162
        if err != nil {
×
163
                return err
×
164
        }
×
165

166
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
167

×
168
        nextCheck = time.Now().Add(-c.config.Interval)
×
169

×
170
        for {
×
171
                select {
×
172
                case <-time.After(time.Until(nextCheck)):
×
173

×
174
                        nextCheck = time.Now().Add(c.config.Interval)
×
175

×
176
                        stackSetContainers, err := c.collectResources(ctx)
×
177
                        if err != nil {
×
178
                                c.logger.Errorf("Failed to collect resources: %v", err)
×
179
                                continue
×
180
                        }
181

182
                        var reconcileGroup errgroup.Group
×
183
                        reconcileGroup.SetLimit(c.config.ReconcileWorkers)
×
184
                        for stackset, container := range stackSetContainers {
×
185
                                container := container
×
186
                                stackset := stackset
×
187

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

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

×
212
                        // update/delete existing entry
×
213
                        if _, ok := c.stacksetStore[stackset.UID]; ok {
×
214
                                if e.Deleted || !c.hasOwnership(&stackset) {
×
215
                                        delete(c.stacksetStore, stackset.UID)
×
216
                                        continue
×
217
                                }
218

219
                                // update stackset entry
220
                                c.stacksetStore[stackset.UID] = stackset
×
221
                                continue
×
222
                        }
223

224
                        // check if stackset should be managed by the controller
225
                        if !c.hasOwnership(&stackset) {
×
226
                                continue
×
227
                        }
228

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

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

1✔
245
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
246

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

258
                stacksetContainer := core.NewContainer(
1✔
259
                        &stackset,
1✔
260
                        reconciler,
1✔
261
                        c.config.BackendWeightsAnnotationKey,
1✔
262
                        c.config.ClusterDomains,
1✔
263
                        c.config.SyncIngressAnnotations,
1✔
264
                )
1✔
265
                stacksets[uid] = stacksetContainer
1✔
266
        }
267

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

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

278
        if c.config.RouteGroupSupportEnabled {
2✔
279
                err = c.collectRouteGroups(ctx, stacksets)
1✔
280
                if err != nil {
1✔
281
                        return nil, err
×
282
                }
×
283
        }
284

285
        err = c.collectDeployments(ctx, stacksets)
1✔
286
        if err != nil {
1✔
287
                return nil, err
×
288
        }
×
289

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

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

300
        if c.config.ConfigMapSupportEnabled {
2✔
301
                err = c.collectConfigMaps(ctx, stacksets)
1✔
302
                if err != nil {
1✔
303
                        return nil, err
×
304
                }
×
305
        }
306

307
        if c.config.SecretSupportEnabled {
2✔
308
                err = c.collectSecrets(ctx, stacksets)
1✔
309
                if err != nil {
1✔
310
                        return nil, err
×
311
                }
×
312
        }
313

314
        if c.config.PcsSupportEnabled {
2✔
315
                err = c.collectPlatformCredentialsSet(ctx, stacksets)
1✔
316
                if err != nil {
1✔
317
                        return nil, err
×
318
                }
×
319
        }
320

321
        return stacksets, nil
1✔
322
}
323

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

521
func (c *StackSetController) collectSecrets(
522
        ctx context.Context,
523
        stacksets map[types.UID]*core.StackSetContainer,
524
) error {
1✔
525
        secrets, err := c.client.CoreV1().Secrets(c.config.Namespace).List(ctx, metav1.ListOptions{})
1✔
526
        if err != nil {
1✔
527
                return fmt.Errorf("failed to list Secrets: %v", err)
×
528
        }
×
529

530
        for _, sct := range secrets.Items {
2✔
531
                secret := sct
1✔
532
                if uid, ok := getOwnerUID(secret.ObjectMeta); ok {
2✔
533
                        for _, stackset := range stacksets {
2✔
534
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
535
                                        s.Resources.Secrets = append(s.Resources.Secrets, &secret)
1✔
536
                                        break
1✔
537
                                }
538
                        }
539
                }
540
        }
541
        return nil
1✔
542
}
543

544
func (c *StackSetController) collectPlatformCredentialsSet(
545
        ctx context.Context,
546
        stacksets map[types.UID]*core.StackSetContainer,
547
) error {
1✔
548
        platformCredentialsSets, err := c.client.ZalandoV1().PlatformCredentialsSets(c.config.Namespace).
1✔
549
                List(ctx, metav1.ListOptions{})
1✔
550
        if err != nil {
1✔
551
                return fmt.Errorf("failed to list PlatformCredentialsSet: %v", err)
×
552
        }
×
553

554
        for _, platformCredentialsSet := range platformCredentialsSets.Items {
1✔
555
                pcs := platformCredentialsSet
×
556
                if uid, ok := getOwnerUID(platformCredentialsSet.ObjectMeta); ok {
×
557
                        for _, stackset := range stacksets {
×
558
                                if s, ok := stackset.StackContainers[uid]; ok {
×
559
                                        s.Resources.PlatformCredentialsSets = append(
×
560
                                                s.Resources.PlatformCredentialsSets,
×
561
                                                &pcs,
×
562
                                        )
×
563
                                        break
×
564
                                }
565
                        }
566
                }
567
        }
568
        return nil
1✔
569
}
570

571
func getOwnerUID(objectMeta metav1.ObjectMeta) (types.UID, bool) {
1✔
572
        if len(objectMeta.OwnerReferences) == 1 {
2✔
573
                return objectMeta.OwnerReferences[0].UID, true
1✔
574
        }
1✔
575
        return "", false
1✔
576
}
577

578
func (c *StackSetController) errorEventf(object runtime.Object, reason string, err error) error {
×
579
        switch err.(type) {
×
580
        case *eventedError:
×
581
                // already notified
×
582
                return err
×
583
        default:
×
584
                c.recorder.Eventf(
×
585
                        object,
×
586
                        v1.EventTypeWarning,
×
587
                        reason,
×
588
                        err.Error())
×
589
                return &eventedError{err: err}
×
590
        }
591
}
592

593
// hasOwnership returns true if the controller is the "owner" of the stackset.
594
// Whether it's owner is determined by the value of the
595
// 'stackset-controller.zalando.org/controller' annotation. If the value
596
// matches the controllerID then it owns it, or if the controllerID is
597
// "" and there's no annotation set.
598
func (c *StackSetController) hasOwnership(stackset *zv1.StackSet) bool {
×
599
        if stackset.Annotations != nil {
×
600
                if owner, ok := stackset.Annotations[StacksetControllerControllerAnnotationKey]; ok {
×
601
                        return owner == c.config.ControllerID
×
602
                }
×
603
        }
604
        return c.config.ControllerID == ""
×
605
}
606

607
func (c *StackSetController) startWatch(ctx context.Context) error {
×
608
        informer := cache.NewSharedIndexInformer(
×
609
                cache.NewListWatchFromClient(c.client.ZalandoV1().RESTClient(), "stacksets", c.config.Namespace, fields.Everything()),
×
610
                &zv1.StackSet{},
×
611
                0, // skip resync
×
612
                cache.Indexers{},
×
613
        )
×
614

×
615
        _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
616
                AddFunc:    c.add,
×
617
                UpdateFunc: c.update,
×
618
                DeleteFunc: c.del,
×
619
        })
×
620
        if err != nil {
×
621
                return fmt.Errorf("failed to add event handler: %w", err)
×
622
        }
×
623

624
        go informer.Run(ctx.Done())
×
625
        if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
×
626
                return fmt.Errorf("timed out waiting for caches to sync")
×
627
        }
×
628
        c.logger.Info("Synced StackSet watcher")
×
629

×
630
        return nil
×
631
}
632

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

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

645
func (c *StackSetController) update(oldObj, newObj interface{}) {
×
646
        newStackset, ok := newObj.(*zv1.StackSet)
×
647
        if !ok {
×
648
                return
×
649
        }
×
650

651
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
652
        if !ok {
×
653
                return
×
654
        }
×
655

656
        c.logger.Debugf("StackSet %s/%s changed: %s",
×
657
                newStackset.Namespace,
×
658
                newStackset.Name,
×
659
                cmp.Diff(oldStackset, newStackset, cmpopts.IgnoreUnexported(resource.Quantity{})),
×
660
        )
×
661

×
662
        c.logger.Infof("StackSet updated %s/%s", newStackset.Namespace, newStackset.Name)
×
663
        c.stacksetEvents <- stacksetEvent{
×
664
                StackSet: newStackset.DeepCopy(),
×
665
        }
×
666
}
667

668
func (c *StackSetController) del(obj interface{}) {
×
669
        stackset, ok := obj.(*zv1.StackSet)
×
670
        if !ok {
×
671
                return
×
672
        }
×
673

674
        c.logger.Infof("StackSet deleted %s/%s", stackset.Namespace, stackset.Name)
×
675
        c.stacksetEvents <- stacksetEvent{
×
676
                StackSet: stackset.DeepCopy(),
×
677
                Deleted:  true,
×
678
        }
×
679
}
680

681
func retryUpdate(updateFn func(retry bool) error) error {
×
682
        retry := false
×
683
        for {
×
684
                err := updateFn(retry)
×
685
                if err != nil {
×
686
                        if errors.IsConflict(err) {
×
687
                                retry = true
×
688
                                continue
×
689
                        }
690
                        return err
×
691
                }
692
                return nil
×
693
        }
694
}
695

696
// ReconcileStatuses reconciles the statuses of StackSets and Stacks.
697
func (c *StackSetController) ReconcileStatuses(ctx context.Context, ssc *core.StackSetContainer) error {
×
698
        for _, sc := range ssc.StackContainers {
×
699
                stack := sc.Stack.DeepCopy()
×
700
                status := *sc.GenerateStackStatus()
×
701
                err := retryUpdate(func(retry bool) error {
×
702
                        if retry {
×
703
                                updated, err := c.client.ZalandoV1().Stacks(sc.Namespace()).Get(ctx, stack.Name, metav1.GetOptions{})
×
704
                                if err != nil {
×
705
                                        return err
×
706
                                }
×
707
                                stack = updated
×
708
                        }
709
                        if !equality.Semantic.DeepEqual(status, stack.Status) {
×
710
                                stack.Status = status
×
711
                                _, err := c.client.ZalandoV1().Stacks(sc.Namespace()).UpdateStatus(ctx, stack, metav1.UpdateOptions{})
×
712
                                return err
×
713
                        }
×
714
                        return nil
×
715
                })
716
                if err != nil {
×
717
                        return c.errorEventf(sc.Stack, "FailedUpdateStackStatus", err)
×
718
                }
×
719
        }
720

721
        stackset := ssc.StackSet.DeepCopy()
×
722
        status := *ssc.GenerateStackSetStatus()
×
723
        err := retryUpdate(func(retry bool) error {
×
724
                if retry {
×
725
                        updated, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).Get(ctx, ssc.StackSet.Name, metav1.GetOptions{})
×
726
                        if err != nil {
×
727
                                return err
×
728
                        }
×
729
                        stackset = updated
×
730
                }
731
                if !equality.Semantic.DeepEqual(status, stackset.Status) {
×
732
                        stackset.Status = status
×
733
                        _, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, stackset, metav1.UpdateOptions{})
×
734
                        return err
×
735
                }
×
736
                return nil
×
737
        })
738
        if err != nil {
×
739
                return c.errorEventf(ssc.StackSet, "FailedUpdateStackSetStatus", err)
×
740
        }
×
741
        return nil
×
742
}
743

744
// ReconcileTrafficSegments updates the traffic segments according to the actual
745
// traffic weight of each stack.
746
//
747
// Returns the ordered list of Trafic Segments that need to be updated.
748
func (c *StackSetController) ReconcileTrafficSegments(
749
        ctx context.Context,
750
        ssc *core.StackSetContainer,
751
) ([]types.UID, error) {
×
752
        // Compute segments
×
753
        toUpdate, err := ssc.ComputeTrafficSegments()
×
754
        if err != nil {
×
755
                return nil, c.errorEventf(ssc.StackSet, "FailedManageSegments", err)
×
756
        }
×
757

758
        return toUpdate, nil
×
759
}
760

761
// CreateCurrentStack creates a new Stack object for the current stack, if needed
762
func (c *StackSetController) CreateCurrentStack(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
763
        newStack, newStackVersion := ssc.NewStack()
1✔
764
        if newStack == nil {
2✔
765
                return nil
1✔
766
        }
1✔
767

768
        if c.config.ConfigMapSupportEnabled || c.config.SecretSupportEnabled {
2✔
769
                // ensure that ConfigurationResources are prefixed by Stack name.
1✔
770
                if err := validateAllConfigurationResourcesNames(newStack.Stack); err != nil {
1✔
771
                        return err
×
772
                }
×
773
        }
774

775
        created, err := c.client.ZalandoV1().Stacks(newStack.Namespace()).Create(ctx, newStack.Stack, metav1.CreateOptions{})
1✔
776
        if err != nil {
1✔
777
                return err
×
778
        }
×
779
        fixupStackTypeMeta(created)
1✔
780

1✔
781
        c.recorder.Eventf(
1✔
782
                ssc.StackSet,
1✔
783
                v1.EventTypeNormal,
1✔
784
                "CreatedStack",
1✔
785
                "Created stack %s",
1✔
786
                newStack.Name(),
1✔
787
        )
1✔
788

1✔
789
        // Persist ObservedStackVersion in the status
1✔
790
        updated := ssc.StackSet.DeepCopy()
1✔
791
        updated.Status.ObservedStackVersion = newStackVersion
1✔
792

1✔
793
        result, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, updated, metav1.UpdateOptions{})
1✔
794
        if err != nil {
1✔
795
                return err
×
796
        }
×
797
        fixupStackSetTypeMeta(result)
1✔
798
        ssc.StackSet = result
1✔
799

1✔
800
        ssc.StackContainers[created.UID] = &core.StackContainer{
1✔
801
                Stack:          created,
1✔
802
                PendingRemoval: false,
1✔
803
                Resources:      core.StackResources{},
1✔
804
        }
1✔
805
        return nil
1✔
806
}
807

808
// CleanupOldStacks deletes stacks that are no longer needed.
809
func (c *StackSetController) CleanupOldStacks(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
810
        for _, sc := range ssc.StackContainers {
2✔
811
                if !sc.PendingRemoval {
2✔
812
                        continue
1✔
813
                }
814

815
                stack := sc.Stack
1✔
816
                err := c.client.ZalandoV1().Stacks(stack.Namespace).Delete(ctx, stack.Name, metav1.DeleteOptions{})
1✔
817
                if err != nil {
1✔
818
                        return c.errorEventf(ssc.StackSet, "FailedDeleteStack", err)
×
819
                }
×
820
                c.recorder.Eventf(
1✔
821
                        ssc.StackSet,
1✔
822
                        v1.EventTypeNormal,
1✔
823
                        "DeletedExcessStack",
1✔
824
                        "Deleted excess stack %s",
1✔
825
                        stack.Name)
1✔
826
        }
827

828
        return nil
1✔
829
}
830

831
// AddUpdateStackSetIngress reconciles the Ingress but never deletes it, it returns the existing/new Ingress
832
func (c *StackSetController) AddUpdateStackSetIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup, ingress *networking.Ingress) (*networking.Ingress, error) {
×
833
        // Ingress removed, handled outside
×
834
        if ingress == nil {
×
835
                return existing, nil
×
836
        }
×
837

838
        if existing == nil {
×
839
                if ingress.Annotations == nil {
×
840
                        ingress.Annotations = make(map[string]string)
×
841
                }
×
842
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
843

×
844
                createdIng, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
×
845
                if err != nil {
×
846
                        return nil, err
×
847
                }
×
848
                c.recorder.Eventf(
×
849
                        stackset,
×
850
                        v1.EventTypeNormal,
×
851
                        "CreatedIngress",
×
852
                        "Created Ingress %s",
×
853
                        ingress.Name)
×
854
                return createdIng, nil
×
855
        }
856

857
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
858
        if existingHaveUpdateTimeStamp {
×
859
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
860
        }
×
861

862
        // Check if we need to update the Ingress
863
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(ingress.Spec, existing.Spec) &&
×
864
                equality.Semantic.DeepEqual(ingress.Annotations, existing.Annotations) &&
×
865
                equality.Semantic.DeepEqual(ingress.Labels, existing.Labels) {
×
866
                // add the annotation back after comparing
×
867
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
868
                return existing, nil
×
869
        }
×
870

871
        updated := existing.DeepCopy()
×
872
        updated.Spec = ingress.Spec
×
873
        if ingress.Annotations != nil {
×
874
                updated.Annotations = ingress.Annotations
×
875
        } else {
×
876
                updated.Annotations = make(map[string]string)
×
877
        }
×
878
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
879

×
880
        updated.Labels = ingress.Labels
×
881

×
882
        createdIngress, err := c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
883
        if err != nil {
×
884
                return nil, err
×
885
        }
×
886
        c.recorder.Eventf(
×
887
                stackset,
×
888
                v1.EventTypeNormal,
×
889
                "UpdatedIngress",
×
890
                "Updated Ingress %s",
×
891
                ingress.Name)
×
892
        return createdIngress, nil
×
893
}
894

895
// AddUpdateStackSetRouteGroup reconciles the RouteGroup but never deletes it, it returns the existing/new RouteGroup
896
func (c *StackSetController) AddUpdateStackSetRouteGroup(ctx context.Context, stackset *zv1.StackSet, existing *rgv1.RouteGroup, ingress *networking.Ingress, rg *rgv1.RouteGroup) (*rgv1.RouteGroup, error) {
×
897
        // RouteGroup removed, handled outside
×
898
        if rg == nil {
×
899
                return existing, nil
×
900
        }
×
901

902
        // Create new RouteGroup
903
        if existing == nil {
×
904
                if rg.Annotations == nil {
×
905
                        rg.Annotations = make(map[string]string)
×
906
                }
×
907
                rg.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
908

×
909
                createdRg, err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Create(ctx, rg, metav1.CreateOptions{})
×
910
                if err != nil {
×
911
                        return nil, err
×
912
                }
×
913
                c.recorder.Eventf(
×
914
                        stackset,
×
915
                        v1.EventTypeNormal,
×
916
                        "CreatedRouteGroup",
×
917
                        "Created RouteGroup %s",
×
918
                        rg.Name)
×
919
                return createdRg, nil
×
920
        }
921

922
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
923
        if existingHaveUpdateTimeStamp {
×
924
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
925
        }
×
926

927
        // Check if we need to update the RouteGroup
928
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(rg.Spec, existing.Spec) &&
×
929
                equality.Semantic.DeepEqual(rg.Annotations, existing.Annotations) &&
×
930
                equality.Semantic.DeepEqual(rg.Labels, existing.Labels) {
×
931
                // add the annotation back after comparing
×
932
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
933
                return existing, nil
×
934
        }
×
935

936
        updated := existing.DeepCopy()
×
937
        updated.Spec = rg.Spec
×
938
        if rg.Annotations != nil {
×
939
                updated.Annotations = rg.Annotations
×
940
        } else {
×
941
                updated.Annotations = make(map[string]string)
×
942
        }
×
943
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
944

×
945
        updated.Labels = rg.Labels
×
946

×
947
        createdRg, err := c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
948
        if err != nil {
×
949
                return nil, err
×
950
        }
×
951
        c.recorder.Eventf(
×
952
                stackset,
×
953
                v1.EventTypeNormal,
×
954
                "UpdatedRouteGroup",
×
955
                "Updated RouteGroup %s",
×
956
                rg.Name)
×
957
        return createdRg, nil
×
958
}
959

960
// RecordTrafficSwitch records an event detailing when switches in traffic to
961
// Stacks, only when there are changes to record.
962
func (c *StackSetController) RecordTrafficSwitch(ctx context.Context, ssc *core.StackSetContainer) error {
×
963
        trafficChanges := ssc.TrafficChanges()
×
964
        if len(trafficChanges) != 0 {
×
965
                var changeMessages []string
×
966
                for _, change := range trafficChanges {
×
967
                        changeMessages = append(changeMessages, change.String())
×
968
                }
×
969

970
                c.recorder.Eventf(
×
971
                        ssc.StackSet,
×
972
                        v1.EventTypeNormal,
×
973
                        "TrafficSwitched",
×
974
                        "Switched traffic: %s",
×
975
                        strings.Join(changeMessages, ", "))
×
976
        }
977

978
        return nil
×
979
}
980

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

1✔
984
        if equality.Semantic.DeepEqual(existing.Spec.Traffic, updatedTraffic) {
1✔
985
                return nil
×
986
        }
×
987

988
        updated := existing.DeepCopy()
1✔
989
        updated.Spec.Traffic = updatedTraffic
1✔
990

1✔
991
        _, err := c.client.ZalandoV1().StackSets(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
992
        if err != nil {
1✔
993
                return err
×
994
        }
×
995
        c.recorder.Eventf(
1✔
996
                updated,
1✔
997
                v1.EventTypeNormal,
1✔
998
                "UpdatedStackSet",
1✔
999
                "Updated StackSet %s",
1✔
1000
                updated.Name)
1✔
1001
        return nil
1✔
1002
}
1003

1004
func (c *StackSetController) ReconcileStackResources(ctx context.Context, ssc *core.StackSetContainer, sc *core.StackContainer) error {
×
1005
        err := c.ReconcileStackIngress(ctx, sc.Stack, sc.Resources.Ingress, sc.GenerateIngress)
×
1006
        if err != nil {
×
1007
                return c.errorEventf(sc.Stack, "FailedManageIngress", err)
×
1008
        }
×
1009

1010
        err = c.ReconcileStackIngress(
×
1011
                ctx,
×
1012
                sc.Stack,
×
1013
                sc.Resources.IngressSegment,
×
1014
                sc.GenerateIngressSegment,
×
1015
        )
×
1016
        if err != nil {
×
1017
                return c.errorEventf(sc.Stack, "FailedManageIngressSegment", err)
×
1018
        }
×
1019

1020
        if c.config.RouteGroupSupportEnabled {
×
1021
                err = c.ReconcileStackRouteGroup(ctx, sc.Stack, sc.Resources.RouteGroup, sc.GenerateRouteGroup)
×
1022
                if err != nil {
×
1023
                        return c.errorEventf(sc.Stack, "FailedManageRouteGroup", err)
×
1024
                }
×
1025

1026
                err = c.ReconcileStackRouteGroup(
×
1027
                        ctx,
×
1028
                        sc.Stack,
×
1029
                        sc.Resources.RouteGroupSegment,
×
1030
                        sc.GenerateRouteGroupSegment,
×
1031
                )
×
1032
                if err != nil {
×
1033
                        return c.errorEventf(
×
1034
                                sc.Stack,
×
1035
                                "FailedManageRouteGroupSegment",
×
1036
                                err,
×
1037
                        )
×
1038
                }
×
1039
        }
1040

1041
        if c.config.ConfigMapSupportEnabled {
×
1042
                err := c.ReconcileStackConfigMapRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1043
                if err != nil {
×
1044
                        return c.errorEventf(sc.Stack, "FailedManageConfigMapRefs", err)
×
1045
                }
×
1046
        }
1047

1048
        if c.config.SecretSupportEnabled {
×
1049
                err := c.ReconcileStackSecretRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1050
                if err != nil {
×
1051
                        return c.errorEventf(sc.Stack, "FailedManageSecretRefs", err)
×
1052
                }
×
1053
        }
1054

1055
        if c.config.PcsSupportEnabled {
×
1056
                err = c.ReconcileStackPlatformCredentialsSets(
×
1057
                        ctx,
×
1058
                        sc.Stack,
×
1059
                        sc.Resources.PlatformCredentialsSets,
×
1060
                        sc.GeneratePlatformCredentialsSet,
×
1061
                )
×
1062
                if err != nil {
×
1063
                        return c.errorEventf(sc.Stack, "FailedManagePlatformCredentialsSet", err)
×
1064
                }
×
1065
        }
1066

1067
        err = c.ReconcileStackDeployment(ctx, sc.Stack, sc.Resources.Deployment, sc.GenerateDeployment)
×
1068
        if err != nil {
×
1069
                return c.errorEventf(sc.Stack, "FailedManageDeployment", err)
×
1070
        }
×
1071

1072
        hpaGenerator := sc.GenerateHPA
×
1073
        err = c.ReconcileStackHPA(ctx, sc.Stack, sc.Resources.HPA, hpaGenerator)
×
1074
        if err != nil {
×
1075
                return c.errorEventf(sc.Stack, "FailedManageHPA", err)
×
1076
        }
×
1077

1078
        err = c.ReconcileStackService(ctx, sc.Stack, sc.Resources.Service, sc.GenerateService)
×
1079
        if err != nil {
×
1080
                return c.errorEventf(sc.Stack, "FailedManageService", err)
×
1081
        }
×
1082

1083
        return nil
×
1084
}
1085

1086
// ReconcileStackSet reconciles all the things from a stackset
1087
func (c *StackSetController) ReconcileStackSet(ctx context.Context, container *core.StackSetContainer) (err error) {
×
1088
        defer func() {
×
1089
                if r := recover(); r != nil {
×
1090
                        c.metricsReporter.ReportPanic()
×
1091
                        c.stacksetLogger(container).Errorf("Encountered a panic while processing a stackset: %v\n%s", r, debug.Stack())
×
1092
                        err = fmt.Errorf("panic: %v", r)
×
1093
                }
×
1094
        }()
1095

1096
        // Create current stack, if needed. Proceed on errors.
1097
        err = c.CreateCurrentStack(ctx, container)
×
1098
        if err != nil {
×
1099
                err = c.errorEventf(container.StackSet, "FailedCreateStack", err)
×
1100
                c.stacksetLogger(container).Errorf("Unable to create stack: %v", err)
×
1101
        }
×
1102

1103
        // Update statuses from external resources (ingresses, deployments, etc). Abort on errors.
1104
        err = container.UpdateFromResources()
×
1105
        if err != nil {
×
1106
                return err
×
1107
        }
×
1108

1109
        // Update the stacks with the currently selected traffic reconciler. Proceed on errors.
1110
        err = container.ManageTraffic(time.Now())
×
1111
        if err != nil {
×
1112
                c.stacksetLogger(container).Errorf("Traffic reconciliation failed: %v", err)
×
1113
                c.recorder.Eventf(
×
1114
                        container.StackSet,
×
1115
                        v1.EventTypeWarning,
×
1116
                        "TrafficNotSwitched",
×
1117
                        "Failed to switch traffic: "+err.Error())
×
1118
        }
×
1119

1120
        // Mark stacks that should be removed
1121
        container.MarkExpiredStacks()
×
1122

×
1123
        // Update traffic segments. Proceed on errors.
×
1124
        segsInOrder, err := c.ReconcileTrafficSegments(ctx, container)
×
1125
        if err != nil {
×
1126
                err = c.errorEventf(
×
1127
                        container.StackSet,
×
1128
                        reasonFailedManageStackSet,
×
1129
                        err,
×
1130
                )
×
1131
                c.stacksetLogger(container).Errorf(
×
1132
                        "Unable to reconcile traffic segments: %v",
×
1133
                        err,
×
1134
                )
×
1135
        }
×
1136

1137
        // Reconcile stack resources. Proceed on errors.
1138
        reconciledStacks := map[types.UID]bool{}
×
1139
        for _, id := range segsInOrder {
×
1140
                reconciledStacks[id] = true
×
1141
                sc := container.StackContainers[id]
×
1142
                err = c.ReconcileStackResources(ctx, container, sc)
×
1143
                if err != nil {
×
1144
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1145
                        c.stackLogger(container, sc).Errorf(
×
1146
                                "Unable to reconcile stack resources: %v",
×
1147
                                err,
×
1148
                        )
×
1149
                }
×
1150
        }
1151

1152
        for k, sc := range container.StackContainers {
×
1153
                if reconciledStacks[k] {
×
1154
                        continue
×
1155
                }
1156

1157
                err = c.ReconcileStackResources(ctx, container, sc)
×
1158
                if err != nil {
×
1159
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1160
                        c.stackLogger(container, sc).Errorf("Unable to reconcile stack resources: %v", err)
×
1161
                }
×
1162
        }
1163

1164
        // Reconcile stackset resources (update ingress and/or routegroups). Proceed on errors.
1165
        err = c.RecordTrafficSwitch(ctx, container)
×
1166
        if err != nil {
×
1167
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1168
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset resources: %v", err)
×
1169
        }
×
1170

1171
        // Reconcile desired traffic in the stackset. Proceed on errors.
1172
        err = c.ReconcileStackSetDesiredTraffic(ctx, container.StackSet, container.GenerateStackSetTraffic)
×
1173
        if err != nil {
×
1174
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1175
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset traffic: %v", err)
×
1176
        }
×
1177

1178
        // Delete old stacks. Proceed on errors.
1179
        err = c.CleanupOldStacks(ctx, container)
×
1180
        if err != nil {
×
1181
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1182
                c.stacksetLogger(container).Errorf("Unable to delete old stacks: %v", err)
×
1183
        }
×
1184

1185
        // Update statuses.
1186
        err = c.ReconcileStatuses(ctx, container)
×
1187
        if err != nil {
×
1188
                return err
×
1189
        }
×
1190

1191
        return nil
×
1192
}
1193

1194
// getResetMinReplicasDelay parses and returns the reset delay if set in the
1195
// stackset annotation.
1196
func getResetMinReplicasDelay(annotations map[string]string) (time.Duration, bool) {
1✔
1197
        resetDelayStr, ok := annotations[ResetHPAMinReplicasDelayAnnotationKey]
1✔
1198
        if !ok {
2✔
1199
                return 0, false
1✔
1200
        }
1✔
1201
        resetDelay, err := time.ParseDuration(resetDelayStr)
1✔
1202
        if err != nil {
1✔
1203
                return 0, false
×
1204
        }
×
1205
        return resetDelay, true
1✔
1206
}
1207

1208
func fixupStackSetTypeMeta(stackset *zv1.StackSet) {
1✔
1209
        // set TypeMeta manually because of this bug:
1✔
1210
        // https://github.com/kubernetes/client-go/issues/308
1✔
1211
        stackset.APIVersion = core.APIVersion
1✔
1212
        stackset.Kind = core.KindStackSet
1✔
1213
}
1✔
1214

1215
func fixupStackTypeMeta(stack *zv1.Stack) {
1✔
1216
        // set TypeMeta manually because of this bug:
1✔
1217
        // https://github.com/kubernetes/client-go/issues/308
1✔
1218
        stack.APIVersion = core.APIVersion
1✔
1219
        stack.Kind = core.KindStack
1✔
1220
}
1✔
1221

1222
// validateConfigurationResourcesNames returns an error if any ConfigurationResource
1223
// name is not prefixed by Stack name.
1224
func validateAllConfigurationResourcesNames(stack *zv1.Stack) error {
1✔
1225
        for _, rsc := range stack.Spec.ConfigurationResources {
1✔
1226
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
×
1227
                        return err
×
1228
                }
×
1229
        }
1230
        return nil
1✔
1231
}
1232

1233
// validateConfigurationResourceName returns an error if specific resource
1234
// name is not prefixed by Stack name.
1235
func validateConfigurationResourceName(stack string, rsc string) error {
1✔
1236
        if !strings.HasPrefix(rsc, stack) {
2✔
1237
                return fmt.Errorf(configurationResourceNameError, rsc, stack)
1✔
1238
        }
1✔
1239
        return nil
1✔
1240
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc