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

opendefensecloud / artifact-conduit / 19705097266

26 Nov 2025 01:19PM UTC coverage: 60.219% (-1.1%) from 61.352%
19705097266

Pull #94

github

web-flow
Merge dbe584ca1 into f3a6c2f69
Pull Request #94: Feature/87 workflow template ref refactor

38 of 38 new or added lines in 3 files covered. (100.0%)

6 existing lines in 2 files now uncovered.

551 of 915 relevant lines covered (60.22%)

952.19 hits per line

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

79.24
/pkg/controller/order_controller.go
1
// Copyright 2025 BWI GmbH and Artifact Conduit contributors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package controller
5

6
import (
7
        "context"
8
        "crypto/sha256"
9
        "encoding/hex"
10
        "encoding/json"
11
        "fmt"
12
        "slices"
13
        "time"
14

15
        "github.com/go-logr/logr"
16
        arcv1alpha1 "go.opendefense.cloud/arc/api/arc/v1alpha1"
17
        corev1 "k8s.io/api/core/v1"
18
        apierrors "k8s.io/apimachinery/pkg/api/errors"
19
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20
        "k8s.io/apimachinery/pkg/runtime"
21
        "k8s.io/client-go/tools/record"
22
        ctrl "sigs.k8s.io/controller-runtime"
23
        "sigs.k8s.io/controller-runtime/pkg/client"
24
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
25
)
26

27
const (
28
        orderFinalizer = "arc.bwi.de/order-finalizer"
29
)
30

31
// OrderReconciler reconciles a Order object
32
type OrderReconciler struct {
33
        client.Client
34
        Scheme   *runtime.Scheme
35
        Recorder record.EventRecorder
36
}
37

38
type desiredAW struct {
39
        index       int
40
        objectMeta  metav1.ObjectMeta
41
        artifact    *arcv1alpha1.OrderArtifact
42
        typeSpec    *arcv1alpha1.ArtifactTypeSpec
43
        srcEndpoint *arcv1alpha1.Endpoint
44
        dstEndpoint *arcv1alpha1.Endpoint
45
        srcSecret   *corev1.Secret
46
        dstSecret   *corev1.Secret
47
        sha         string
48
}
49

50
//+kubebuilder:rbac:groups=arc.bwi.de,resources=endpoints,verbs=get;list;watch
51
//+kubebuilder:rbac:groups=arc.bwi.de,resources=artifacttypes,verbs=get;list;watch
52
//+kubebuilder:rbac:groups=arc.bwi.de,resources=clusterartifacttypes,verbs=get;list;watch
53
//+kubebuilder:rbac:groups=arc.bwi.de,resources=artifactworkflows,verbs=get;list;watch;create;update;patch;delete
54
//+kubebuilder:rbac:groups=arc.bwi.de,resources=orders,verbs=get;list;watch;create;update;patch;delete
55
//+kubebuilder:rbac:groups=arc.bwi.de,resources=orders/status,verbs=get;update;patch
56
//+kubebuilder:rbac:groups=arc.bwi.de,resources=orders/finalizers,verbs=update
57
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
58
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
59

