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

zalando-incubator / stackset-controller / 15634078789

13 Jun 2025 12:03PM UTC coverage: 55.441% (-0.04%) from 55.484%
15634078789

push

github

demonCoder95
refactor StackSetController config in a separate struct

41 of 66 new or added lines in 3 files covered. (62.12%)

3006 of 5422 relevant lines covered (55.44%)

0.62 hits per line

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

34.63
/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
        ReconcileWorkers            int
69
        BackendWeightsAnnotationKey string
70
        ClusterDomains              []string
71
        Interval                    time.Duration
72
        RouteGroupSupportEnabled    bool
73
        SyncIngressAnnotations      []string
74
        ConfigMapSupportEnabled     bool
75
        SecretSupportEnabled        bool
76
        PcsSupportEnabled           bool
77
}
78

79
type stacksetEvent struct {
80
        Deleted  bool
81
        StackSet *zv1.StackSet
82
}
83

84
// eventedError wraps an error that was already exposed as an event to the user
85
type eventedError struct {
86
        err error
87
}
88

89
func (ee *eventedError) Error() string {
×
90
        return ee.err.Error()
×
91
}
×
92

93
func now() string {
×
94
        return time.Now().Format(time.RFC3339)
×
95
}
×
96

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

108
        return &StackSetController{
1✔
109
                logger:          log.WithFields(log.Fields{"controller": "stackset"}),
1✔
110
                client:          client,
1✔
111
                config:          config,
1✔
112
                stacksetEvents:  make(chan stacksetEvent, 1),
1✔
113
                stacksetStore:   make(map[types.UID]zv1.StackSet),
1✔
114
                recorder:        recorder.CreateEventRecorder(client),
1✔
115
                metricsReporter: metricsReporter,
1✔
116
                HealthReporter:  healthcheck.NewHandler(),
1✔
117
                now:             now,
1✔
118
        }, nil
1✔
119
}
120

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

128
func (c *StackSetController) stackLogger(ssc *core.StackSetContainer, sc *core.StackContainer) *log.Entry {
×
129
        return c.logger.WithFields(map[string]interface{}{
×
130
                "namespace": ssc.StackSet.Namespace,
×
131
                "stackset":  ssc.StackSet.Name,
×
132
                "stack":     sc.Name(),
×
133
        })
×
134
}
×
135

136
// Run runs the main loop of the StackSetController. Before the loops it
137
// sets up a watcher to watch StackSet resources. The watch will send
138
// changes over a channel which is polled from the main loop.
139
func (c *StackSetController) Run(ctx context.Context) error {
×
140
        var nextCheck time.Time
×
141

×
142
        // We're not alive if nextCheck is too far in the past
×
143
        c.HealthReporter.AddLivenessCheck("nextCheck", func() error {
×
NEW
144
                if time.Since(nextCheck) > 5*c.config.Interval {
×
145
                        return fmt.Errorf("nextCheck too old")
×
146
                }
×
147
                return nil
×
148
        })
149

150
        err := c.startWatch(ctx)
×
151
        if err != nil {
×
152
                return err
×
153
        }
×
154

155
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
156

×
NEW
157
        nextCheck = time.Now().Add(-c.config.Interval)
×
158

×
159
        for {
×
160
                select {
×
161
                case <-time.After(time.Until(nextCheck)):
×
162

×
NEW
163
                        nextCheck = time.Now().Add(c.config.Interval)
×
164

×
165
                        stackSetContainers, err := c.collectResources(ctx)
×
166
                        if err != nil {
×
167
                                c.logger.Errorf("Failed to collect resources: %v", err)
×
168
                                continue
×
169
                        }
170

171
                        var reconcileGroup errgroup.Group
×
NEW
172
                        reconcileGroup.SetLimit(c.config.ReconcileWorkers)
×
173
                        for stackset, container := range stackSetContainers {
×
174
                                container := container
×
175
                                stackset := stackset
×
176

×
177
                                reconcileGroup.Go(func() error {
×
178
                                        if _, ok := c.stacksetStore[stackset]; ok {
×
179
                                                err := c.ReconcileStackSet(ctx, container)
×
180
                                                if err != nil {
×
181
                                                        c.stacksetLogger(container).Errorf("unable to reconcile a stackset: %v", err)
×
182
                                                        return c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
183
                                                }
×
184
                                        }
185
                                        return nil
×
186
                                })
187
                        }
188

189
                        err = reconcileGroup.Wait()
×
190
                        if err != nil {
×
191
                                c.logger.Errorf("Failed waiting for reconcilers: %v", err)
×
192
                        }
×
193
                        err = c.metricsReporter.Report(stackSetContainers)
×
194
                        if err != nil {
×
195
                                c.logger.Errorf("Failed reporting metrics: %v", err)
×
196
                        }
×
197
                case e := <-c.stacksetEvents:
×
198
                        stackset := *e.StackSet
×
199
                        fixupStackSetTypeMeta(&stackset)
×
200

×
201
                        // update/delete existing entry
×
202
                        if _, ok := c.stacksetStore[stackset.UID]; ok {
×
203
                                if e.Deleted || !c.hasOwnership(&stackset) {
×
204
                                        delete(c.stacksetStore, stackset.UID)
×
205
                                        continue
×
206
                                }
207

208
                                // update stackset entry
209
                                c.stacksetStore[stackset.UID] = stackset
×
210
                                continue
×
211
                        }
212

213
                        // check if stackset should be managed by the controller
214
                        if !c.hasOwnership(&stackset) {
×
215
                                continue
×
216
                        }
217

218
                        c.logger.Infof("Adding entry for StackSet %s/%s", stackset.Namespace, stackset.Name)
×
219
                        c.stacksetStore[stackset.UID] = stackset
×
220
                case <-ctx.Done():
×
221
                        c.logger.Info("Terminating main controller loop.")
×
222
                        return nil
×
223
                }
224
        }
225
}
226

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

