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

zalando-incubator / stackset-controller / 20349054833

18 Dec 2025 07:36PM UTC coverage: 49.953% (-0.1%) from 50.085%
20349054833

Pull #721

github

mikkeloscar
Improve forward feature

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
Pull Request #721: Improve forward feature

14 of 30 new or added lines in 3 files covered. (46.67%)

137 existing lines in 3 files now uncovered.

2662 of 5329 relevant lines covered (49.95%)

0.56 hits per line

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

79.85
/controller/stack_resources.go
1
package controller
2

3
import (
4
        "context"
5
        "fmt"
6

7
        rgv1 "github.com/szuecs/routegroup-client/apis/zalando.org/v1"
8
        zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
9
        "github.com/zalando-incubator/stackset-controller/pkg/core"
10
        apps "k8s.io/api/apps/v1"
11
        v2 "k8s.io/api/autoscaling/v2"
12
        apiv1 "k8s.io/api/core/v1"
13
        networking "k8s.io/api/networking/v1"
14
        "k8s.io/apimachinery/pkg/api/equality"
15
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16
        "k8s.io/apimachinery/pkg/types"
17
)
18

19
func pint32Equal(p1, p2 *int32) bool {
1✔
20
        if p1 == nil && p2 == nil {
1✔
21
                return true
×
22
        }
×
23
        if p1 != nil && p2 != nil {
2✔
24
                return *p1 == *p2
1✔
25
        }
1✔
26
        return false
×
27
}
28

29
// syncObjectMeta copies metadata elements such as labels or annotations from source to target
30
func syncObjectMeta(target, source metav1.Object) {
1✔
31
        target.SetLabels(source.GetLabels())
1✔
32
        target.SetAnnotations(source.GetAnnotations())
1✔
33
}
1✔
34

35
// isOwned checks if the resource is owned and returns the UID of the owner.
36
func isOwned(ownerReferences []metav1.OwnerReference) (bool, types.UID) {
1✔
37
        for _, ownerRef := range ownerReferences {
2✔
38
                return true, ownerRef.UID
1✔
39
        }
1✔
40

41
        return false, ""
1✔
42
}
43

