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

zalando-incubator / stackset-controller / 19518745162

19 Nov 2025 10:39PM UTC coverage: 50.122% (+0.3%) from 49.791%
19518745162

Pull #716

github

szuecs
fix: test case should test for deisred 1 and not nil deployment

Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de>
Pull Request #716: feature: zalando.org/forward-backend annotation support to enable migration to eks

43 of 50 new or added lines in 3 files covered. (86.0%)

60 existing lines in 4 files now uncovered.

2665 of 5317 relevant lines covered (50.12%)

0.56 hits per line

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

95.06
/pkg/core/stack_resources.go
1
package core
2

3
import (
4
        "fmt"
5
        "sort"
6
        "strconv"
7
        "strings"
8

9
        log "github.com/sirupsen/logrus"
10
        rgv1 "github.com/szuecs/routegroup-client/apis/zalando.org/v1"
11
        zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
12
        appsv1 "k8s.io/api/apps/v1"
13
        autoscaling "k8s.io/api/autoscaling/v2"
14
        v1 "k8s.io/api/core/v1"
15
        networking "k8s.io/api/networking/v1"
16
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17
        "k8s.io/apimachinery/pkg/labels"
18
        "k8s.io/apimachinery/pkg/util/intstr"
19
        "k8s.io/apimachinery/pkg/util/sets"
20
)
21

22
const (
23
        apiVersionAppsV1 = "apps/v1"
24
        kindDeployment   = "Deployment"
25

26
        SegmentSuffix       = "-traffic-segment"
27
        IngressPredicateKey = "zalando.org/skipper-predicate"
28
)
29

30
type ingressOrRouteGroupSpec interface {
31
        GetAnnotations() map[string]string
32
        GetHosts() []string
33
}
34

35
var (
36
        // set implementation with 0 Byte value
37
        selectorLabels = map[string]struct{}{
38
                StacksetHeritageLabelKey: {},
39
                StackVersionLabelKey:     {},
40
        }
41

42
        // PathTypeImplementationSpecific is the used implementation path type
43
        // for k8s.io/api/networking/v1.HTTPIngressPath resources.
44
        PathTypeImplementationSpecific = networking.PathTypeImplementationSpecific
45
)
46

47
func mapCopy(m map[string]string) map[string]string {
1✔
48
        newMap := map[string]string{}
1✔
49
        for k, v := range m {
2✔
50
                newMap[k] = v
1✔
51
        }
1✔
52
        return newMap
1✔
53
}
54

55
// limitLabels returns a limited set of labels based on the validKeys.
56
func limitLabels(labels map[string]string, validKeys map[string]struct{}) map[string]string {
1✔
57
        newLabels := make(map[string]string, len(labels))
1✔
58
        for k, v := range labels {
2✔
59
                if _, ok := validKeys[k]; ok {
2✔
60
                        newLabels[k] = v
1✔
61
                }
1✔
62
        }
63
        return newLabels
1✔
64
}
65

66
// templateObjectMetaInjectLabels injects labels into a pod template spec.
67
func objectMetaInjectLabels(objectMeta metav1.ObjectMeta, labels map[string]string) metav1.ObjectMeta {
1✔
68
        if objectMeta.Labels == nil {
2✔
69
                objectMeta.Labels = map[string]string{}
1✔
70
        }
1✔
71
        for key, value := range labels {
2✔
72
                if _, ok := objectMeta.Labels[key]; !ok {
2✔
73
                        objectMeta.Labels[key] = value
1✔
74
                }
1✔
75
        }
76
        return objectMeta
1✔
77
}
78

79
// patchForwardBackend rewrites a RouteGroupSpec to send all traffic to another cluster chosen by the operator of skipper
80
func patchForwardBackend(rg *rgv1.RouteGroupSpec) {
1✔
81
        rg.Backends = []rgv1.RouteGroupBackend{
1✔
82
                {
1✔
83
                        Name: forwardBackendName,
1✔
84
                        Type: rgv1.ForwardRouteGroupBackend,
1✔
85
                },
1✔
86
        }
1✔
87
        rg.DefaultBackends = []rgv1.RouteGroupBackendReference{
1✔
88
                {
1✔
89
                        BackendName: forwardBackendName,
1✔
90
                },
1✔
91
        }
1✔
92
        for i := range rg.Routes {
2✔
93
                rg.Routes[i].Backends = []rgv1.RouteGroupBackendReference{
1✔
94
                        {
1✔
95
                                BackendName: forwardBackendName,
1✔
96
                        },
1✔
97
                }
1✔
98
        }
1✔
99
}
100