1✔
234
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
235

1✔
236
                // use prescaling logic if enabled with an annotation
1✔
237
                if _, ok := stackset.Annotations[PrescaleStacksAnnotationKey]; ok {
2✔
238
                        resetDelay := defaultResetMinReplicasDelay
1✔
239
                        if resetDelayValue, ok := getResetMinReplicasDelay(stackset.Annotations); ok {
2✔
240
                                resetDelay = resetDelayValue
1✔
241
                        }
1✔
242
                        reconciler = &core.PrescalingTrafficReconciler{
1✔
243
                                ResetHPAMinReplicasTimeout: resetDelay,
1✔
244
                        }
1✔
245
                }
246

247
                stacksetContainer := core.NewContainer(
1✔
248
                        &stackset,
1✔
249
                        reconciler,
1✔
250
                        c.config.BackendWeightsAnnotationKey,
1✔
251
                        c.config.ClusterDomains,
1✔
252
                        c.config.SyncIngressAnnotations,
1✔
253
                )
1✔
254
                stacksets[uid] = stacksetContainer
1✔
255
        }
256

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

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

267
        if c.config.RouteGroupSupportEnabled {
2✔
268
                err = c.collectRouteGroups(ctx, stacksets)
1✔
269
                if err != nil {
1✔
270
                        return nil, err
×
271
                }
×
272
        }
273

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

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

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

289
        if c.config.ConfigMapSupportEnabled {
2✔
290
                err = c.collectConfigMaps(ctx, stacksets)
1✔
291
                if err != nil {
1✔
292
                        return nil, err
×
293
                }
×
294
        }
295

296
        if c.config.SecretSupportEnabled {
2✔
297
                err = c.collectSecrets(ctx, stacksets)
1✔
298
                if err != nil {
1✔
299
                        return nil, err
×
300
                }
×
301
        }
302

303
        if c.config.PcsSupportEnabled {
2✔
304
                err = c.collectPlatformCredentialsSet(ctx, stacksets)
1✔
305
                if err != nil {
1✔
306
                        return nil, err
×
307
                }
×
308
        }
309

310
        return stacksets, nil
1✔
311
}
312

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

1✔
316
        if err != nil {
1✔
317
                return fmt.Errorf("failed to list Ingresses: %v", err)
×
318
        }
×
319

320
        for _, i := range ingresses.Items {
2✔
321
                ingress := i
1✔
322
                if uid, ok := getOwnerUID(ingress.ObjectMeta); ok {
2✔
323
                        // stackset ingress
1✔
324
                        if s, ok := stacksets[uid]; ok {
2✔
325
                                s.Ingress = &ingress
1✔
326
                                continue
1✔
327
                        }
328

329
                        // stack ingress
330
                        for _, stackset := range stacksets {
2✔
331
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
332
                                        if strings.HasSuffix(
1✔
333
                                                ingress.ObjectMeta.Name,
1✔
334
                                                core.SegmentSuffix,
1✔
335
                                        ) {
2✔
336
                                                // Traffic Segment
1✔
337
                                                s.Resources.IngressSegment = &ingress
1✔
338
                                        } else {
2✔
339
                                                s.Resources.Ingress = &ingress
1✔
340
                                        }
1✔
341
                                        break
1✔
342
                                }
343
                        }
344
                }
345
        }
346
        return nil
1✔
347
}
348

349
func (c *StackSetController) collectRouteGroups(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
350
        rgs, err := c.client.RouteGroupV1().RouteGroups(c.config.Namespace).List(
1✔
351
                ctx,
1✔
352
                metav1.ListOptions{},
1✔
353
        )
1✔
354
        if err != nil {
1✔
355
                return fmt.Errorf("failed to list RouteGroups: %v", err)
×
356
        }
×
357

358
        for _, rg := range rgs.Items {
2✔
359
                routegroup := rg
1✔
360
                if uid, ok := getOwnerUID(routegroup.ObjectMeta); ok {
2✔
361
                        // stackset routegroups
1✔
362
                        if s, ok := stacksets[uid]; ok {
2✔
363
                                s.RouteGroup = &routegroup
1✔
364
                                continue
1✔
365
                        }
366

367
                        // stack routegroups
368
                        for _, stackset := range stacksets {
2✔
369
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
370
                                        if strings.HasSuffix(
1✔
371
                                                routegroup.ObjectMeta.Name,
1✔
372
                                                core.SegmentSuffix,
1✔
373
                                        ) {
2✔
374
                                                // Traffic Segment
1✔
375
                                                s.Resources.RouteGroupSegment = &routegroup
1✔
376
                                        } else {
2✔
377
                                                s.Resources.RouteGroup = &routegroup
1✔
378
                                        }
1✔
379
                                        break
1✔
380
                                }
381
                        }
382
                }
383
        }