44
func (c *StackSetController) ReconcileStackDeployment(ctx context.Context, stack *zv1.Stack, existing *apps.Deployment, generateUpdated func() *apps.Deployment) error {
1✔
45
        deployment := generateUpdated()
1✔
46

1✔
47
        // no deployment
1✔
48
        if deployment == nil {
1✔
NEW
49
                if existing != nil {
×
NEW
50
                        err := c.client.AppsV1().Deployments(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
×
NEW
51
                        if err != nil {
×
NEW
52
                                return err
×
NEW
53
                        }
×
NEW
54
                        c.recorder.Eventf(
×
NEW
55
                                stack,
×
NEW
56
                                apiv1.EventTypeNormal,
×
NEW
57
                                "DeletedDeployment",
×
NEW
58
                                "Deleted Deployment %s",
×
NEW
59
                                existing.Name)
×
60
                }
NEW
61
                return nil
×
62
        }
63

64
        // Create new deployment
65
        if existing == nil {
2✔
66
                _, err := c.client.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
1✔
67
                if err != nil {
1✔
68
                        return err
×
69
                }
×
70
                c.recorder.Eventf(
1✔
71
                        stack,
1✔
72
                        apiv1.EventTypeNormal,
1✔
73
                        "CreatedDeployment",
1✔
74
                        "Created Deployment %s",
1✔
75
                        deployment.Name)
1✔
76
                return nil
1✔
77
        }
78

79
        // Check if we need to update the deployment
80
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) && pint32Equal(existing.Spec.Replicas, deployment.Spec.Replicas) {
2✔
81
                return nil
1✔
82
        }
1✔
83

84
        updated := existing.DeepCopy()
1✔
85
        syncObjectMeta(updated, deployment)
1✔
86
        updated.Spec = deployment.Spec
1✔
87
        updated.Spec.Selector = existing.Spec.Selector
1✔
88

1✔
89
        _, err := c.client.AppsV1().Deployments(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
90
        if err != nil {
1✔
91
                return err
×
92
        }
×
93
        c.recorder.Eventf(
1✔
94
                stack,
1✔
95
                apiv1.EventTypeNormal,
1✔
96
                "UpdatedDeployment",
1✔
97
                "Updated Deployment %s",
1✔
98
                deployment.Name)
1✔
99
        return nil
1✔
100
}
101

102
func (c *StackSetController) ReconcileStackHPA(ctx context.Context, stack *zv1.Stack, existing *v2.HorizontalPodAutoscaler, generateUpdated func() (*v2.HorizontalPodAutoscaler, error)) error {
1✔
103
        hpa, err := generateUpdated()
1✔
104
        if err != nil {
1✔
105
                return err
×
106
        }
×
107

108
        // no HPA
109
        if hpa == nil {
2✔
110
                if existing != nil {
2✔
111
                        err := c.client.AutoscalingV2().HorizontalPodAutoscalers(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
112
                        if err != nil {
1✔
113
                                return err
×
114
                        }
×
115
                        c.recorder.Eventf(
1✔
116
                                stack,
1✔
117
                                apiv1.EventTypeNormal,
1✔
118
                                "DeletedHPA",
1✔
119
                                "Deleted HPA %s",
1✔
120
                                existing.Name)
1✔
121
                }
122
                return nil
1✔
123
        }
124

125
        // Create new HPA
126
        if existing == nil {
2✔
127
                _, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Create(ctx, hpa, metav1.CreateOptions{})
1✔
128
                if err != nil {
1✔
129
                        return err
×
130
                }
×
131
                c.recorder.Eventf(
1✔
132
                        stack,
1✔
133
                        apiv1.EventTypeNormal,
1✔
134
                        "CreatedHPA",
1✔
135
                        "Created HPA %s",
1✔
136
                        hpa.Name)
1✔
137
                return nil
1✔
138
        }
139

140
        // Check if we need to update the HPA
141
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) &&
1✔
142
                pint32Equal(existing.Spec.MinReplicas, hpa.Spec.MinReplicas) &&
1✔
143
                core.AreAnnotationsUpToDate(hpa.ObjectMeta, existing.ObjectMeta) {
2✔
144
                return nil
1✔
145
        }
1✔
146

147
        updated := existing.DeepCopy()
1✔
148
        syncObjectMeta(updated, hpa)
1✔
149
        updated.Spec = hpa.Spec
1✔
150

1✔
151
        _, err = c.client.AutoscalingV2().HorizontalPodAutoscalers(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
152
        if err != nil {
1✔
153
                return err
×
154
        }
×
155
        c.recorder.Eventf(
1✔
156
                stack,
1✔
157
                apiv1.EventTypeNormal,
1✔
158
                "UpdatedHPA",
1✔
159
                "Updated HPA %s",
1✔
160
                hpa.Name)
1✔
161
        return nil
1✔
162
}
163

164
func (c *StackSetController) ReconcileStackService(ctx context.Context, stack *zv1.Stack, existing *apiv1.Service, generateUpdated func() (*apiv1.Service, error)) error {
1✔
165
        service, err := generateUpdated()
1✔
166
        if err != nil {
1✔
167
                return err
×
168
        }
×
169

170
        // Create new service
171
        if existing == nil {
2✔
172
                _, err := c.client.CoreV1().Services(service.Namespace).Create(ctx, service, metav1.CreateOptions{})
1✔
173
                if err != nil {
1✔
174
                        return err
×
175
                }
×
176
                c.recorder.Eventf(
1✔
177
                        stack,
1✔
178
                        apiv1.EventTypeNormal,
1✔
179
                        "CreatedService",
1✔
180
                        "Created Service %s",
1✔
181
                        service.Name)
1✔
182
                return nil
1✔
183
        }
184

185
        // Check if we need to update the service
186
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
187
                return nil
1✔
188
        }
1✔
189

190
        updated := existing.DeepCopy()
1✔
191
        syncObjectMeta(updated, service)
1✔
192
        updated.Spec = service.Spec
1✔
193
        updated.Spec.ClusterIP = existing.Spec.ClusterIP // ClusterIP is immutable
1✔
194

1✔
195
        _, err = c.client.CoreV1().Services(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
196
        if err != nil {
1✔
197
                return err
×
198
        }
×
199
        c.recorder.Eventf(
1✔
200
                stack,
1✔
201
                apiv1.EventTypeNormal,
1✔
202
                "UpdatedService",
1✔
203
                "Updated Service %s",
1✔
204
                service.Name)
1✔
205
        return nil
1✔
206
}
207

208
func (c *StackSetController) ReconcileStackIngress(ctx context.Context, stack *zv1.Stack, existing *networking.Ingress, generateUpdated func() (*networking.Ingress, error)) error {
1✔
209
        ingress, err := generateUpdated()
1✔
210
        if err != nil {
1✔
211
                return err
×
212
        }
×
213

214
        // Ingress removed
215
        if ingress == nil {
2✔
216
                if existing != nil {
2✔
217
                        err := c.client.NetworkingV1().Ingresses(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
218
                        if err != nil {
1✔
219
                                return err
×
220
                        }
×
221
                        c.recorder.Eventf(
1✔
222
                                stack,
1✔
223
                                apiv1.EventTypeNormal,
1✔
224
                                "DeletedIngress",
1✔
225
                                "Deleted Ingress %s",
1✔
226
                                existing.Namespace)
1✔
227
                }
228
                return nil
1✔
229
        }
230

231
        // Create new Ingress
232
        if existing == nil {
2✔
233
                _, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
1✔
234
                if err != nil {
1✔
235
                        return err
×
236
                }
×
237
                c.recorder.Eventf(
1✔
238
                        stack,
1✔
239
                        apiv1.EventTypeNormal,
1✔
240
                        "CreatedIngress",
1✔
241
                        "Created Ingress %s",
1✔
242
                        ingress.Name)
1✔
243
                return nil
1✔
244
        }
245

246
        // Check if we need to update the Ingress
247
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) &&
1✔
248
                core.AreAnnotationsUpToDate(ingress.ObjectMeta, existing.ObjectMeta) {
2✔
249

1✔
250
                return nil
1✔
251
        }
1✔
252

253
        updated := existing.DeepCopy()
1✔
254
        syncObjectMeta(updated, ingress)
1✔
255
        updated.Spec = ingress.Spec
1✔
256

1✔
257
        _, err = c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
258
        if err != nil {
1✔
259
                return err
×
260
        }
×
261
        c.recorder.Eventf(
1✔
262
                stack,
1✔
263
                apiv1.EventTypeNormal,
1✔
264
                "UpdatedIngress",
1✔
265
                "Updated Ingress %s",
1✔
266
                ingress.Name)
1✔
267
        return nil
1✔
268
}
269

270
func (c *StackSetController) ReconcileStackRouteGroup(ctx context.Context, stack *zv1.Stack, existing *rgv1.RouteGroup, generateUpdated func() (*rgv1.RouteGroup, error)) error {
1✔
271
        routegroup, err := generateUpdated()
1✔
272
        if err != nil {
1✔
273
                return err
×
274
        }
×
275

276
        // RouteGroup removed
277
        if routegroup == nil {
2✔
278
                if existing != nil {
2✔
279
                        err := c.client.RouteGroupV1().RouteGroups(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
280
                        if err != nil {
1✔
281
                                return err
×
282
                        }
×
283
                        c.recorder.Eventf(
1✔
284
                                stack,
1✔
285
                                apiv1.EventTypeNormal,
1✔
286
                                "DeletedRouteGroup",
1✔
287
                                "Deleted RouteGroup %s",
1✔
288
                                existing.Namespace)
1✔
289
                }
290
                return nil
1✔
291
        }
292

293
        // Create new RouteGroup
294
        if existing == nil {
2✔
295
                _, err := c.client.RouteGroupV1().RouteGroups(routegroup.Namespace).Create(ctx, routegroup, metav1.CreateOptions{})
1✔
296
                if err != nil {
1✔
297
                        return err
×
298
                }
×
299
                c.recorder.Eventf(
1✔
300
                        stack,
1✔
301
                        apiv1.EventTypeNormal,
1✔
302
                        "CreatedRouteGroup",
1✔
303
                        "Created RouteGroup %s",
1✔
304
                        routegroup.Name)
1✔
305
                return nil
1✔
306
        }
307

308
        // Check if we need to update the RouteGroup
309
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) &&
1✔
310
                equality.Semantic.DeepEqual(routegroup.Spec, existing.Spec) &&
1✔
311
                core.AreAnnotationsUpToDate(
1✔
312
                        routegroup.ObjectMeta,
1✔
313
                        existing.ObjectMeta,
1✔
314
                ) {
2✔
315

1✔
316
                return nil
1✔
317
        }
1✔
318

319
        updated := existing.DeepCopy()
1✔
320
        syncObjectMeta(updated, routegroup)
1✔
321
        updated.Spec = routegroup.Spec
1✔
322

1✔
323
        _, err = c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
324
        if err != nil {
1✔
325
                return err
×
326
        }
×
327
        c.recorder.Eventf(
1✔
328
                stack,
1✔
329
                apiv1.EventTypeNormal,
1✔
330
                "UpdatedRouteGroup",
1✔
331
                "Updated RouteGroup %s",
1✔
332
                routegroup.Name)
1✔
333
        return nil
1✔
334
}
335

336
// ReconcileStackConfigMapRefs will update the named user-provided ConfigMaps to be
337
// attached to the Stack by ownerReferences, when a list of Configuration
338
// Resources are defined on the Stack template.
339
//
340
// The provided ConfigMap name must be prefixed by the Stack name.
341
// eg: Stack: myapp-v1 ConfigMap: myapp-v1-my-config
342
//
343
// User update of running versioned ConfigMaps is not encouraged but is allowed
344
// on consideration of emergency needs. Similarly, addition of ConfigMaps to
345
// running resources is also allowed, so the method checks for changes on the
346
// ConfigurationResources to ensure all listed ConfigMaps are properly linked
347
// to the Stack.
348
func (c *StackSetController) ReconcileStackConfigMapRefs(
349
        ctx context.Context,
350
        stack *zv1.Stack,
351
        updateObjMeta func(*metav1.ObjectMeta) *metav1.ObjectMeta,
352
) error {
1✔
353
        for _, rsc := range stack.Spec.ConfigurationResources {
2✔
354
                if !rsc.IsConfigMapRef() {
1✔
355
                        continue
×
356
                }
357

358
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
2✔
359
                        return err
1✔
360
                }
1✔
361

362
                if err := c.ReconcileStackConfigMapRef(ctx, stack, rsc, updateObjMeta); err != nil {
1✔
363
                        return err
×
364
                }
×
365
        }
366

367
        return nil
1✔
368
}
369

370
func (c *StackSetController) ReconcileStackConfigMapRef(
371
        ctx context.Context,
372
        stack *zv1.Stack,
373
        rsc zv1.ConfigurationResourcesSpec,
374
        updateObjMeta func(*metav1.ObjectMeta) *metav1.ObjectMeta,
375
) error {
1✔
376
        configMap, err := c.client.CoreV1().ConfigMaps(stack.Namespace).
1✔
377
                Get(ctx, rsc.GetName(), metav1.GetOptions{})
1✔
378
        if err != nil {
1✔
379
                return err
×
380
        }
×
381

382
        // Check if the ConfigMap is already owned by us or another resource.
383
        isOwned, owner := isOwned(configMap.OwnerReferences)
1✔
384
        if isOwned {
2✔
385
                // If the ConfigMap is already owned by us, we don't need to do anything.
1✔
386
                if owner == stack.UID {
2✔
387
                        return nil
1✔
388
                }
1✔
389

390
                // If the ConfigMap is owned by another resource, we should not update it.
391
                return fmt.Errorf("ConfigMap already owned by other resource. "+
×
392
                        "ConfigMap: %s, Stack: %s", rsc.GetName(), stack.Name)
×
393
        }
394

395
        objectMeta := updateObjMeta(&configMap.ObjectMeta)
1✔
396
        configMap.ObjectMeta = *objectMeta
1✔
397

1✔
398
        _, err = c.client.CoreV1().ConfigMaps(configMap.Namespace).
1✔
399
                Update(ctx, configMap, metav1.UpdateOptions{})
1✔
400
        if err != nil {
1✔
401
                return err
×
402
        }
×
403

404
        c.recorder.Eventf(
1✔
405
                stack,
1✔
406
                apiv1.EventTypeNormal,
1✔
407
                "UpdatedConfigMap",
1✔
408
                "Updated ConfigMap %s",
1✔
409
                configMap.Name,
1✔
410
        )
1✔
411

1✔
412
        return nil
1✔
413
}
414

415
// ReconcileStackSecretRefs will update the named user-provided Secrets to be
416
// attached to the Stack by ownerReferences, when a list of Configuration
417
// Resources are defined on the Stack template.
418
//
419
// The provided Secret name must be prefixed by the Stack name.
420
// eg: Stack: myapp-v1 Secret: myapp-v1-my-secret
421
//
422
// User update of running versioned Secrets is not encouraged but is allowed
423
// on consideration of emergency needs. Similarly, addition of Secrets to
424
// running resources is also allowed, so the method checks for changes on the
425
// ConfigurationResources to ensure all listed Secrets are properly linked
426
// to the Stack.
427
func (c *StackSetController) ReconcileStackSecretRefs(
428
        ctx context.Context,
429
        stack *zv1.Stack,
430
        updateObjMeta func(*metav1.ObjectMeta) *metav1.ObjectMeta,
431
) error {
1✔
432
        for _, rsc := range stack.Spec.ConfigurationResources {
2✔
433
                if !rsc.IsSecretRef() {
2✔
434
                        continue
1✔
435
                }
436

437
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
1✔
438
                        return err
×
439
                }
×
440

441
                if err := c.ReconcileStackSecretRef(ctx, stack, rsc, updateObjMeta); err != nil {
1✔
442
                        return err
×
443
                }
×
444
        }
445

446
        return nil
1✔
447
}
448