101
func (sc *StackContainer) objectMeta(segment bool) metav1.ObjectMeta {
1✔
102
        resourceLabels := mapCopy(sc.Stack.Labels)
1✔
103

1✔
104
        name := sc.Name()
1✔
105
        if segment {
2✔
106
                name += SegmentSuffix
1✔
107
        }
1✔
108

109
        return metav1.ObjectMeta{
1✔
110
                Name:      name,
1✔
111
                Namespace: sc.Namespace(),
1✔
112
                Annotations: map[string]string{
1✔
113
                        stackGenerationAnnotationKey: strconv.FormatInt(sc.Stack.Generation, 10),
1✔
114
                },
1✔
115
                Labels: resourceLabels,
1✔
116
                OwnerReferences: []metav1.OwnerReference{
1✔
117
                        {
1✔
118
                                APIVersion: APIVersion,
1✔
119
                                Kind:       KindStack,
1✔
120
                                Name:       sc.Name(),
1✔
121
                                UID:        sc.Stack.UID,
1✔
122
                        },
1✔
123
                },
1✔
124
        }
1✔
125
}
126

127
func (sc *StackContainer) resourceMeta() metav1.ObjectMeta {
1✔
128
        return sc.objectMeta(false)
1✔
129
}
1✔
130

131
// getServicePorts gets the service ports to be used for the stack service.
132
func getServicePorts(stackSpec zv1.StackSpecInternal, backendPort *intstr.IntOrString) ([]v1.ServicePort, error) {
1✔
133
        var servicePorts []v1.ServicePort
1✔
134
        if stackSpec.StackSpec.Service == nil ||
1✔
135
                len(stackSpec.StackSpec.Service.Ports) == 0 {
2✔
136

1✔
137
                servicePorts = servicePortsFromContainers(
1✔
138
                        stackSpec.StackSpec.PodTemplate.Spec.Containers,
1✔
139
                )
1✔
140
        } else {
2✔
141
                servicePorts = stackSpec.StackSpec.Service.Ports
1✔
142
        }
1✔
143

144
        // validate that one port in the list maps to the backendPort.
145
        if backendPort != nil {
2✔
146
                for _, port := range servicePorts {
2✔
147
                        switch backendPort.Type {
1✔
148
                        case intstr.Int:
1✔
149
                                if port.Port == backendPort.IntVal {
2✔
150
                                        return servicePorts, nil
1✔
151
                                }
1✔
152
                        case intstr.String:
1✔
153
                                if port.Name == backendPort.StrVal {
2✔
154
                                        return servicePorts, nil
1✔
155
                                }
1✔
156
                        }
157
                }
158

159
                return nil, fmt.Errorf("no service ports matching backendPort '%s'", backendPort.String())
1✔
160
        }
161

162
        return servicePorts, nil
1✔
163
}
164

165
// servicePortsFromTemplate gets service port from pod template.
166
func servicePortsFromContainers(containers []v1.Container) []v1.ServicePort {
1✔
167
        ports := make([]v1.ServicePort, 0)
1✔
168
        for i, container := range containers {
2✔
169
                for j, port := range container.Ports {
2✔
170
                        name := fmt.Sprintf("port-%d-%d", i, j)
1✔
171
                        if port.Name != "" {
2✔
172
                                name = port.Name
1✔
173
                        }
1✔
174
                        servicePort := v1.ServicePort{
1✔
175
                                Name:       name,
1✔
176
                                Protocol:   port.Protocol,
1✔
177
                                Port:       port.ContainerPort,
1✔
178
                                TargetPort: intstr.FromInt(int(port.ContainerPort)),
1✔
179
                        }
1✔
180
                        // set default protocol if not specified
1✔
181
                        if servicePort.Protocol == "" {
2✔
182
                                servicePort.Protocol = v1.ProtocolTCP
1✔
183
                        }
1✔
184
                        ports = append(ports, servicePort)
1✔
185
                }
186
        }
187
        return ports
1✔
188
}
189

190
func (sc *StackContainer) selector() map[string]string {
1✔
191
        return limitLabels(sc.Stack.Labels, selectorLabels)
1✔
192
}
1✔
193