60
// Reconcile moves the current state of the cluster closer to the desired state
61
func (r *OrderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
2,563✔
62
        log := ctrl.LoggerFrom(ctx)
2,563✔
63

2,563✔
64
        // Fetch the Order instance
2,563✔
65
        order := &arcv1alpha1.Order{}
2,563✔
66
        if err := r.Get(ctx, req.NamespacedName, order); err != nil {
2,565✔
67
                if apierrors.IsNotFound(err) {
4✔
68
                        // Object not found, return. Created objects are automatically garbage collected.
2✔
69
                        return ctrl.Result{}, nil
2✔
70
                }
2✔
71
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get object")
×
72
        }
73

74
        // Handle deletion: cleanup artifact workflows, then remove finalizer
75
        if !order.DeletionTimestamp.IsZero() {
2,563✔
76
                log.V(1).Info("Order is being deleted")
2✔
77
                r.Recorder.Event(order, corev1.EventTypeWarning, "Deleting", "Order is being deleted, cleaning up artifact workflows")
2✔
78

2✔
79
                // Cleanup all artifact workflows
2✔
80
                if len(order.Status.ArtifactWorkflows) > 0 {
3✔
81
                        for sha := range order.Status.ArtifactWorkflows {
3✔
82
                                // Remove Secret and ArtifactWorkflow
2✔
83
                                aw := &arcv1alpha1.ArtifactWorkflow{
2✔
84
                                        ObjectMeta: awObjectMeta(order, sha),
2✔
85
                                }
2✔
86
                                _ = r.Delete(ctx, aw) // Ignore errors
2✔
87
                                delete(order.Status.ArtifactWorkflows, sha)
2✔
88
                        }
2✔
89
                        if err := r.Status().Update(ctx, order); err != nil {
1✔
90
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to update order status")
×
91
                        }
×
92
                        log.V(1).Info("Order artifact workflows cleaned up")
1✔
93

1✔
94
                        // Requeue until all artifact workflows are gone
1✔
95
                        return ctrl.Result{}, nil
1✔
96
                }
97
                // All artifact workflows are gone, remove finalizer
98
                if slices.Contains(order.Finalizers, orderFinalizer) {
2✔
99
                        log.V(1).Info("No artifact workflows, removing finalizer from Order")
1✔
100
                        order.Finalizers = slices.DeleteFunc(order.Finalizers, func(f string) bool {
2✔
101
                                return f == orderFinalizer
1✔
102
                        })
1✔
103
                        if err := r.Update(ctx, order); err != nil {
1✔
104
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to remove finalizer")
×
105
                        }
×
106
                }
107
                return ctrl.Result{}, nil
1✔
108
        }
109

110
        // Add finalizer if not present and not deleting
111
        if order.DeletionTimestamp.IsZero() {
5,118✔
112
                if !slices.Contains(order.Finalizers, orderFinalizer) {
2,570✔
113
                        log.V(1).Info("Adding finalizer to Order")
11✔
114
                        order.Finalizers = append(order.Finalizers, orderFinalizer)
11✔
115
                        if err := r.Update(ctx, order); err != nil {
11✔
116
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to add finalizer")
×
117
                        }
×
118
                        // Return without requeue; the Update event will trigger reconciliation again
119
                        return ctrl.Result{}, nil
11✔
120
                }
121
        }
122

123
        // Make sure status is initialized
124
        if order.Status.ArtifactWorkflows == nil {
2,570✔
125
                order.Status.ArtifactWorkflows = map[string]arcv1alpha1.OrderArtifactWorkflowStatus{}
22✔
126
        }
22✔
127

128
        // Before we compare to our status, let's fetch all necessary information
129
        // to compute desired state:
130
        desiredAWs := map[string]desiredAW{}
2,548✔
131
        for i, artifact := range order.Spec.Artifacts {
6,738✔
132
                daw, err := r.computeDesiredAW(ctx, log, order, &artifact, i)
4,190✔
133
                if err != nil {
4,212✔
134
                        r.Recorder.Event(order, corev1.EventTypeWarning, "ComputationFailed", fmt.Sprintf("Failed to compute desired artifact workflow for artifact index %d: %v", i, err))
22✔
135
                        order.Status.Message = fmt.Sprintf("Failed to compute desired artifact workflow for artifact index %d: %v", i, err)
22✔
136
                        if err := r.Status().Update(ctx, order); err != nil {
22✔
137
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to update status")
×
138
                        }
×
139
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to compute desired artifact workflow")
22✔
140
                }
141
                desiredAWs[daw.sha] = *daw
4,168✔
142
        }
143

144
        // List missing artifact workflows
145
        createAWs := []string{}
2,526✔
146
        for sha := range desiredAWs {
6,691✔
147
                _, exists := order.Status.ArtifactWorkflows[sha]
4,165✔
148
                if exists {
8,310✔
149
                        continue
4,145✔
150
                }
151
                createAWs = append(createAWs, sha)
20✔
152
        }
153

154
        // Find obsolete artifact workflows
155
        deleteAWs := []string{}
2,526✔
156
        for sha := range order.Status.ArtifactWorkflows {
6,672✔
157
                _, exists := desiredAWs[sha]
4,146✔
158
                if exists {
8,291✔
159
                        continue
4,145✔
160
                }
161
                deleteAWs = append(deleteAWs, sha)
1✔
162
        }
163

164
        // Find finished artifact workflows to clean up
165
        finishedAWs := []string{}
2,526✔
166
        for sha := range order.Status.ArtifactWorkflows {
6,672✔
167
                awStatus := order.Status.ArtifactWorkflows[sha]
4,146✔
168

4,146✔
169
                // Only consider succeeded workflows for TTL cleanup
4,146✔
170
                if awStatus.Phase != arcv1alpha1.WorkflowSucceeded {
8,188✔
171
                        continue
4,042✔
172
                }
173

174
                // If TTL is set, check if it has expired
175
                if order.Spec.TTLSecondsAfterCompletion != nil && *order.Spec.TTLSecondsAfterCompletion > 0 {
104✔
176
                        if time.Since(awStatus.CompletionTime.Time) > time.Duration(*order.Spec.TTLSecondsAfterCompletion)*time.Second {
×
177
                                finishedAWs = append(finishedAWs, sha)
×
178
                        }
×
179
                        continue
×
180
                }
181

182
                // No TTL set, cleanup immediately
183
                finishedAWs = append(finishedAWs, sha)
104✔
184
        }
185

186
        // Create missing artifact workflows
187
        for _, sha := range createAWs {
2,546✔
188
                daw := desiredAWs[sha]
20✔
189
                aw, err := r.hydrateArtifactWorkflow(&daw)
20✔
190
                if err != nil {
20✔
191
                        r.Recorder.Event(order, corev1.EventTypeWarning, "HydrationFailed", fmt.Sprintf("Failed to hydrate artifact workflow for artifact index %d: %v", daw.index, err))
×
192
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to hydrate artifact workflow")
×
193
                }
×
194

195
                // Set owner references
196
                if err := controllerutil.SetControllerReference(order, aw, r.Scheme); err != nil {
20✔
197
                        r.Recorder.Event(order, corev1.EventTypeWarning, "HydrationFailed", fmt.Sprintf("Failed to set controller reference for artifact workflow: %v", err))
×
198
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to set controller reference")
×
199
                }
×
200

201
                // Create artifact workflow
202
                if err := r.Create(ctx, aw); err != nil {
22✔
203
                        if apierrors.IsAlreadyExists(err) {
4✔
204
                                // Already created by a previous reconcile — that's fine
2✔
205
                                continue
2✔
206
                        }
207
                        r.Recorder.Event(order, corev1.EventTypeWarning, "CreationFailed", fmt.Sprintf("Failed to create artifact workflow for artifact index %d: %v", daw.index, err))
×
208
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to create artifact workflow")
×
209
                }
210

211
                // Update status
212
                order.Status.ArtifactWorkflows[sha] = arcv1alpha1.OrderArtifactWorkflowStatus{
18✔
213
                        ArtifactIndex: daw.index,
18✔
214
                        Phase:         arcv1alpha1.WorkflowUnknown,
18✔
215
                }
18✔
216

18✔
217
                r.Recorder.Event(order, corev1.EventTypeNormal, "ArtifactWorkflowCreated", fmt.Sprintf("Created artifact workflow '%s' for artifact index %d", aw.Name, daw.index))
18✔
218
                log.V(1).Info("Created artifact workflow", "artifactWorkflow", aw.Name)
18✔
219
        }
220

221
        // Delete obsolete artifact workflows
222
        for _, sha := range deleteAWs {
2,527✔
223
                // Does not exist anymore, let's clean up!
1✔
224
                if err := r.Delete(ctx, &arcv1alpha1.ArtifactWorkflow{
1✔
225
                        ObjectMeta: awObjectMeta(order, sha),
1✔
226
                }); client.IgnoreNotFound(err) != nil {
1✔
227
                        r.Recorder.Event(order, corev1.EventTypeWarning, "DeletionFailed", fmt.Sprintf("Failed to delete obsolete artifact workflow '%s': %v", sha, err))
×
228
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
229
                }
×
230

231
                // Update status
232
                delete(order.Status.ArtifactWorkflows, sha)
1✔
233
                log.V(1).Info("Deleted obsolete artifact workflow", "artifactWorkflow", sha)
1✔
234
                r.Recorder.Event(order, corev1.EventTypeNormal, "ArtifactWorkflowDeleted", fmt.Sprintf("Deleted obsolete artifact workflow '%s'", sha))
1✔
235
        }
236

237
        // Delete finished artifact workflows
238
        for _, sha := range finishedAWs {
2,630✔
239
                // Finished, let's clean up!
104✔
240
                if err := r.Delete(ctx, &arcv1alpha1.ArtifactWorkflow{
104✔
241
                        ObjectMeta: awObjectMeta(order, sha),
104✔
242
                }); client.IgnoreNotFound(err) != nil {
104✔
243
                        r.Recorder.Event(order, corev1.EventTypeWarning, "DeletionFailed", fmt.Sprintf("Failed to delete finished artifact workflow '%s': %v", sha, err))
×
244
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
245
                }
×
246

247
                log.V(1).Info("Deleted finished artifact workflow", "artifactWorkflow", sha)
104✔
248
                r.Recorder.Event(order, corev1.EventTypeNormal, "ArtifactWorkflowDeleted", fmt.Sprintf("Deleted finished artifact workflow '%s'", sha))
104✔
249
        }
250

251
        anyPhaseChanged := false
2,526✔
252
        for sha, daw := range desiredAWs {
6,691✔
253
                if slices.Contains(createAWs, sha) {
4,185✔
254
                        // If it was just created we skip the update
20✔
255
                        continue
20✔
256
                }
257
                if order.Status.ArtifactWorkflows[sha].Phase.Completed() {
4,249✔
258
                        // We do not need to check for updates if the workflow is completed
104✔
259
                        continue
104✔
260
                }
261
                aw := arcv1alpha1.ArtifactWorkflow{}
4,041✔
262
                if err := r.Get(ctx, namespacedName(daw.objectMeta.Namespace, daw.objectMeta.Name), &aw); err != nil {
4,041✔
UNCOV
263
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get artifact workflow")
×
UNCOV
264
                }
×
265
                if order.Status.ArtifactWorkflows[sha].Phase != aw.Status.Phase {
5,663✔
266
                        awStatus := order.Status.ArtifactWorkflows[sha]
1,622✔
267
                        awStatus.Phase = aw.Status.Phase
1,622✔
268
                        awStatus.CompletionTime = aw.Status.CompletionTime
1,622✔
269
                        order.Status.ArtifactWorkflows[sha] = awStatus
1,622✔
270
                        anyPhaseChanged = true
1,622✔
271
                }
1,622✔
272
        }
273

274
        // Update status
275
        if len(createAWs) > 0 || len(deleteAWs) > 0 || anyPhaseChanged {
4,121✔
276
                log.V(1).Info("Updating order status")
1,595✔
277
                // Make sure ArtifactIndex is up to date
1,595✔
278
                for sha, daw := range desiredAWs {
4,237✔
279
                        aws := order.Status.ArtifactWorkflows[sha]
2,642✔
280
                        aws.ArtifactIndex = daw.index
2,642✔
281
                        order.Status.ArtifactWorkflows[sha] = aws
2,642✔
282
                }
2,642✔
283
                if err := r.Status().Update(ctx, order); err != nil {
1,638✔
284
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to update status")
43✔
285
                }
43✔
286
        }
287

288
        return ctrl.Result{}, nil
2,483✔
289
}
290