384
        return nil
1✔
385
}
386

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

393
        for _, stack := range stacks.Items {
2✔
394
                if uid, ok := getOwnerUID(stack.ObjectMeta); ok {
2✔
395
                        if s, ok := stacksets[uid]; ok {
2✔
396
                                stack := stack
1✔
397
                                fixupStackTypeMeta(&stack)
1✔
398

1✔
399
                                s.StackContainers[stack.UID] = &core.StackContainer{
1✔
400
                                        Stack: &stack,
1✔
401
                                }
1✔
402
                                continue
1✔
403
                        }
404
                }
405
        }
406
        return nil
1✔
407
}
408

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

415
        for _, d := range deployments.Items {
2✔
416
                deployment := d
1✔
417
                if uid, ok := getOwnerUID(deployment.ObjectMeta); ok {
2✔
418
                        for _, stackset := range stacksets {
2✔
419
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
420
                                        s.Resources.Deployment = &deployment
1✔
421
                                        break
1✔
422
                                }
423
                        }
424
                }
425
        }
426
        return nil
1✔
427
}
428

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

435
Items:
1✔
436
        for _, s := range services.Items {
2✔
437
                service := s
1✔
438
                if uid, ok := getOwnerUID(service.ObjectMeta); ok {
2✔
439
                        for _, stackset := range stacksets {
2✔
440
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
441
                                        s.Resources.Service = &service
1✔
442
                                        continue Items
1✔
443
                                }
444

445
                                // service/HPA used to be owned by the deployment for some reason
446
                                for _, stack := range stackset.StackContainers {
2✔
447
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
448
                                                stack.Resources.Service = &service
1✔
449
                                                continue Items
1✔
450
                                        }
451
                                }
452
                        }
453
                }
454
        }
455
        return nil
1✔
456
}
457

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

464
Items:
1✔
465
        for _, h := range hpas.Items {
2✔
466
                hpa := h
1✔
467
                if uid, ok := getOwnerUID(hpa.ObjectMeta); ok {
2✔
468
                        for _, stackset := range stacksets {
2✔
469
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
470
                                        s.Resources.HPA = &hpa
1✔
471
                                        continue Items
1✔
472
                                }
473

474
                                // service/HPA used to be owned by the deployment for some reason
475
                                for _, stack := range stackset.StackContainers {
2✔
476
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
477
                                                stack.Resources.HPA = &hpa
1✔
478
                                                continue Items
1✔
479
                                        }
480
                                }
481
                        }
482
                }
483
        }
484
        return nil
1✔
485
}
486

487
func (c *StackSetController) collectConfigMaps(
488
        ctx context.Context,
489
        stacksets map[types.UID]*core.StackSetContainer,
490
) error {
1✔
491
        configMaps, err := c.client.CoreV1().ConfigMaps(c.config.Namespace).List(ctx, metav1.ListOptions{})
1✔
492
        if err != nil {
1✔
493
                return fmt.Errorf("failed to list ConfigMaps: %v", err)
×
494
        }
×
495

496
        for _, cm := range configMaps.Items {
2✔
497
                configMap := cm
1✔
498
                if uid, ok := getOwnerUID(configMap.ObjectMeta); ok {
2✔
499
                        for _, stackset := range stacksets {
2✔
500
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
501
                                        s.Resources.ConfigMaps = append(s.Resources.ConfigMaps, &configMap)
1✔
502
                                        break
1✔
503
                                }
504
                        }
505
                }
506
        }
507
        return nil
1✔
508
}
509

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

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

533
func (c *StackSetController) collectPlatformCredentialsSet(
534
        ctx context.Context,
535
        stacksets map[types.UID]*core.StackSetContainer,
536
) error {
1✔
537
        platformCredentialsSets, err := c.client.ZalandoV1().PlatformCredentialsSets(c.config.Namespace).
1✔
538
                List(ctx, metav1.ListOptions{})
1✔
539
        if err != nil {
1✔
540
                return fmt.Errorf("failed to list PlatformCredentialsSet: %v", err)
×
541
        }
×
542

543
        for _, platformCredentialsSet := range platformCredentialsSets.Items {
1✔
544
                pcs := platformCredentialsSet
×
545
                if uid, ok := getOwnerUID(platformCredentialsSet.ObjectMeta); ok {
×
546
                        for _, stackset := range stacksets {
×
547
                                if s, ok := stackset.StackContainers[uid]; ok {
×
548
                                        s.Resources.PlatformCredentialsSets = append(
×
549
                                                s.Resources.PlatformCredentialsSets,
×
550
                                                &pcs,
×
551
                                        )
×
552
                                        break
×
553
                                }
554
                        }
555
                }
556
        }
557
        return nil
1✔
558
}
559

560
func getOwnerUID(objectMeta metav1.ObjectMeta) (types.UID, bool) {
1✔
561
        if len(objectMeta.OwnerReferences) == 1 {
2✔
562
                return objectMeta.OwnerReferences[0].UID, true
1✔
563
        }
1✔
564
        return "", false
1✔
565
}
566

567
func (c *StackSetController) errorEventf(object runtime.Object, reason string, err error) error {
×
568
        switch err.(type) {
×
569
        case *eventedError:
×
570
                // already notified
×
571
                return err
×
572
        default:
×
573
                c.recorder.Eventf(
×
574
                        object,
×
575
                        v1.EventTypeWarning,
×
576
                        reason,
×
577
                        err.Error())
×
578
                return &eventedError{err: err}
×
579
        }
580
}
581

