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

opendefensecloud / artifact-conduit / 19827480366

01 Dec 2025 03:13PM UTC coverage: 65.42% (+4.1%) from 61.352%
19827480366

Pull #96

github

web-flow
Merge d4340e185 into c45fe1461
Pull Request #96: Issue #23: Artifact Immutability

647 of 989 relevant lines covered (65.42%)

917.24 hits per line

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

85.31
/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,451✔
62
        log := ctrl.LoggerFrom(ctx)
2,451✔
63
        ctrlResult := ctrl.Result{}
2,451✔
64

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

75
        // Update last reconcile time
76
        order.Status.LastReconcileAt = metav1.Now()
2,450✔
77

2,450✔
78
        // Handle deletion: cleanup artifact workflows, then remove finalizer
2,450✔
79
        if !order.DeletionTimestamp.IsZero() {
2,453✔
80
                log.V(1).Info("Order is being deleted")
3✔
81
                r.Recorder.Event(order, corev1.EventTypeWarning, "Deleting", "Order is being deleted, cleaning up artifact workflows")
3✔
82

3✔
83
                // Cleanup all artifact workflows
3✔
84
                if len(order.Status.ArtifactWorkflows) > 0 {
5✔
85
                        for sha := range order.Status.ArtifactWorkflows {
6✔
86
                                // Remove ArtifactWorkflow
4✔
87
                                aw := &arcv1alpha1.ArtifactWorkflow{
4✔
88
                                        ObjectMeta: awObjectMeta(order, sha),
4✔
89
                                }
4✔
90
                                _ = r.Delete(ctx, aw) // Ignore errors
4✔
91
                                delete(order.Status.ArtifactWorkflows, sha)
4✔
92
                        }
4✔
93
                        if err := r.Status().Update(ctx, order); err != nil {
3✔
94
                                return ctrlResult, errLogAndWrap(log, err, "failed to update order status")
1✔
95
                        }
1✔
96
                        log.V(1).Info("Order artifact workflows cleaned up")
1✔
97

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

114
        // Add finalizer if not present and not deleting
115
        if order.DeletionTimestamp.IsZero() {
4,894✔
116
                if !slices.Contains(order.Finalizers, orderFinalizer) {
2,464✔
117
                        log.V(1).Info("Adding finalizer to Order")
17✔
118
                        order.Finalizers = append(order.Finalizers, orderFinalizer)
17✔
119
                        if err := r.Update(ctx, order); err != nil {
17✔
120
                                return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer")
×
121
                        }
×
122
                        // Return without requeue; the Update event will trigger reconciliation again
123
                        return ctrlResult, nil
17✔
124
                }
125
        }
126

127
        // Handle force reconcile annotation
128
        forceAt, err := GetForceAtAnnotationValue(order)
2,430✔
129
        if err != nil {
2,430✔
130
                log.V(1).Error(err, "Invalid force reconcile annotation, ignoring")
×
131
        }
×
132
        if !forceAt.IsZero() && (order.Status.LastForceAt.IsZero() || forceAt.After(order.Status.LastForceAt.Time)) {
2,431✔
133
                log.V(1).Info("Force reconcile requested")
1✔
134
                r.Recorder.Event(order, corev1.EventTypeNormal, "ForceReconcile", "Force reconcile requested via annotation")
1✔
135
                // Delete existing artifact workflows to force re-creation
1✔
136
                for sha := range order.Status.ArtifactWorkflows {
2✔
137
                        // Remove Secret and ArtifactWorkflow
1✔
138
                        aw := &arcv1alpha1.ArtifactWorkflow{
1✔
139
                                ObjectMeta: awObjectMeta(order, sha),
1✔
140
                        }
1✔
141
                        _ = r.Delete(ctx, aw) // Ignore errors
1✔
142
                        delete(order.Status.ArtifactWorkflows, sha)
1✔
143
                }
1✔
144
                // Update last force time
145
                order.Status.LastForceAt = metav1.Now()
1✔
146
                if err := r.Status().Update(ctx, order); err != nil {
1✔
147
                        return ctrlResult, errLogAndWrap(log, err, "failed to update last force time")
×
148
                }
×
149
                // Return without requeue; the update event will trigger reconciliation again
150
                return ctrlResult, nil
1✔
151
        }
152

153
        // Make sure status is initialized
154
        if order.Status.ArtifactWorkflows == nil {
2,523✔
155
                order.Status.ArtifactWorkflows = map[string]arcv1alpha1.OrderArtifactWorkflowStatus{}
94✔
156
        }
94✔
157

158
        // Before we compare to our status, let's fetch all necessary information
159
        // to compute desired state:
160
        desiredAWs := map[string]desiredAW{}
2,429✔
161
        for i, artifact := range order.Spec.Artifacts {
6,367✔
162
                daw, err := r.computeDesiredAW(ctx, log, order, &artifact, i)
3,938✔
163
                if err != nil {
4,046✔
164
                        r.Recorder.Event(order, corev1.EventTypeWarning, "ComputationFailed", fmt.Sprintf("Failed to compute desired artifact workflow for artifact index %d: %v", i, err))
108✔
165
                        order.Status.Message = fmt.Sprintf("Failed to compute desired artifact workflow for artifact index %d: %v", i, err)
108✔
166
                        if err := r.Status().Update(ctx, order); err != nil {
109✔
167
                                return ctrlResult, errLogAndWrap(log, err, "failed to update status")
1✔
168
                        }
1✔
169
                        return ctrlResult, errLogAndWrap(log, err, "failed to compute desired artifact workflow")
107✔
170
                }
171
                desiredAWs[daw.sha] = *daw
3,830✔
172
        }
173
        order.Status.Message = "" // Clear any previous error message
2,321✔
174

2,321✔
175
        // List missing artifact workflows
2,321✔
176
        createAWs := []string{}
2,321✔
177
        for sha := range desiredAWs {
6,146✔
178
                _, exists := order.Status.ArtifactWorkflows[sha]
3,825✔
179
                if exists {
7,625✔
180
                        continue
3,800✔
181
                }
182
                createAWs = append(createAWs, sha)
25✔
183
        }
184

185
        // Find obsolete artifact workflows
186
        deleteAWs := []string{}
2,321✔
187
        for sha := range order.Status.ArtifactWorkflows {
6,122✔
188
                _, exists := desiredAWs[sha]
3,801✔
189
                if exists {
7,601✔
190
                        continue
3,800✔
191
                }
192
                deleteAWs = append(deleteAWs, sha)
1✔
193
        }
194

195
        // Find finished artifact workflows to clean up
196
        finishedAWs := []string{}
2,321✔
197
        for sha := range order.Status.ArtifactWorkflows {
6,122✔
198
                awStatus := order.Status.ArtifactWorkflows[sha]
3,801✔
199

3,801✔
200
                // Only consider succeeded workflows for TTL cleanup
3,801✔
201
                if awStatus.Phase != arcv1alpha1.WorkflowSucceeded {
7,529✔
202
                        continue
3,728✔
203
                }
204

205
                // If TTL is set, check if it has expired
206
                if order.Spec.TTLSecondsAfterCompletion != nil && *order.Spec.TTLSecondsAfterCompletion > 0 {
73✔
207
                        if time.Since(awStatus.CompletionTime.Time) > time.Duration(*order.Spec.TTLSecondsAfterCompletion)*time.Second {
×
208
                                finishedAWs = append(finishedAWs, sha)
×
209
                        } else {
×
210
                                // Requeue when the next TTL expires
×
211
                                ctrlResult.RequeueAfter = time.Duration((*order.Spec.TTLSecondsAfterCompletion)+1)*time.Second - time.Since(awStatus.CompletionTime.Time)
×
212
                        }
×
213
                        continue
×
214
                }
215

216
                // No TTL set, cleanup immediately
217
                finishedAWs = append(finishedAWs, sha)
73✔
218
        }
219

220
        // Create missing artifact workflows
221
        for _, sha := range createAWs {
2,346✔
222
                daw := desiredAWs[sha]
25✔
223
                aw, err := r.hydrateArtifactWorkflow(&daw)
25✔
224
                if err != nil {
25✔
225
                        r.Recorder.Event(order, corev1.EventTypeWarning, "HydrationFailed", fmt.Sprintf("Failed to hydrate artifact workflow for artifact index %d: %v", daw.index, err))
×
226
                        return ctrlResult, errLogAndWrap(log, err, "failed to hydrate artifact workflow")
×
227
                }
×
228

229
                // Set owner references
230
                if err := controllerutil.SetControllerReference(order, aw, r.Scheme); err != nil {
25✔
231
                        r.Recorder.Event(order, corev1.EventTypeWarning, "HydrationFailed", fmt.Sprintf("Failed to set controller reference for artifact workflow: %v", err))
×
232
                        return ctrlResult, errLogAndWrap(log, err, "failed to set controller reference")
×
233
                }
×
234

235
                // Create artifact workflow
236
                if err := r.Create(ctx, aw); err != nil {
31✔
237
                        if apierrors.IsAlreadyExists(err) {
12✔
238
                                // Already created by a previous reconcile — that's fine
6✔
239
                                continue
6✔
240
                        }
241
                        r.Recorder.Event(order, corev1.EventTypeWarning, "CreationFailed", fmt.Sprintf("Failed to create artifact workflow for artifact index %d: %v", daw.index, err))
×
242
                        return ctrlResult, errLogAndWrap(log, err, "failed to create artifact workflow")
×
243
                }
244

245
                // Update status
246
                order.Status.ArtifactWorkflows[sha] = arcv1alpha1.OrderArtifactWorkflowStatus{
19✔
247
                        ArtifactIndex: daw.index,
19✔
248
                        Phase:         arcv1alpha1.WorkflowUnknown,
19✔
249
                }
19✔
250

19✔
251
                r.Recorder.Event(order, corev1.EventTypeNormal, "ArtifactWorkflowCreated", fmt.Sprintf("Created artifact workflow '%s' for artifact index %d", aw.Name, daw.index))
19✔
252
                log.V(1).Info("Created artifact workflow", "artifactWorkflow", aw.Name)
19✔
253
        }
254

255
        // Delete obsolete artifact workflows
256
        for _, sha := range deleteAWs {
2,322✔
257
                // Does not exist anymore, let's clean up!
1✔
258
                if err := r.Delete(ctx, &arcv1alpha1.ArtifactWorkflow{
1✔
259
                        ObjectMeta: awObjectMeta(order, sha),
1✔
260
                }); client.IgnoreNotFound(err) != nil {
1✔
261
                        r.Recorder.Event(order, corev1.EventTypeWarning, "DeletionFailed", fmt.Sprintf("Failed to delete obsolete artifact workflow '%s': %v", sha, err))
×
262
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
263
                }
×
264

265
                // Update status
266
                delete(order.Status.ArtifactWorkflows, sha)
1✔
267
                log.V(1).Info("Deleted obsolete artifact workflow", "artifactWorkflow", sha)
1✔
268
                r.Recorder.Event(order, corev1.EventTypeNormal, "ArtifactWorkflowDeleted", fmt.Sprintf("Deleted obsolete artifact workflow '%s'", sha))
1✔
269
        }
270

271
        // Delete finished artifact workflows
272
        for _, sha := range finishedAWs {
2,394✔
273
                // Finished, let's clean up!
73✔
274
                if err := r.Delete(ctx, &arcv1alpha1.ArtifactWorkflow{
73✔
275
                        ObjectMeta: awObjectMeta(order, sha),
73✔
276
                }); client.IgnoreNotFound(err) != nil {
73✔
277
                        r.Recorder.Event(order, corev1.EventTypeWarning, "DeletionFailed", fmt.Sprintf("Failed to delete finished artifact workflow '%s': %v", sha, err))
×
278
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
279
                }
×
280

281
                log.V(1).Info("Deleted finished artifact workflow", "artifactWorkflow", sha)
73✔
282
                r.Recorder.Event(order, corev1.EventTypeNormal, "ArtifactWorkflowDeleted", fmt.Sprintf("Deleted finished artifact workflow '%s'", sha))
73✔
283
        }
284

285
        anyPhaseChanged := false
2,321✔
286
        for sha, daw := range desiredAWs {
6,146✔
287
                if slices.Contains(createAWs, sha) {
3,850✔
288
                        // If it was just created we skip the update
25✔
289
                        continue
25✔
290
                }
291
                if order.Status.ArtifactWorkflows[sha].Phase.Completed() {
3,873✔
292
                        // We do not need to check for updates if the workflow is completed
73✔
293
                        continue
73✔
294
                }
295
                aw := arcv1alpha1.ArtifactWorkflow{}
3,727✔
296
                if err := r.Get(ctx, namespacedName(daw.objectMeta.Namespace, daw.objectMeta.Name), &aw); err != nil {
3,734✔
297
                        return ctrlResult, errLogAndWrap(log, err, "failed to get artifact workflow")
7✔
298
                }
7✔
299
                if order.Status.ArtifactWorkflows[sha].Phase != aw.Status.Phase {
5,197✔
300
                        awStatus := order.Status.ArtifactWorkflows[sha]
1,477✔
301
                        awStatus.Phase = aw.Status.Phase
1,477✔
302
                        awStatus.CompletionTime = aw.Status.CompletionTime
1,477✔
303
                        order.Status.ArtifactWorkflows[sha] = awStatus
1,477✔
304
                        anyPhaseChanged = true
1,477✔
305
                }
1,477✔
306
        }
307

308
        // Update status
309
        if len(createAWs) > 0 || len(deleteAWs) > 0 || anyPhaseChanged {
3,752✔
310
                log.V(1).Info("Updating order status")
1,438✔
311
                // Make sure ArtifactIndex is up to date
1,438✔
312
                for sha, daw := range desiredAWs {
3,840✔
313
                        aws := order.Status.ArtifactWorkflows[sha]
2,402✔
314
                        aws.ArtifactIndex = daw.index
2,402✔
315
                        order.Status.ArtifactWorkflows[sha] = aws
2,402✔
316
                }
2,402✔
317
                if err := r.Status().Update(ctx, order); err != nil {
1,502✔
318
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
64✔
319
                }
64✔
320
        }
321

322
        return ctrlResult, nil
2,250✔
323
}
324

325
func (r *OrderReconciler) hydrateArtifactWorkflow(daw *desiredAW) (*arcv1alpha1.ArtifactWorkflow, error) {
25✔
326
        params, err := dawToParameters(daw)
25✔
327
        if err != nil {
25✔
328
                return nil, err
×
329
        }
×
330

331
        // Next we create the ArtifactWorkflow instance
332
        aw := &arcv1alpha1.ArtifactWorkflow{
25✔
333
                ObjectMeta: daw.objectMeta,
25✔
334
                Spec: arcv1alpha1.ArtifactWorkflowSpec{
25✔
335
                        WorkflowTemplateRef: daw.typeSpec.WorkflowTemplateRef,
25✔
336
                        Parameters:          params,
25✔
337
                        SrcSecretRef:        daw.srcEndpoint.Spec.SecretRef,
25✔
338
                        DstSecretRef:        daw.dstEndpoint.Spec.SecretRef,
25✔
339
                },
25✔
340
        }
25✔
341

25✔
342
        return aw, nil
25✔
343
}
344

345
func (r *OrderReconciler) computeDesiredAW(ctx context.Context, log logr.Logger, order *arcv1alpha1.Order, artifact *arcv1alpha1.OrderArtifact, i int) (*desiredAW, error) {
3,938✔
346
        log = log.WithValues("artifactIndex", i)
3,938✔
347

3,938✔
348
        // We need the referenced src- and dst-endpoints for the artifact
3,938✔
349
        srcRefName := artifact.SrcRef.Name
3,938✔
350
        if srcRefName == "" {
4,323✔
351
                srcRefName = order.Spec.Defaults.SrcRef.Name
385✔
352
        }
385✔
353
        dstRefName := artifact.DstRef.Name
3,938✔
354
        if dstRefName == "" {
4,430✔
355
                dstRefName = order.Spec.Defaults.DstRef.Name
492✔
356
        }
492✔
357
        srcEndpoint := &arcv1alpha1.Endpoint{}
3,938✔
358
        if err := r.Get(ctx, namespacedName(order.Namespace, srcRefName), srcEndpoint); err != nil {
3,950✔
359
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Failed to fetch source endpoint '%s': %v", srcRefName, err))
12✔
360
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for source")
12✔
361
        }
12✔
362
        dstEndpoint := &arcv1alpha1.Endpoint{}
3,926✔
363
        if err := r.Get(ctx, namespacedName(order.Namespace, dstRefName), dstEndpoint); err != nil {
3,938✔
364
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Failed to fetch destination endpoint '%s': %v", dstRefName, err))
12✔
365
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for destination")
12✔
366
        }
12✔
367

368
        // Validate that the endpoint usage is correct
369
        if srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePullOnly && srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
3,928✔
370
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with source usage", srcEndpoint.Name, srcEndpoint.Spec.Usage)
14✔
371
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Source endpoint '%s' has incompatible usage '%s'", srcEndpoint.Name, srcEndpoint.Spec.Usage))
14✔
372
                return nil, errLogAndWrap(log, err, "artifact validation failed")
14✔
373
        }
14✔
374
        if dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePushOnly && dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
3,914✔
375
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with destination usage", dstEndpoint.Name, dstEndpoint.Spec.Usage)
14✔
376
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidEndpoint", fmt.Sprintf("Destination endpoint '%s' has incompatible usage '%s'", dstEndpoint.Name, dstEndpoint.Spec.Usage))
14✔
377
                return nil, errLogAndWrap(log, err, "artifact validation failed")
14✔
378
        }