291
func (r *OrderReconciler) hydrateArtifactWorkflow(daw *desiredAW) (*arcv1alpha1.ArtifactWorkflow, error) {
20✔
292
        params, err := dawToParameters(daw)
20✔
293
        if err != nil {
20✔
294
                return nil, err
×
295
        }
×
296

297
        // Next we create the ArtifactWorkflow instance
298
        aw := &arcv1alpha1.ArtifactWorkflow{
20✔
299
                ObjectMeta: daw.objectMeta,
20✔
300
                Spec: arcv1alpha1.ArtifactWorkflowSpec{
20✔
301
                        WorkflowTemplateRef: daw.typeSpec.WorkflowTemplateRef,
20✔
302
                        Parameters:          params,
20✔
303
                        SrcSecretRef:        daw.srcEndpoint.Spec.SecretRef,
20✔
304
                        DstSecretRef:        daw.dstEndpoint.Spec.SecretRef,
20✔
305
                },
20✔
306
        }
20✔
307

20✔
308
        return aw, nil
20✔
309
}
310

311
func (r *OrderReconciler) computeDesiredAW(ctx context.Context, log logr.Logger, order *arcv1alpha1.Order, artifact *arcv1alpha1.OrderArtifact, i int) (*desiredAW, error) {
4,190✔
312
        log = log.WithValues("artifactIndex", i)
4,190✔
313

4,190✔
314
        // We need the referenced src- and dst-endpoints for the artifact
4,190✔
315
        srcRefName := artifact.SrcRef.Name
4,190✔
316
        if srcRefName == "" {
4,712✔
317
                srcRefName = order.Spec.Defaults.SrcRef.Name
522✔
318
        }
522✔
319
        dstRefName := artifact.DstRef.Name
4,190✔
320
        if dstRefName == "" {
4,877✔
321
                dstRefName = order.Spec.Defaults.DstRef.Name
687✔
322
        }
687✔
323
        srcEndpoint := &arcv1alpha1.Endpoint{}
4,190✔
324
        if err := r.Get(ctx, namespacedName(order.Namespace, srcRefName), srcEndpoint); err != nil {
4,190✔
325
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Failed to fetch source endpoint '%s': %v", srcRefName, err))
×
326
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for source")
×
327
        }
×
328
        dstEndpoint := &arcv1alpha1.Endpoint{}
4,190✔
329
        if err := r.Get(ctx, namespacedName(order.Namespace, dstRefName), dstEndpoint); err != nil {
4,190✔
330
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Failed to fetch destination endpoint '%s': %v", dstRefName, err))
×
331
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for destination")
×
332
        }
×
333

334
        // Validate that the endpoint usage is correct
335
        if srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePullOnly && srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
4,190✔
336
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with source usage", srcEndpoint.Name, srcEndpoint.Spec.Usage)
×
337
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Source endpoint '%s' has incompatible usage '%s'", srcEndpoint.Name, srcEndpoint.Spec.Usage))
×
338
                return nil, errLogAndWrap(log, err, "artifact validation failed")
×
339
        }