582
// hasOwnership returns true if the controller is the "owner" of the stackset.
583
// Whether it's owner is determined by the value of the
584
// 'stackset-controller.zalando.org/controller' annotation. If the value
585
// matches the controllerID then it owns it, or if the controllerID is
586
// "" and there's no annotation set.
587
func (c *StackSetController) hasOwnership(stackset *zv1.StackSet) bool {
×
588
        if stackset.Annotations != nil {
×
589
                if owner, ok := stackset.Annotations[StacksetControllerControllerAnnotationKey]; ok {
×
NEW
590
                        return owner == c.config.ControllerID
×
591
                }
×
592
        }
NEW
593
        return c.config.ControllerID == ""
×
594
}
595

596
func (c *StackSetController) startWatch(ctx context.Context) error {
×
597
        informer := cache.NewSharedIndexInformer(
×
NEW
598
                cache.NewListWatchFromClient(c.client.ZalandoV1().RESTClient(), "stacksets", c.config.Namespace, fields.Everything()),
×
599
                &zv1.StackSet{},
×
600
                0, // skip resync
×
601
                cache.Indexers{},
×
602
        )
×
603

×
604
        _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
605
                AddFunc:    c.add,
×
606
                UpdateFunc: c.update,
×
607
                DeleteFunc: c.del,
×
608
        })
×
609
        if err != nil {
×
610
                return fmt.Errorf("failed to add event handler: %w", err)
×
611
        }
×
612

613
        go informer.Run(ctx.Done())
×
614
        if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
×
615
                return fmt.Errorf("timed out waiting for caches to sync")
×
616
        }
×
617
        c.logger.Info("Synced StackSet watcher")
×
618

×
619
        return nil
×
620
}
621

622
func (c *StackSetController) add(obj interface{}) {
×
623
        stackset, ok := obj.(*zv1.StackSet)
×
624
        if !ok {
×
625
                return
×
626
        }
×
627

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

634
func (c *StackSetController) update(oldObj, newObj interface{}) {
×
635
        newStackset, ok := newObj.(*zv1.StackSet)
×
636
        if !ok {
×
637
                return
×
638
        }
×
639

640
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
641
        if !ok {
×
642
                return
×
643
        }
×
644

645
        c.logger.Debugf("StackSet %s/%s changed: %s",
×
646
                newStackset.Namespace,
×
647
                newStackset.Name,
×
648
                cmp.Diff(oldStackset, newStackset, cmpopts.IgnoreUnexported(resource.Quantity{})),
×
649
        )
×
650

×
651
        c.logger.Infof("StackSet updated %s/%s", newStackset.Namespace, newStackset.Name)
×
652
        c.stacksetEvents <- stacksetEvent{
×
653
                StackSet: newStackset.DeepCopy(),
×
654
        }
×
655
}
656

657
func (c *StackSetController) del(obj interface{}) {
×
658
        stackset, ok := obj.(*zv1.StackSet)
×
659
        if !ok {
×
660
                return
×
661
        }
×
662

663
        c.logger.Infof("StackSet deleted %s/%s", stackset.Namespace, stackset.Name)
×
664
        c.stacksetEvents <- stacksetEvent{
×
665
                StackSet: stackset.DeepCopy(),
×
666
                Deleted:  true,
×
667
        }
×
668
}
669

670
func retryUpdate(updateFn func(retry bool) error) error {
×
671
        retry := false
×
672
        for {
×
673
                err := updateFn(retry)
×
674
                if err != nil {
×
675
                        if errors.IsConflict(err) {
×
676
                                retry = true
×
677
                                continue
×
678
                        }
679
                        return err
×
680
                }
681
                return nil
×
682
        }
683
}
684

685
// ReconcileStatuses reconciles the statuses of StackSets and Stacks.
686
func (c *StackSetController) ReconcileStatuses(ctx context.Context, ssc *core.StackSetContainer) error {
×
687
        for _, sc := range ssc.StackContainers {
×
688
                stack := sc.Stack.DeepCopy()
×
689
                status := *sc.GenerateStackStatus()
×
690
                err := retryUpdate(func(retry bool) error {
×
691
                        if retry {
×
692
                                updated, err := c.client.ZalandoV1().Stacks(sc.Namespace()).Get(ctx, stack.Name, metav1.GetOptions{})
×
693
                                if err != nil {
×
694
                                        return err
×
695
                                }
×
696
                                stack = updated
×
697
                        }
698
                        if !equality.Semantic.DeepEqual(status, stack.Status) {
×
699
                                stack.Status = status
×
700
                                _, err := c.client.ZalandoV1().Stacks(sc.Namespace()).UpdateStatus(ctx, stack, metav1.UpdateOptions{})
×
701
                                return err
×
702
                        }
×
703
                        return nil
×
704
                })
705
                if err != nil {
×
706
                        return c.errorEventf(sc.Stack, "FailedUpdateStackStatus", err)
×
707
                }
×
708
        }
709

710
        stackset := ssc.StackSet.DeepCopy()
×
711
        status := *ssc.GenerateStackSetStatus()
×
712
        err := retryUpdate(func(retry bool) error {
×
713
                if retry {
×
714
                        updated, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).Get(ctx, ssc.StackSet.Name, metav1.GetOptions{})
×
715
                        if err != nil {
×
716
                                return err
×
717
                        }
×
718
                        stackset = updated
×
719
                }
720
                if !equality.Semantic.DeepEqual(status, stackset.Status) {
×
721
                        stackset.Status = status
×
722
                        _, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, stackset, metav1.UpdateOptions{})
×
723
                        return err
×
724
                }
×
725
                return nil
×
726
        })