194
// GenerateDeployment generates a deployment as configured in the
195
// stack.  On cluster migrations set by stackset annotation
196
// "zalando.org/forward-backend", the deployment will be set to
197
// replicas 1.
198
func (sc *StackContainer) GenerateDeployment() *appsv1.Deployment {
1✔
199

1✔
200
        stack := sc.Stack
1✔
201

1✔
202
        desiredReplicas := sc.stackReplicas
1✔
203
        if sc.prescalingActive {
2✔
204
                desiredReplicas = sc.prescalingReplicas
1✔
205
        }
1✔
206

207
        var updatedReplicas *int32
1✔
208

1✔
209
        if desiredReplicas != 0 && !sc.ScaledDown() {
2✔
210
                // Stack scaled up, rescale the deployment if it's at 0 replicas, or if HPA is unused and we don't run autoscaling
1✔
211
                if sc.deploymentReplicas == 0 || (!sc.IsAutoscaled() && desiredReplicas != sc.deploymentReplicas) {
2✔
212
                        updatedReplicas = wrapReplicas(desiredReplicas)
1✔
213
                }
1✔
214
        } else {
1✔
215
                // Stack scaled down (manually or because it doesn't receive traffic), check if we need to scale down the deployment
1✔
216
                if sc.deploymentReplicas != 0 {
2✔
217
                        updatedReplicas = wrapReplicas(0)
1✔
218
                }
1✔
219
        }
220

221
        if updatedReplicas == nil {
2✔
222
                updatedReplicas = wrapReplicas(sc.deploymentReplicas)
1✔
223
        }
1✔
224

225
        var strategy *appsv1.DeploymentStrategy
1✔
226
        if stack.Spec.StackSpec.Strategy != nil {
2✔
227
                strategy = stack.Spec.StackSpec.Strategy.DeepCopy()
1✔
228
        }
1✔
229

230
        embeddedCopy := stack.Spec.StackSpec.PodTemplate.EmbeddedObjectMeta.DeepCopy()
1✔
231

1✔
232
        templateObjectMeta := metav1.ObjectMeta{
1✔
233
                Annotations: embeddedCopy.Annotations,
1✔
234
                Labels:      embeddedCopy.Labels,
1✔
235
        }
1✔
236

1✔
237
        if _, clusterMigration := sc.Stack.Annotations[forwardBackendAnnotation]; clusterMigration && *updatedReplicas != 0 {
2✔
238
                updatedReplicas = wrapReplicas(1)
1✔
239
                sc.deploymentReplicas = 1
1✔
240
                sc.stackReplicas = 1
1✔
241
        }
1✔
242

243
        deployment := &appsv1.Deployment{
1✔
244
                ObjectMeta: sc.resourceMeta(),
1✔
245
                Spec: appsv1.DeploymentSpec{
1✔
246
                        Replicas:        updatedReplicas,
1✔
247
                        MinReadySeconds: sc.Stack.Spec.StackSpec.MinReadySeconds,
1✔
248
                        Selector: &metav1.LabelSelector{
1✔
249
                                MatchLabels: sc.selector(),
1✔
250
                        },
1✔
251
                        Template: v1.PodTemplateSpec{
1✔
252
                                ObjectMeta: objectMetaInjectLabels(templateObjectMeta, stack.Labels),
1✔
253
                                Spec:       *stack.Spec.StackSpec.PodTemplate.Spec.DeepCopy(),
1✔
254
                        },
1✔
255
                },
1✔
256
        }
1✔
257
        if strategy != nil {
2✔
258
                deployment.Spec.Strategy = *strategy
1✔
259
        }
1✔
260

261
        return deployment
1✔
262
}
263