14✔
379

380
        // Validate against ArtifactType rules
381
        artifactType := &arcv1alpha1.ArtifactType{}
3,886✔
382
        if err := r.Get(ctx, namespacedName(order.Namespace, artifact.Type), artifactType); client.IgnoreNotFound(err) != nil {
3,886✔
383
                r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidArtifactType", fmt.Sprintf("Failed to fetch ArtifactType '%s': %v", artifact.Type, err))
×
384
                return nil, errLogAndWrap(log, err, "failed to fetch referenced ArtifactType")
×
385
        }
×
386
        var (
3,886✔
387
                artifactTypeGen  int64
3,886✔
388
                artifactTypeSpec *arcv1alpha1.ArtifactTypeSpec
3,886✔
389
        )
3,886✔
390
        if artifactType.Name == "" { // was not found, let's check ClusterArtifactType
7,667✔
391
                clusterArtifactType := &arcv1alpha1.ClusterArtifactType{}
3,781✔
392
                if err := r.Get(ctx, namespacedName("", artifact.Type), clusterArtifactType); err != nil {
3,827✔
393
                        return nil, errLogAndWrap(log, err, "failed to fetch ArtifactType or ClusterArtifactType")
46✔
394
                }
46✔
395
                artifactTypeSpec = &clusterArtifactType.Spec
3,735✔
396
                artifactTypeGen = clusterArtifactType.Generation
3,735✔
397
                // NOTE: ClusterArtifactTypes can only referes ClusterWorkflowTemplates, so we enforce this here:
3,735✔
398
                artifactTypeSpec.WorkflowTemplateRef.ClusterScope = true
3,735✔
399
        } else {
105✔
400
                artifactTypeSpec = &artifactType.Spec
105✔
401
                artifactTypeGen = artifactType.Generation
105✔
402
        }