449
func (c *StackSetController) ReconcileStackSecretRef(ctx context.Context,
450
        stack *zv1.Stack,
451
        rsc zv1.ConfigurationResourcesSpec,
452
        updateObjMeta func(*metav1.ObjectMeta) *metav1.ObjectMeta,
453
) error {
1✔
454
        secret, err := c.client.CoreV1().Secrets(stack.Namespace).
1✔
455
                Get(ctx, rsc.GetName(), metav1.GetOptions{})
1✔
456
        if err != nil {
1✔
457
                return err
×
458
        }
×
459

460
        // Check if the Secret is already owned by us or another resource.
461
        isOwned, owner := isOwned(secret.OwnerReferences)
1✔
462
        if isOwned {
2✔
463
                // If the Secret is already owned by us, we don't need to do anything.
1✔
464
                if owner == stack.UID {
2✔
465
                        return nil
1✔
466
                }
1✔
467

468
                // If the Secret is owned by another resource, we should not update it.
469
                return fmt.Errorf("secret already owned by other resource. "+
×
470
                        "Secret: %s, Stack: %s", rsc.GetName(), stack.Name)
×
471
        }
472

473
        objectMeta := updateObjMeta(&secret.ObjectMeta)
1✔
474
        secret.ObjectMeta = *objectMeta
1✔
475

1✔
476
        _, err = c.client.CoreV1().Secrets(secret.Namespace).
1✔
477
                Update(ctx, secret, metav1.UpdateOptions{})
1✔
478
        if err != nil {
1✔
479
                return err
×
480
        }
×
481

482
        c.recorder.Eventf(
1✔
483
                stack,
1✔
484
                apiv1.EventTypeNormal,
1✔
485
                "UpdatedSecret",
1✔
486
                "Updated Secret %s",
1✔
487
                secret.Name,
1✔
488
        )
1✔
489

1✔
490
        return nil
1✔
491
}
492