727
        if err != nil {
×
728
                return c.errorEventf(ssc.StackSet, "FailedUpdateStackSetStatus", err)
×
729
        }
×
730
        return nil
×
731
}
732

733
// ReconcileTrafficSegments updates the traffic segments according to the actual
734
// traffic weight of each stack.
735
//
736
// Returns the ordered list of Trafic Segments that need to be updated.
737
func (c *StackSetController) ReconcileTrafficSegments(
738
        ctx context.Context,
739
        ssc *core.StackSetContainer,
740
) ([]types.UID, error) {
×
741
        // Compute segments
×
742
        toUpdate, err := ssc.ComputeTrafficSegments()
×
743
        if err != nil {
×
744
                return nil, c.errorEventf(ssc.StackSet, "FailedManageSegments", err)
×
745
        }
×
746

747
        return toUpdate, nil
×
748
}
749

750
// CreateCurrentStack creates a new Stack object for the current stack, if needed
751
func (c *StackSetController) CreateCurrentStack(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
752
        newStack, newStackVersion := ssc.NewStack()
1✔
753
        if newStack == nil {
2✔
754
                return nil
1✔
755
        }
1✔
756

757
        if c.config.ConfigMapSupportEnabled || c.config.SecretSupportEnabled {
2✔
758
                // ensure that ConfigurationResources are prefixed by Stack name.
1✔
759
                if err := validateAllConfigurationResourcesNames(newStack.Stack); err != nil {
1✔
760
                        return err
×
761
                }
×
762
        }
763

764
        created, err := c.client.ZalandoV1().Stacks(newStack.Namespace()).Create(ctx, newStack.Stack, metav1.CreateOptions{})
1✔
765
        if err != nil {
1✔
766
                return err
×
767
        }
×
768
        fixupStackTypeMeta(created)
1✔
769

1✔
770
        c.recorder.Eventf(
1✔
771
                ssc.StackSet,
1✔
772
                v1.EventTypeNormal,
1✔
773
                "CreatedStack",
1✔
774
                "Created stack %s",
1✔
775
                newStack.Name(),
1✔
776
        )
1✔
777

1✔
778
        // Persist ObservedStackVersion in the status
1✔
779
        updated := ssc.StackSet.DeepCopy()
1✔
780
        updated.Status.ObservedStackVersion = newStackVersion
1✔
781

1✔
782
        result, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, updated, metav1.UpdateOptions{})
1✔
783
        if err != nil {
1✔
784
                return err
×
785
        }
×
786
        fixupStackSetTypeMeta(result)
1✔
787
        ssc.StackSet = result
1✔
788

1✔
789
        ssc.StackContainers[created.UID] = &core.StackContainer{
1✔
790
                Stack:          created,
1✔
791
                PendingRemoval: false,
1✔
792
                Resources:      core.StackResources{},
1✔
793
        }
1✔
794
        return nil
1✔
795
}
796

797
// CleanupOldStacks deletes stacks that are no longer needed.
798
func (c *StackSetController) CleanupOldStacks(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
799
        for _, sc := range ssc.StackContainers {
2✔
800
                if !sc.PendingRemoval {
2✔
801
                        continue
1✔
802
                }
803

804
                stack := sc.Stack
1✔
805
                err := c.client.ZalandoV1().Stacks(stack.Namespace).Delete(ctx, stack.Name, metav1.DeleteOptions{})
1✔
806
                if err != nil {
1✔
807
                        return c.errorEventf(ssc.StackSet, "FailedDeleteStack", err)
×
808
                }
×
809
                c.recorder.Eventf(
1✔
810
                        ssc.StackSet,
1✔
811
                        v1.EventTypeNormal,
1✔
812
                        "DeletedExcessStack",
1✔
813
                        "Deleted excess stack %s",
1✔
814
                        stack.Name)
1✔
815
        }
816

817
        return nil
1✔
818
}
819