105✔
403

404
        if len(artifactTypeSpec.Rules.SrcTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.SrcTypes, srcEndpoint.Spec.Type) {
3,850✔
405
                err := fmt.Errorf("source endpoint type '%s' is not allowed by ArtifactType rules", srcEndpoint.Spec.Type)
10✔
406
                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))
10✔
407
                return nil, errLogAndWrap(log, err, "artifact validation failed")
10✔
408
        }
10✔
409
        if len(artifactTypeSpec.Rules.DstTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.DstTypes, dstEndpoint.Spec.Type) {
3,830✔
410
                err := fmt.Errorf("destination endpoint type '%s' is not allowed by ArtifactType rules", dstEndpoint.Spec.Type)
×
411
                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))
×
412
                return nil, errLogAndWrap(log, err, "artifact validation failed")
×
413
        }
×
414

415
        // Next, we need the secret contents
416
        srcSecret := &corev1.Secret{}
3,830✔
417
        if srcEndpoint.Spec.SecretRef.Name != "" {
7,531✔
418
                if err := r.Get(ctx, namespacedName(order.Namespace, srcEndpoint.Spec.SecretRef.Name), srcSecret); err != nil {
3,701✔
419
                        r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidSecret", fmt.Sprintf("Failed to fetch source secret '%s': %v", srcEndpoint.Spec.SecretRef.Name, err))
×
420
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for source")
×
421
                }
×
422
        }