264
// GenerateHPA generates a hpa as configured in the
265
// stack.  On cluster migrations set by stackset annotation
266
// "zalando.org/forward-backend", the hpa will be set to
267
// minReplicas = maxReplicass = 1.
268
func (sc *StackContainer) GenerateHPA() (
269
        *autoscaling.HorizontalPodAutoscaler,
270
        error,
271
) {
1✔
272
        if _, clusterMigration := sc.Stack.Annotations[forwardBackendAnnotation]; clusterMigration {
2✔
273
                return nil, nil
1✔
274
        }
1✔
275

276
        autoscalerSpec := sc.Stack.Spec.StackSpec.Autoscaler
1✔
277
        trafficWeight := sc.actualTrafficWeight
1✔
278

1✔
279
        if autoscalerSpec == nil {
1✔
UNCOV
280
                return nil, nil
×
281
        }
×
282

283
        if sc.ScaledDown() {
2✔
284
                return nil, nil
1✔
285
        }
1✔
286

287
        result := &autoscaling.HorizontalPodAutoscaler{
1✔
288
                ObjectMeta: sc.resourceMeta(),
1✔
289
                TypeMeta: metav1.TypeMeta{
1✔
290
                        Kind:       "HorizontalPodAutoscaler",
1✔
291
                        APIVersion: "autoscaling/v2",
1✔
292
                },
1✔
293
                Spec: autoscaling.HorizontalPodAutoscalerSpec{
1✔
294
                        ScaleTargetRef: autoscaling.CrossVersionObjectReference{
1✔
295
                                APIVersion: apiVersionAppsV1,
1✔
296
                                Kind:       kindDeployment,
1✔
297
                                Name:       sc.Name(),
1✔
298
                        },
1✔
299
                },
1✔
300
        }
1✔
301

1✔
302
        result.Spec.MinReplicas = autoscalerSpec.MinReplicas
1✔
303
        result.Spec.MaxReplicas = autoscalerSpec.MaxReplicas
1✔
304

1✔
305
        metrics, annotations, err := convertCustomMetrics(
1✔
306
                sc.Name()+SegmentSuffix,
1✔
307
                sc.Name(),
1✔
308
                sc.Namespace(),
1✔
309
                autoscalerMetricsList(autoscalerSpec.Metrics),
1✔
310
                trafficWeight,
1✔
311
        )
1✔
312

1✔
313
        if err != nil {
1✔
UNCOV
314
                return nil, err
×
315
        }
×
316
        result.Spec.Metrics = metrics
1✔
317
        result.Annotations = mergeLabels(result.Annotations, annotations)
1✔
318
        result.Spec.Behavior = autoscalerSpec.Behavior
1✔
319

1✔
320
        // If prescaling is enabled, ensure we have at least `precalingReplicas` pods
1✔
321
        if sc.prescalingActive && (result.Spec.MinReplicas == nil || *result.Spec.MinReplicas < sc.prescalingReplicas) {
1✔
UNCOV
322
                pr := sc.prescalingReplicas
×
323
                result.Spec.MinReplicas = &pr
×
324
        }
×
325

326
        return result, nil
1✔
327
}
328

329
func (sc *StackContainer) GenerateService() (*v1.Service, error) {
1✔
330
        // get service ports to be used for the service
1✔
331
        var backendPort *intstr.IntOrString
1✔
332
        // Ingress or external managed Ingress
1✔
333
        if sc.HasBackendPort() {
1✔
UNCOV
334
                backendPort = sc.backendPort
×
335
        }
×
336

337
        servicePorts, err := getServicePorts(sc.Stack.Spec, backendPort)
1✔
338
        if err != nil {
1✔
UNCOV
339
                return nil, err
×
340
        }
×
341

342
        metaObj := sc.resourceMeta()
1✔
343
        stackSpec := sc.Stack.Spec
1✔
344
        if stackSpec.StackSpec.Service != nil {
2✔
345
                metaObj.Annotations = mergeLabels(
1✔
346
                        metaObj.Annotations,
1✔
347
                        stackSpec.StackSpec.Service.Annotations,
1✔
348
                )
1✔
349
        }
1✔
350
        return &v1.Service{
1✔
351
                ObjectMeta: metaObj,
1✔
352
                Spec: v1.ServiceSpec{
1✔
353
                        Selector: sc.selector(),
1✔
354
                        Type:     v1.ServiceTypeClusterIP,
1✔
355
                        Ports:    servicePorts,
1✔
356
                },
1✔
357
        }, nil
1✔
358
}
359

360
func (sc *StackContainer) stackHostnames(
361
        spec ingressOrRouteGroupSpec,
362
        segment bool,
363
) []string {
1✔
364
        // The Ingress segment uses the original hostnames
1✔
365
        if segment {
2✔
366
                return spec.GetHosts()
1✔
367
        }
1✔
368
        result := sets.NewString()
1✔
369

1✔
370
        // Old-style autogenerated hostnames
1✔
371
        for _, host := range spec.GetHosts() {
2✔
372
                for _, domain := range sc.clusterDomains {
2✔
373
                        if strings.HasSuffix(host, domain) {
2✔
374
                                result.Insert(fmt.Sprintf("%s.%s", sc.Name(), domain))
1✔
375
                        } else {
2✔
376
                                log.Debugf(
1✔
377
                                        "Ingress host: %s suffix did not match cluster-domain %s",
1✔
378
                                        host,
1✔
379
                                        domain,
1✔
380
                                )
1✔
381
                        }
1✔
382
                }
383
        }
384
        return result.List()
1✔
385
}
386