820
// AddUpdateStackSetIngress reconciles the Ingress but never deletes it, it returns the existing/new Ingress
821
func (c *StackSetController) AddUpdateStackSetIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup, ingress *networking.Ingress) (*networking.Ingress, error) {
×
822
        // Ingress removed, handled outside
×
823
        if ingress == nil {
×
824
                return existing, nil
×
825
        }
×
826

827
        if existing == nil {
×
828
                if ingress.Annotations == nil {
×
829
                        ingress.Annotations = make(map[string]string)
×
830
                }
×
831
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
832

×
833
                createdIng, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
×
834
                if err != nil {
×
835
                        return nil, err
×
836
                }
×
837
                c.recorder.Eventf(
×
838
                        stackset,
×
839
                        v1.EventTypeNormal,
×
840
                        "CreatedIngress",
×
841
                        "Created Ingress %s",
×
842
                        ingress.Name)
×
843
                return createdIng, nil
×
844
        }
845

846
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
847
        if existingHaveUpdateTimeStamp {
×
848
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
849
        }
×
850

851
        // Check if we need to update the Ingress
852
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(ingress.Spec, existing.Spec) &&
×
853
                equality.Semantic.DeepEqual(ingress.Annotations, existing.Annotations) &&
×
854
                equality.Semantic.DeepEqual(ingress.Labels, existing.Labels) {
×
855
                // add the annotation back after comparing
×
856
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
857
                return existing, nil
×
858
        }
×
859

860
        updated := existing.DeepCopy()
×
861
        updated.Spec = ingress.Spec
×
862
        if ingress.Annotations != nil {
×
863
                updated.Annotations = ingress.Annotations
×
864
        } else {
×
865
                updated.Annotations = make(map[string]string)
×
866
        }
×
867
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
868

×
869
        updated.Labels = ingress.Labels
×
870

×
871
        createdIngress, err := c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
872
        if err != nil {
×
873
                return nil, err
×
874
        }
×
875
        c.recorder.Eventf(
×
876
                stackset,
×
877
                v1.EventTypeNormal,
×
878
                "UpdatedIngress",
×
879
                "Updated Ingress %s",
×
880
                ingress.Name)
×
881
        return createdIngress, nil
×
882
}
883

884
// AddUpdateStackSetRouteGroup reconciles the RouteGroup but never deletes it, it returns the existing/new RouteGroup
885
func (c *StackSetController) AddUpdateStackSetRouteGroup(ctx context.Context, stackset *zv1.StackSet, existing *rgv1.RouteGroup, ingress *networking.Ingress, rg *rgv1.RouteGroup) (*rgv1.RouteGroup, error) {
×
886
        // RouteGroup removed, handled outside
×
887
        if rg == nil {
×
888
                return existing, nil
×
889
        }
×
890

891
        // Create new RouteGroup
892
        if existing == nil {
×
893
                if rg.Annotations == nil {
×
894
                        rg.Annotations = make(map[string]string)
×
895
                }
×
896
                rg.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
897

×
898
                createdRg, err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Create(ctx, rg, metav1.CreateOptions{})
×
899
                if err != nil {
×
900
                        return nil, err
×
901
                }
×
902
                c.recorder.Eventf(
×
903
                        stackset,
×
904
                        v1.EventTypeNormal,
×
905
                        "CreatedRouteGroup",
×
906
                        "Created RouteGroup %s",
×
907
                        rg.Name)
×
908
                return createdRg, nil
×
909
        }
910

911
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
912
        if existingHaveUpdateTimeStamp {
×
913
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
914
        }
×
915

916
        // Check if we need to update the RouteGroup
917
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(rg.Spec, existing.Spec) &&
×
918
                equality.Semantic.DeepEqual(rg.Annotations, existing.Annotations) &&
×
919
                equality.Semantic.DeepEqual(rg.Labels, existing.Labels) {
×
920
                // add the annotation back after comparing
×
921
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
922
                return existing, nil
×
923
        }
×
924

925
        updated := existing.DeepCopy()
×
926
        updated.Spec = rg.Spec
×
927
        if rg.Annotations != nil {
×
928
                updated.Annotations = rg.Annotations
×
929
        } else {
×
930
                updated.Annotations = make(map[string]string)
×
931
        }
×
932
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
933

×
934
        updated.Labels = rg.Labels
×
935

×
936
        createdRg, err := c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
937
        if err != nil {
×
938
                return nil, err
×
939
        }
×
940
        c.recorder.Eventf(
×
941
                stackset,
×
942
                v1.EventTypeNormal,
×
943
                "UpdatedRouteGroup",
×
944
                "Updated RouteGroup %s",
×
945
                rg.Name)
×
946
        return createdRg, nil
×
947
}
948

949
// RecordTrafficSwitch records an event detailing when switches in traffic to
950
// Stacks, only when there are changes to record.
951
func (c *StackSetController) RecordTrafficSwitch(ctx context.Context, ssc *core.StackSetContainer) error {
×
952
        trafficChanges := ssc.TrafficChanges()
×
953
        if len(trafficChanges) != 0 {
×
954
                var changeMessages []string
×
955
                for _, change := range trafficChanges {
×
956
                        changeMessages = append(changeMessages, change.String())
×
957
                }
×
958

959
                c.recorder.Eventf(
×
960
                        ssc.StackSet,
×
961
                        v1.EventTypeNormal,
×
962
                        "TrafficSwitched",
×
963
                        "Switched traffic: %s",
×
964
                        strings.Join(changeMessages, ", "))
×
965
        }
966

967
        return nil
×
968
}
969

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

1✔
973
        if equality.Semantic.DeepEqual(existing.Spec.Traffic, updatedTraffic) {
1✔
974
                return nil
×
975
        }
×
976

977
        updated := existing.DeepCopy()
1✔
978
        updated.Spec.Traffic = updatedTraffic
1✔
979

1✔
980
        _, err := c.client.ZalandoV1().StackSets(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
981
        if err != nil {
1✔
982
                return err
×
983
        }
×
984
        c.recorder.Eventf(
1✔
985
                updated,
1✔
986
                v1.EventTypeNormal,
1✔
987
                "UpdatedStackSet",
1✔
988
                "Updated StackSet %s",
1✔
989
                updated.Name)
1✔
990
        return nil
1✔
991
}
992