×
340
        if dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePushOnly && dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
4,190✔
341
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with destination usage", dstEndpoint.Name, dstEndpoint.Spec.Usage)
×
342
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Destination endpoint '%s' has incompatible usage '%s'", dstEndpoint.Name, dstEndpoint.Spec.Usage))
×
343
                return nil, errLogAndWrap(log, err, "artifact validation failed")
×
344
        }
×
345

346
        // Validate against ArtifactType rules
347
        artifactType := &arcv1alpha1.ArtifactType{}
4,190✔
348
        if err := r.Get(ctx, namespacedName(order.Namespace, artifact.Type), artifactType); client.IgnoreNotFound(err) != nil {
4,190✔
349
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidArtifactType", fmt.Sprintf("Failed to fetch ArtifactType '%s': %v", artifact.Type, err))
×
350
                return nil, errLogAndWrap(log, err, "failed to fetch referenced ArtifactType")
×
351
        }
×
352
        var (
4,190✔
353
                artifactTypeGen  int64
4,190✔
354
                artifactTypeSpec *arcv1alpha1.ArtifactTypeSpec
4,190✔
355
        )
4,190✔
356
        if artifactType.Name == "" { // was not found, let's check ClusterArtifactType
8,204✔
357
                clusterArtifactType := &arcv1alpha1.ClusterArtifactType{}
4,014✔
358
                if err := r.Get(ctx, namespacedName("", artifact.Type), clusterArtifactType); err != nil {
4,027✔
359
                        return nil, errLogAndWrap(log, err, "failed to fetch ArtifactType or ClusterArtifactType")
13✔
360
                }
13✔
361
                artifactTypeSpec = &clusterArtifactType.Spec
4,001✔
362
                artifactTypeGen = clusterArtifactType.Generation
4,001✔
363
                // NOTE: ClusterArtifactTypes can only referes ClusterWorkflowTemplates, so we enforce this here:
4,001✔
364
                artifactTypeSpec.WorkflowTemplateRef.ClusterScope = true
4,001✔
365
        } else {
176✔
366
                artifactTypeSpec = &artifactType.Spec
176✔
367
                artifactTypeGen = artifactType.Generation
176✔
368
        }