387
func (sc *StackContainer) GenerateIngress() (*networking.Ingress, error) {
1✔
388
        return sc.generateIngress(false)
1✔
389
}
1✔
390

391
func (sc *StackContainer) GenerateIngressSegment() (
392
        *networking.Ingress,
393
        error,
394
) {
1✔
395
        res, err := sc.generateIngress(true)
1✔
396
        if err != nil || res == nil {
2✔
397
                return res, err
1✔
398
        }
1✔
399

400
        // Synchronize annotations specified in the StackSet.
401
        res.Annotations = syncAnnotations(
1✔
402
                res.Annotations,
1✔
403
                sc.syncAnnotationsInIngress,
1✔
404
                sc.ingressAnnotationsToSync,
1✔
405
        )
1✔
406

1✔
407
        if predVal, ok := res.Annotations[IngressPredicateKey]; !ok || predVal == "" {
2✔
408
                res.Annotations = mergeLabels(
1✔
409
                        res.Annotations,
1✔
410
                        map[string]string{IngressPredicateKey: sc.trafficSegment()},
1✔
411
                )
1✔
412
        } else {
2✔
413
                res.Annotations = mergeLabels(
1✔
414
                        res.Annotations,
1✔
415
                        map[string]string{
1✔
416
                                IngressPredicateKey: sc.trafficSegment() + " && " + predVal,
1✔
417
                        },
1✔
418
                )
1✔
419
        }
1✔
420

421
        return res, nil
1✔
422
}
423

424
// generateIngress generates an ingress as configured in the stack.
425
// On cluster migrations set by stackset annotation
426
// "zalando.org/forward-backend", the annotation will be copied to the
427
// ingress.
428
func (sc *StackContainer) generateIngress(segment bool) (
429
        *networking.Ingress,
430
        error,
431
) {
1✔
432

1✔
433
        if !sc.HasBackendPort() || sc.ingressSpec == nil {
2✔
434
                return nil, nil
1✔
435
        }
1✔
436

437
        hostnames := sc.stackHostnames(sc.ingressSpec, segment)
1✔
438
        if len(hostnames) == 0 {
2✔
439
                return nil, nil
1✔
440
        }
1✔
441

442
        rules := make([]networking.IngressRule, 0, len(hostnames))
1✔
443
        for _, hostname := range hostnames {
2✔
444
                rules = append(rules, networking.IngressRule{
1✔
445
                        IngressRuleValue: networking.IngressRuleValue{
1✔
446
                                HTTP: &networking.HTTPIngressRuleValue{
1✔
447
                                        Paths: []networking.HTTPIngressPath{
1✔
448
                                                {
1✔
449
                                                        PathType: &PathTypeImplementationSpecific,
1✔
450
                                                        Path:     sc.ingressSpec.Path,
1✔
451
                                                        Backend: networking.IngressBackend{
1✔
452
                                                                Service: &networking.IngressServiceBackend{
1✔
453
                                                                        Name: sc.Name(),
1✔
454
                                                                        Port: networking.ServiceBackendPort{
1✔
455
                                                                                Name:   sc.backendPort.StrVal,
1✔
456
                                                                                Number: sc.backendPort.IntVal,
1✔
457
                                                                        },
1✔
458
                                                                },
1✔
459
                                                        },
1✔
460
                                                },
1✔
461
                                        },
1✔
462
                                },
1✔
463
                        },
1✔
464
                        Host: hostname,
1✔
465
                })
1✔
466
        }
1✔
467

468
        // sort rules by hostname for a stable order
469
        sort.Slice(rules, func(i, j int) bool {
2✔
470
                return rules[i].Host < rules[j].Host
1✔
471
        })
1✔
472

473
        result := &networking.Ingress{
1✔
474
                ObjectMeta: sc.objectMeta(segment),
1✔
475
                Spec: networking.IngressSpec{
1✔
476
                        Rules: rules,
1✔
477
                },
1✔
478
        }
1✔
479
        if _, clusterMigration := sc.Stack.Annotations[forwardBackendAnnotation]; clusterMigration {
2✔
480
                // see https://opensource.zalando.com/skipper/kubernetes/ingress-usage/#skipper-ingress-annotations
1✔
481
                result.Annotations["zalando.org/skipper-backend"] = "forward"
1✔
482
        }
1✔
483

484
        // insert annotations
485
        result.Annotations = mergeLabels(
1✔
486
                result.Annotations,
1✔
487
                sc.ingressSpec.GetAnnotations(),
1✔
488
        )
1✔
489

1✔
490
        return result, nil
1✔
491
}
492