493
func (c *StackSetController) ReconcileStackPlatformCredentialsSets(
494
        ctx context.Context,
495
        stack *zv1.Stack,
496
        existing []*zv1.PlatformCredentialsSet,
497
        generateUpdated func(*zv1.PCS) (*zv1.PlatformCredentialsSet, error),
498
) error {
1✔
499
        for _, rsc := range stack.Spec.ConfigurationResources {
2✔
500
                if !rsc.IsPlatformCredentialsSet() {
1✔
501
                        continue
×
502
                }
503

504
                if err := c.ReconcileStackPlatformCredentialsSet(
1✔
505
                        ctx, stack, rsc.PlatformCredentialsSet, existing, generateUpdated); err != nil {
1✔
506
                        return err
×
507
                }
×
508
        }
509
        return nil
1✔
510
}
511

512
func (c *StackSetController) ReconcileStackPlatformCredentialsSet(
513
        ctx context.Context,
514
        stack *zv1.Stack,
515
        rsc *zv1.PCS,
516
        existing []*zv1.PlatformCredentialsSet,
517
        generateUpdated func(*zv1.PCS) (*zv1.PlatformCredentialsSet, error),
518
) error {
1✔
519
        pcs, err := generateUpdated(rsc)
1✔
520
        if err != nil {
1✔
521
                return err
×
522
        }
×
523

524
        // Check if a PlatformCredentialsSet needs update
525
        for _, e := range existing {
2✔
526
                if pcs.Name != e.Name {
2✔
527
                        continue
1✔
528
                }
529

530
                if core.IsResourceUpToDate(stack, e.ObjectMeta) &&
1✔
531
                        equality.Semantic.DeepEqual(pcs.Spec, e.Spec) &&
1✔
532
                        core.AreAnnotationsUpToDate(pcs.ObjectMeta, e.ObjectMeta) {
1✔
533
                        return nil
×
534
                }
×
535

536
                updated := e.DeepCopy()
1✔
537
                syncObjectMeta(updated, pcs)
1✔
538
                updated.Spec = pcs.Spec
1✔
539

1✔
540
                _, err := c.client.ZalandoV1().PlatformCredentialsSets(updated.Namespace).
1✔
541
                        Update(ctx, updated, metav1.UpdateOptions{})
1✔
542
                if err != nil {
1✔
543
                        return err
×
544
                }
×
545
                c.recorder.Eventf(
1✔
546
                        stack,
1✔
547
                        apiv1.EventTypeNormal,
1✔
548
                        "UpdatedPlatformCredentialsSet",
1✔
549
                        "Updated PlatformCredentialsSet %s",
1✔
550
                        pcs.Name,
1✔
551
                )
1✔
552
                return nil
1✔
553
        }
554

555
        // Create new PlatformCredentialsSet
556
        _, err = c.client.ZalandoV1().PlatformCredentialsSets(pcs.Namespace).
1✔
557
                Create(ctx, pcs, metav1.CreateOptions{})
1✔
558
        if err != nil {
1✔
559
                return err
×
560
        }
×
561
        c.recorder.Eventf(
1✔
562
                stack,
1✔
563
                apiv1.EventTypeNormal,
1✔
564
                "CreatedPlatformCredentialsSet",
1✔
565
                "Created PlatformCredentialsSet %s",
1✔
566
                pcs.Name,
1✔
567
        )
1✔
568

1✔
569
        return nil
1✔
570
}
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