176✔
369

370
        if len(artifactTypeSpec.Rules.SrcTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.SrcTypes, srcEndpoint.Spec.Type) {
4,186✔
371
                err := fmt.Errorf("source endpoint type '%s' is not allowed by ArtifactType rules", srcEndpoint.Spec.Type)
9✔
372
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidArtifactType", fmt.Sprintf("Source endpoint type '%s' is not allowed by ArtifactType '%s' rules", srcEndpoint.Spec.Type, artifact.Type))
9✔
373
                return nil, errLogAndWrap(log, err, "artifact validation failed")
9✔
374
        }
9✔
375
        if len(artifactTypeSpec.Rules.DstTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.DstTypes, dstEndpoint.Spec.Type) {
4,168✔
376
                err := fmt.Errorf("destination endpoint type '%s' is not allowed by ArtifactType rules", dstEndpoint.Spec.Type)
×
377
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidArtifactType", fmt.Sprintf("Destination endpoint type '%s' is not allowed by ArtifactType '%s' rules", dstEndpoint.Spec.Type, artifact.Type))
×
378
                return nil, errLogAndWrap(log, err, "artifact validation failed")
×
379
        }
×
380

381
        // Next, we need the secret contents
382
        srcSecret := &corev1.Secret{}
4,168✔
383
        if srcEndpoint.Spec.SecretRef.Name != "" {
8,160✔
384
                if err := r.Get(ctx, namespacedName(order.Namespace, srcEndpoint.Spec.SecretRef.Name), srcSecret); err != nil {
3,992✔
385
                        r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidSecret", fmt.Sprintf("Failed to fetch source secret '%s': %v", srcEndpoint.Spec.SecretRef.Name, err))
×
386
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for source")
×
387
                }
×
388
        }