423

424
        dstSecret := &corev1.Secret{}
3,830✔
425
        if dstEndpoint.Spec.SecretRef.Name != "" {
7,531✔
426
                if err := r.Get(ctx, namespacedName(order.Namespace, dstEndpoint.Spec.SecretRef.Name), dstSecret); err != nil {
3,701✔
427
                        r.Recorder.Event(order, corev1.EventTypeWarning, "InvalidSecret", fmt.Sprintf("Failed to fetch destination secret '%s': %v", dstEndpoint.Spec.SecretRef.Name, err))
×
428
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for destination")
×
429
                }
×
430
        }
431

432
        // Create a hash based on all related data for idempotency and compute the workflow name
433
        h := sha256.New()
3,830✔
434
        data := []any{
3,830✔
435
                order.Namespace,
3,830✔
436
                artifact.Type, artifact.Spec.Raw, artifactTypeGen,
3,830✔
437
                srcEndpoint.Name,
3,830✔
438
                dstEndpoint.Name,
3,830✔
439
        }
3,830✔
440
        jsonData, err := json.Marshal(data)
3,830✔
441
        if err != nil {
3,830✔
442
                return nil, errLogAndWrap(log, err, "failed to marshal artifact workflow data")
×
443
        }
×
444
        h.Write(jsonData)
3,830✔
445
        sha := hex.EncodeToString(h.Sum(nil))[:16]
3,830✔
446

3,830✔
447
        // We gave all the information to further process this artifact workflow.
3,830✔
448
        // Let's store it to compare it to the current status!
3,830✔
449
        return &desiredAW{
3,830✔
450
                index:       i,
3,830✔
451
                objectMeta:  awObjectMeta(order, sha),
3,830✔
452
                artifact:    artifact,
3,830✔
453
                typeSpec:    artifactTypeSpec,
3,830✔
454
                srcEndpoint: srcEndpoint,
3,830✔
455
                dstEndpoint: dstEndpoint,
3,830✔
456
                srcSecret:   srcSecret,
3,830✔
457
                dstSecret:   dstSecret,
3,830✔
458
                sha:         sha,
3,830✔
459
        }, nil
3,830✔
460
}
461

462
// SetupWithManager sets up the controller with the Manager.
463
func (r *OrderReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
464
        return ctrl.NewControllerManagedBy(mgr).
1✔
465
                For(&arcv1alpha1.Order{}).
1✔
466
                Owns(&arcv1alpha1.ArtifactWorkflow{}).
1✔
467
                Complete(r)
1✔
468
}
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