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

zalando-incubator / stackset-controller / 16222913344

11 Jul 2025 02:47PM UTC coverage: 49.506% (-0.009%) from 49.515%
16222913344

Pull #701

github

linki
drop the concept of internal all together
Pull Request #701: drop internal domain for a quick test

2603 of 5258 relevant lines covered (49.51%)

0.56 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

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
        return &StackSetController{
1✔
112
                logger:          log.WithFields(log.Fields{"controller": "stackset"}),
1✔
113
                client:          client,
1✔
114
                config:          config,
1✔
115
                stacksetEvents:  make(chan stacksetEvent, 1),
1✔
116
                stacksetStore:   make(map[types.UID]zv1.StackSet),
1✔
117
                recorder:        recorder.CreateEventRecorder(client),
1✔
118
                metricsReporter: metricsReporter,
1✔
119
                HealthReporter:  healthcheck.NewHandler(),
1✔
120
                now:             now,
1✔
121
        }, nil
1✔
122
}
123

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

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

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

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

153
        err := c.startWatch(ctx)
×
154
        if err != nil {
×
155
                return err
×
156
        }
×
157

158
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
159

×
160
        nextCheck = time.Now().Add(-c.config.Interval)
×
161

×
162
        for {
×
163
                select {
×
164
                case <-time.After(time.Until(nextCheck)):
×
165

×
166
                        nextCheck = time.Now().Add(c.config.Interval)
×
167

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

174
                        var reconcileGroup errgroup.Group
×
175
                        reconcileGroup.SetLimit(c.config.ReconcileWorkers)
×
176
                        for stackset, container := range stackSetContainers {
×
177
                                container := container
×
178
                                stackset := stackset
×
179

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

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

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

211
                                // update stackset entry
212
                                c.stacksetStore[stackset.UID] = stackset
×
213
                                continue
×
214
                        }
215

216
                        // check if stackset should be managed by the controller
217
                        if !c.hasOwnership(&stackset) {
×
218
                                continue
×
219
                        }
220

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

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

1✔
237
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
238

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

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

260
        err := c.collectStacks(ctx, stacksets)
1✔
261
        if err != nil {
1✔
262
                return nil, err
×
263
        }
×
264

265
        err = c.collectIngresses(ctx, stacksets)
1✔
266
        if err != nil {
1✔
267
                return nil, err
×
268
        }
×
269

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

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

282
        err = c.collectServices(ctx, stacksets)
1✔
283
        if err != nil {
1✔
284
                return nil, err
×
285
        }
×
286

287
        err = c.collectHPAs(ctx, stacksets)
1✔
288
        if err != nil {
1✔
289
                return nil, err
×
290
        }
×
291

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

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

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

313
        return stacksets, nil
1✔
314
}
315

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
622
        return nil
×
623
}
624

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

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

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

643
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
644
        if !ok {
×
645
                return
×
646
        }
×
647

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

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

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

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

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

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

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

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

750
        return toUpdate, nil
×
751
}
752

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

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

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

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

1✔
781
        // Persist ObservedStackVersion in the status
1✔
782
        updated := ssc.StackSet.DeepCopy()
1✔
783
        updated.Status.ObservedStackVersion = newStackVersion
1✔
784

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

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

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

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

820
        return nil
1✔
821
}
822

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

830
        if existing == nil {
×
831
                if ingress.Annotations == nil {
×
832
                        ingress.Annotations = make(map[string]string)
×
833
                }
×
834
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
835

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

849
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
850
        if existingHaveUpdateTimeStamp {
×
851
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
852
        }
×
853

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

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

×
872
        updated.Labels = ingress.Labels
×
873

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

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

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

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

914
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
915
        if existingHaveUpdateTimeStamp {
×
916
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
917
        }
×
918

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

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

×
937
        updated.Labels = rg.Labels
×
938

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

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

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

970
        return nil
×
971
}
972

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

1✔
976
        if equality.Semantic.DeepEqual(existing.Spec.Traffic, updatedTraffic) {
1✔
977
                return nil
×
978
        }
×
979

980
        updated := existing.DeepCopy()
1✔
981
        updated.Spec.Traffic = updatedTraffic
1✔
982

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

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

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

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

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

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

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

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

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

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

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

1075
        return nil
×
1076
}
1077

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

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

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

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

1112
        // Mark stacks that should be removed
1113
        container.MarkExpiredStacks()
×
1114

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

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

1144
        for k, sc := range container.StackContainers {
×
1145
                if reconciledStacks[k] {
×
1146
                        continue
×
1147
                }
1148

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

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

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

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

1177
        // Update statuses.
1178
        err = c.ReconcileStatuses(ctx, container)
×
1179
        if err != nil {
×
1180
                return err
×
1181
        }
×
1182

1183
        return nil
×
1184
}
1185

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

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

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

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

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

© 2025 Coveralls, Inc