389

390
        dstSecret := &corev1.Secret{}
4,168✔
391
        if dstEndpoint.Spec.SecretRef.Name != "" {
8,160✔
392
                if err := r.Get(ctx, namespacedName(order.Namespace, dstEndpoint.Spec.SecretRef.Name), dstSecret); err != nil {
3,992✔
393
                        r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidSecret", fmt.Sprintf("Failed to fetch destination secret '%s': %v", dstEndpoint.Spec.SecretRef.Name, err))
×
394
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for destination")
×
395
                }
×
396
        }
397

398
        // Create a hash based on all related data for idempotency and compute the workflow name
399
        h := sha256.New()
4,168✔
400
        data := []any{
4,168✔
401
                order.Namespace,
4,168✔
402
                artifact.Type, artifact.Spec.Raw, artifactTypeGen,
4,168✔
403
                srcEndpoint.Name,
4,168✔
404
                dstEndpoint.Name,
4,168✔
405
        }
4,168✔
406
        jsonData, err := json.Marshal(data)
4,168✔
407
        if err != nil {
4,168✔
408
                return nil, errLogAndWrap(log, err, "failed to marshal artifact workflow data")
×
409
        }
×
410
        h.Write(jsonData)
4,168✔
411
        sha := hex.EncodeToString(h.Sum(nil))[:16]
4,168✔
412

4,168✔
413
        // We gave all the information to further process this artifact workflow.
4,168✔
414
        // Let's store it to compare it to the current status!
4,168✔
415
        return &desiredAW{
4,168✔
416
                index:       i,
4,168✔
417
                objectMeta:  awObjectMeta(order, sha),
4,168✔
418
                artifact:    artifact,
4,168✔
419
                typeSpec:    artifactTypeSpec,
4,168✔
420
                srcEndpoint: srcEndpoint,
4,168✔
421
                dstEndpoint: dstEndpoint,
4,168✔
422
                srcSecret:   srcSecret,
4,168✔
423
                dstSecret:   dstSecret,
4,168✔
424
                sha:         sha,
4,168✔
425
        }, nil
4,168✔
426
}
427

428
// SetupWithManager sets up the controller with the Manager.
429
func (r *OrderReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
430
        return ctrl.NewControllerManagedBy(mgr).
1✔
431
                For(&arcv1alpha1.Order{}).
1✔
432
                Owns(&arcv1alpha1.ArtifactWorkflow{}).
1✔
433
                Complete(r)
1✔
434
}
1✔
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