493
func (sc *StackContainer) GenerateRouteGroup() (*rgv1.RouteGroup, error) {
1✔
494
        return sc.generateRouteGroup(false)
1✔
495
}
1✔
496

497
func (sc *StackContainer) GenerateRouteGroupSegment() (
498
        *rgv1.RouteGroup,
499
        error,
500
) {
1✔
501
        res, err := sc.generateRouteGroup(true)
1✔
502
        if err != nil || res == nil {
2✔
503
                return res, err
1✔
504
        }
1✔
505

506
        // Synchronize annotations specified in the StackSet.
507
        res.Annotations = syncAnnotations(
1✔
508
                res.Annotations,
1✔
509
                sc.syncAnnotationsInRouteGroup,
1✔
510
                sc.ingressAnnotationsToSync,
1✔
511
        )
1✔
512

1✔
513
        segmentedRoutes := []rgv1.RouteGroupRouteSpec{}
1✔
514
        for _, r := range res.Spec.Routes {
2✔
515
                r.Predicates = append(r.Predicates, sc.trafficSegment())
1✔
516
                segmentedRoutes = append(segmentedRoutes, r)
1✔
517
        }
1✔
518
        res.Spec.Routes = segmentedRoutes
1✔
519

1✔
520
        return res, nil
1✔
521
}
522

523
// generateRouteGroup generates an RouteGroup as configured in the
524
// stack.  On cluster migrations set by stackset annotation
525
// "zalando.org/forward-backend", the RouteGroup will be patched by
526
// patchForwardBackend() to execute the migration.
527
func (sc *StackContainer) generateRouteGroup(segment bool) (
528
        *rgv1.RouteGroup,
529
        error,
530
) {
1✔
531
        if !sc.HasBackendPort() || sc.routeGroupSpec == nil {
2✔
532
                return nil, nil
1✔
533
        }
1✔
534

535
        hostnames := sc.stackHostnames(sc.routeGroupSpec, segment)
1✔
536
        if len(hostnames) == 0 {
2✔
537
                return nil, nil
1✔
538
        }
1✔
539

540
        result := &rgv1.RouteGroup{
1✔
541
                ObjectMeta: sc.objectMeta(segment),
1✔
542
                Spec: rgv1.RouteGroupSpec{
1✔
543
                        Hosts: hostnames,
1✔
544
                        Backends: []rgv1.RouteGroupBackend{
1✔
545
                                {
1✔
546
                                        Name:        sc.Name(),
1✔
547
                                        Type:        rgv1.ServiceRouteGroupBackend,
1✔
548
                                        ServiceName: sc.Name(),
1✔
549
                                        ServicePort: sc.backendPort.IntValue(),
1✔
550
                                        Algorithm:   sc.routeGroupSpec.LBAlgorithm,
1✔
551
                                },
1✔
552
                        },
1✔
553
                        DefaultBackends: []rgv1.RouteGroupBackendReference{
1✔
554
                                {
1✔
555
                                        BackendName: sc.Name(),
1✔
556
                                        Weight:      100,
1✔
557
                                },
1✔
558
                        },
1✔
559
                },
1✔
560
        }
1✔
561

1✔
562
        result.Spec.Routes = sc.routeGroupSpec.Routes
1✔
563

1✔
564
        // validate not overlapping with main backend
1✔
565
        for _, backend := range sc.routeGroupSpec.AdditionalBackends {
1✔
UNCOV
566
                if backend.Name == sc.Name() {
×
567
                        return nil, fmt.Errorf("invalid additionalBackend '%s', overlaps with Stack name", backend.Name)
×
568
                }
×
569
                if backend.ServiceName == sc.Name() {
×
570
                        return nil, fmt.Errorf("invalid additionalBackend '%s', serviceName '%s' overlaps with Stack name", backend.Name, backend.ServiceName)
×
571
                }
×
572
                result.Spec.Backends = append(result.Spec.Backends, backend)
×
573
        }
574

575
        // sort backends to ensure have a consistent generated RouteGroup resource
576
        sort.Slice(result.Spec.Backends, func(i, j int) bool {
1✔
UNCOV
577
                return result.Spec.Backends[i].Name < result.Spec.Backends[j].Name
×
578
        })
×
579

580
        // insert annotations
581
        result.Annotations = mergeLabels(
1✔
582
                result.Annotations,
1✔
583
                sc.routeGroupSpec.GetAnnotations(),
1✔
584
        )
1✔
585

1✔
586
        if _, clusterMigration := sc.Stack.Annotations[forwardBackendAnnotation]; clusterMigration {
2✔
587
                patchForwardBackend(&result.Spec)
1✔
588
        }
1✔
589

590
        return result, nil
1✔
591
}
592