993
func (c *StackSetController) ReconcileStackResources(ctx context.Context, ssc *core.StackSetContainer, sc *core.StackContainer) error {
×
994
        err := c.ReconcileStackIngress(ctx, sc.Stack, sc.Resources.Ingress, sc.GenerateIngress)
×
995
        if err != nil {
×
996
                return c.errorEventf(sc.Stack, "FailedManageIngress", err)
×
997
        }
×
998

999
        err = c.ReconcileStackIngress(
×
1000
                ctx,
×
1001
                sc.Stack,
×
1002
                sc.Resources.IngressSegment,
×
1003
                sc.GenerateIngressSegment,
×
1004
        )
×
1005
        if err != nil {
×
1006
                return c.errorEventf(sc.Stack, "FailedManageIngressSegment", err)
×
1007
        }
×
1008

NEW
1009
        if c.config.RouteGroupSupportEnabled {
×
1010
                err = c.ReconcileStackRouteGroup(ctx, sc.Stack, sc.Resources.RouteGroup, sc.GenerateRouteGroup)
×
1011
                if err != nil {
×
1012
                        return c.errorEventf(sc.Stack, "FailedManageRouteGroup", err)
×
1013
                }
×
1014

1015
                err = c.ReconcileStackRouteGroup(
×
1016
                        ctx,
×
1017
                        sc.Stack,
×
1018
                        sc.Resources.RouteGroupSegment,
×
1019
                        sc.GenerateRouteGroupSegment,
×
1020
                )
×
1021
                if err != nil {
×
1022
                        return c.errorEventf(
×
1023
                                sc.Stack,
×
1024
                                "FailedManageRouteGroupSegment",
×
1025
                                err,
×
1026
                        )
×
1027
                }
×
1028
        }
1029

NEW
1030
        if c.config.ConfigMapSupportEnabled {
×
1031
                err := c.ReconcileStackConfigMapRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1032
                if err != nil {
×
1033
                        return c.errorEventf(sc.Stack, "FailedManageConfigMapRefs", err)
×
1034
                }
×
1035
        }
1036

NEW
1037
        if c.config.SecretSupportEnabled {
×
1038
                err := c.ReconcileStackSecretRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1039
                if err != nil {
×
1040
                        return c.errorEventf(sc.Stack, "FailedManageSecretRefs", err)
×
1041
                }
×
1042
        }
1043

NEW
1044
        if c.config.PcsSupportEnabled {
×
1045
                err = c.ReconcileStackPlatformCredentialsSets(
×
1046
                        ctx,
×
1047
                        sc.Stack,
×
1048
                        sc.Resources.PlatformCredentialsSets,
×
1049
                        sc.GeneratePlatformCredentialsSet,
×
1050
                )
×
1051
                if err != nil {
×
1052
                        return c.errorEventf(sc.Stack, "FailedManagePlatformCredentialsSet", err)
×
1053
                }
×
1054
        }
1055

1056
        err = c.ReconcileStackDeployment(ctx, sc.Stack, sc.Resources.Deployment, sc.GenerateDeployment)
×
1057
        if err != nil {
×
1058
                return c.errorEventf(sc.Stack, "FailedManageDeployment", err)
×
1059
        }
×
1060

1061
        hpaGenerator := sc.GenerateHPA
×
1062
        err = c.ReconcileStackHPA(ctx, sc.Stack, sc.Resources.HPA, hpaGenerator)
×
1063
        if err != nil {
×
1064
                return c.errorEventf(sc.Stack, "FailedManageHPA", err)
×
1065
        }
×
1066

1067
        err = c.ReconcileStackService(ctx, sc.Stack, sc.Resources.Service, sc.GenerateService)
×
1068
        if err != nil {
×
1069
                return c.errorEventf(sc.Stack, "FailedManageService", err)
×
1070
        }
×
1071

1072
        return nil
×
1073
}
1074

1075
// ReconcileStackSet reconciles all the things from a stackset
1076
func (c *StackSetController) ReconcileStackSet(ctx context.Context, container *core.StackSetContainer) (err error) {
×
1077
        defer func() {
×
1078
                if r := recover(); r != nil {
×
1079
                        c.metricsReporter.ReportPanic()
×
1080
                        c.stacksetLogger(container).Errorf("Encountered a panic while processing a stackset: %v\n%s", r, debug.Stack())
×
1081
                        err = fmt.Errorf("panic: %v", r)
×
1082
                }
×
1083
        }()
1084

1085
        // Create current stack, if needed. Proceed on errors.
1086
        err = c.CreateCurrentStack(ctx, container)
×
1087
        if err != nil {
×
1088
                err = c.errorEventf(container.StackSet, "FailedCreateStack", err)
×
1089
                c.stacksetLogger(container).Errorf("Unable to create stack: %v", err)
×
1090
        }
×
1091

1092
        // Update statuses from external resources (ingresses, deployments, etc). Abort on errors.
1093
        err = container.UpdateFromResources()
×
1094
        if err != nil {
×
1095
                return err
×
1096
        }
×
1097

1098
        // Update the stacks with the currently selected traffic reconciler. Proceed on errors.
1099
        err = container.ManageTraffic(time.Now())
×
1100
        if err != nil {
×
1101
                c.stacksetLogger(container).Errorf("Traffic reconciliation failed: %v", err)
×
1102
                c.recorder.Eventf(
×
1103
                        container.StackSet,
×
1104
                        v1.EventTypeWarning,
×
1105
                        "TrafficNotSwitched",
×
1106
                        "Failed to switch traffic: "+err.Error())
×
1107
        }
×
1108

1109
        // Mark stacks that should be removed
1110
        container.MarkExpiredStacks()
×
1111

×
1112
        // Update traffic segments. Proceed on errors.
×
1113
        segsInOrder, err := c.ReconcileTrafficSegments(ctx, container)
×
1114
        if err != nil {
×
1115
                err = c.errorEventf(
×
1116
                        container.StackSet,
×
1117
                        reasonFailedManageStackSet,
×
1118
                        err,
×
1119
                )
×
1120
                c.stacksetLogger(container).Errorf(
×
1121
                        "Unable to reconcile traffic segments: %v",
×
1122
                        err,
×
1123
                )
×
1124
        }
×
1125

1126
        // Reconcile stack resources. Proceed on errors.
1127
        reconciledStacks := map[types.UID]bool{}
×
1128
        for _, id := range segsInOrder {
×
1129
                reconciledStacks[id] = true
×
1130
                sc := container.StackContainers[id]
×
1131
                err = c.ReconcileStackResources(ctx, container, sc)
×
1132
                if err != nil {
×
1133
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1134
                        c.stackLogger(container, sc).Errorf(
×
1135
                                "Unable to reconcile stack resources: %v",
×
1136
                                err,
×
1137
                        )
×
1138
                }
×
1139
        }
1140

1141
        for k, sc := range container.StackContainers {
×
1142
                if reconciledStacks[k] {
×
1143
                        continue
×
1144
                }
1145

1146
                err = c.ReconcileStackResources(ctx, container, sc)
×
1147
                if err != nil {
×
1148
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1149
                        c.stackLogger(container, sc).Errorf("Unable to reconcile stack resources: %v", err)
×
1150
                }
×
1151
        }
1152

1153
        // Reconcile stackset resources (update ingress and/or routegroups). Proceed on errors.
1154
        err = c.RecordTrafficSwitch(ctx, container)
×
1155
        if err != nil {
×
1156
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1157
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset resources: %v", err)
×
1158
        }
×
1159

1160
        // Reconcile desired traffic in the stackset. Proceed on errors.
1161
        err = c.ReconcileStackSetDesiredTraffic(ctx, container.StackSet, container.GenerateStackSetTraffic)
×
1162
        if err != nil {
×
1163
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1164
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset traffic: %v", err)
×
1165
        }
×
1166

1167
        // Delete old stacks. Proceed on errors.
1168
        err = c.CleanupOldStacks(ctx, container)
×
1169
        if err != nil {
×
1170
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1171
                c.stacksetLogger(container).Errorf("Unable to delete old stacks: %v", err)
×
1172
        }
×
1173

1174
        // Update statuses.
1175
        err = c.ReconcileStatuses(ctx, container)
×
1176
        if err != nil {
×
1177
                return err
×
1178
        }
×
1179

1180
        return nil
×
1181
}
1182

1183
// getResetMinReplicasDelay parses and returns the reset delay if set in the
1184
// stackset annotation.
1185
func getResetMinReplicasDelay(annotations map[string]string) (time.Duration, bool) {
1✔
1186
        resetDelayStr, ok := annotations[ResetHPAMinReplicasDelayAnnotationKey]
1✔
1187
        if !ok {
2✔
1188
                return 0, false
1✔
1189
        }
1✔
1190
        resetDelay, err := time.ParseDuration(resetDelayStr)
1✔
1191
        if err != nil {
1✔
1192
                return 0, false
×
1193
        }
×
1194
        return resetDelay, true
1✔
1195
}
1196

1197
func fixupStackSetTypeMeta(stackset *zv1.StackSet) {
1✔
1198
        // set TypeMeta manually because of this bug:
1✔
1199
        // https://github.com/kubernetes/client-go/issues/308
1✔
1200
        stackset.APIVersion = core.APIVersion
1✔
1201
        stackset.Kind = core.KindStackSet
1✔
1202
}
1✔
1203

1204
func fixupStackTypeMeta(stack *zv1.Stack) {
1✔
1205
        // set TypeMeta manually because of this bug:
1✔
1206
        // https://github.com/kubernetes/client-go/issues/308
1✔
1207
        stack.APIVersion = core.APIVersion
1✔
1208
        stack.Kind = core.KindStack
1✔
1209
}
1✔
1210

1211
// validateConfigurationResourcesNames returns an error if any ConfigurationResource
1212
// name is not prefixed by Stack name.
1213
func validateAllConfigurationResourcesNames(stack *zv1.Stack) error {
1✔
1214
        for _, rsc := range stack.Spec.ConfigurationResources {
1✔
1215
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
×
1216
                        return err
×
1217
                }
×
1218
        }
1219
        return nil
1✔
1220
}
1221

1222
// validateConfigurationResourceName returns an error if specific resource
1223
// name is not prefixed by Stack name.
1224
func validateConfigurationResourceName(stack string, rsc string) error {
1✔
1225
        if !strings.HasPrefix(rsc, stack) {
2✔
1226
                return fmt.Errorf(configurationResourceNameError, rsc, stack)
1✔
1227
        }
1✔
1228
        return nil
1✔
1229
}
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