593
func (sc *StackContainer) trafficSegment() string {
1✔
594
        return fmt.Sprintf(
1✔
595
                segmentString,
1✔
596
                sc.segmentLowerLimit,
1✔
597
                sc.segmentUpperLimit,
1✔
598
        )
1✔
599
}
1✔
600

601
func (sc *StackContainer) UpdateObjectMeta(objMeta *metav1.ObjectMeta) *metav1.ObjectMeta {
1✔
602
        metaObj := sc.resourceMeta()
1✔
603
        objMeta.OwnerReferences = metaObj.OwnerReferences
1✔
604
        objMeta.Labels = mergeLabels(metaObj.Labels, objMeta.Labels)
1✔
605
        objMeta.Annotations = mergeLabels(metaObj.Annotations, objMeta.Annotations)
1✔
606

1✔
607
        return objMeta
1✔
608
}
1✔
609

610
func (sc *StackContainer) GenerateStackStatus() *zv1.StackStatus {
1✔
611
        prescaling := zv1.PrescalingStatus{}
1✔
612
        if sc.prescalingActive {
2✔
613
                prescaling = zv1.PrescalingStatus{
1✔
614
                        Active:               sc.prescalingActive,
1✔
615
                        Replicas:             sc.prescalingReplicas,
1✔
616
                        DesiredTrafficWeight: sc.prescalingDesiredTrafficWeight,
1✔
617
                        LastTrafficIncrease:  wrapTime(sc.prescalingLastTrafficIncrease),
1✔
618
                }
1✔
619
        }
1✔
620
        return &zv1.StackStatus{
1✔
621
                ActualTrafficWeight:  sc.actualTrafficWeight,
1✔
622
                DesiredTrafficWeight: sc.desiredTrafficWeight,
1✔
623
                Replicas:             sc.createdReplicas,
1✔
624
                ReadyReplicas:        sc.readyReplicas,
1✔
625
                UpdatedReplicas:      sc.updatedReplicas,
1✔
626
                DesiredReplicas:      sc.deploymentReplicas,
1✔
627
                Prescaling:           prescaling,
1✔
628
                NoTrafficSince:       wrapTime(sc.noTrafficSince),
1✔
629
                LabelSelector:        labels.Set(sc.selector()).String(),
1✔
630
        }
1✔
631
}
632

633
func (sc *StackContainer) GeneratePlatformCredentialsSet(pcs *zv1.PCS) (*zv1.PlatformCredentialsSet, error) {
1✔
634
        if pcs.Tokens == nil {
1✔
UNCOV
635
                return nil, fmt.Errorf("platformCredentialsSet has no tokens")
×
636
        }
×
637

638
        metaObj := sc.resourceMeta()
1✔
639
        if _, ok := sc.Stack.Labels["application"]; !ok {
1✔
UNCOV
640
                return nil, fmt.Errorf("stack has no label application")
×
641
        }
×
642
        metaObj.Name = pcs.Name
1✔
643

1✔
644
        result := &zv1.PlatformCredentialsSet{
1✔
645
                ObjectMeta: metaObj,
1✔
646
                TypeMeta: metav1.TypeMeta{
1✔
647
                        Kind:       "PlatformCredentialsSet",
1✔
648
                        APIVersion: "zalando.org/v1",
1✔
649
                },
1✔
650
                Spec: zv1.PlatformCredentialsSpec{
1✔
651
                        Application:  metaObj.Labels["application"],
1✔
652
                        TokenVersion: "v2",
1✔
653
                        Tokens:       pcs.Tokens,
1✔
654
                },
1✔
655
        }
1✔
656

1✔
657
        return result, nil
1✔
658
}
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