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

opendefensecloud / artifact-conduit / 20994364778

14 Jan 2026 12:38PM UTC coverage: 83.655% (-1.2%) from 84.904%
20994364778

push

github

web-flow
make ginkgo: autodetect version (#174)

737 of 881 relevant lines covered (83.65%)

1162.83 hits per line

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

82.89
/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.opendefense.cloud/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
        cron        *arcv1alpha1.Cron
49
}
50

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

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

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

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

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

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

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

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

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

155
        // Make sure status is initialized
156
        if order.Status.ArtifactWorkflows == nil {
2,593✔
157
                order.Status.ArtifactWorkflows = map[string]arcv1alpha1.OrderArtifactWorkflowStatus{}
90✔
158
        }
90✔
159

160
        // Before we compare to our status, let's fetch all necessary information
161
        // to compute desired state:
162
        desiredAWs := map[string]desiredAW{}
2,503✔
163
        for i, artifact := range order.Spec.Artifacts {
6,680✔
164
                daw, err := r.computeDesiredAW(ctx, log, order, &artifact, i)
4,177✔
165
                if err != nil {
4,296✔
166
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "ComputationFailed", "Failed to compute desired artifact workflow for artifact index %d: %v", i, err)
119✔
167
                        order.Status.Message = fmt.Sprintf("Failed to compute desired artifact workflow for artifact index %d: %v", i, err)
119✔
168
                        if err := r.Status().Update(ctx, order); err != nil {
119✔
169
                                return ctrlResult, errLogAndWrap(log, err, "failed to update status")
×
170
                        }
×
171
                        return ctrlResult, errLogAndWrap(log, err, "failed to compute desired artifact workflow")
119✔
172
                }
173
                desiredAWs[daw.sha] = *daw
4,058✔
174
        }
175
        order.Status.Message = "" // Clear any previous error message
2,384✔
176

2,384✔
177
        // List missing artifact workflows
2,384✔
178
        var createAWs []string
2,384✔
179
        for sha := range desiredAWs {
6,435✔
180
                if _, exists := order.Status.ArtifactWorkflows[sha]; exists {
8,079✔
181
                        continue
4,028✔
182
                }
183

184
                createAWs = append(createAWs, sha)
23✔
185
        }
186

187
        // Find obsolete artifact workflows
188
        var deleteAWs []string
2,384✔
189
        for sha := range order.Status.ArtifactWorkflows {
6,413✔
190
                if _, exists := desiredAWs[sha]; exists {
8,057✔
191
                        continue
4,028✔
192
                }
193

194
                deleteAWs = append(deleteAWs, sha)
1✔
195
        }
196

197
        // Find finished artifact workflows to clean up
198
        var finishedAWs []string
2,384✔
199
        for sha := range order.Status.ArtifactWorkflows {
6,413✔
200
                awStatus := order.Status.ArtifactWorkflows[sha]
4,029✔
201

4,029✔
202
                // Only consider succeeded workflows for TTL cleanup
4,029✔
203
                if awStatus.Phase != arcv1alpha1.WorkflowSucceeded {
7,968✔
204
                        continue
3,939✔
205
                }
206

207
                // Do not clean up ArtifactWorkflows with cron specified
208
                if daw, ok := desiredAWs[sha]; ok && daw.cron != nil {
90✔
209
                        continue
×
210
                }
211

212
                // If TTL is set, check if it has expired
213
                if order.Spec.TTLSecondsAfterCompletion != nil && *order.Spec.TTLSecondsAfterCompletion > 0 {
90✔
214
                        if time.Since(awStatus.CompletionTime.Time) > time.Duration(*order.Spec.TTLSecondsAfterCompletion)*time.Second {
×
215
                                finishedAWs = append(finishedAWs, sha)
×
216
                        } else {
×
217
                                // Requeue when the next TTL expires
×
218
                                ctrlResult.RequeueAfter = time.Duration((*order.Spec.TTLSecondsAfterCompletion)+1)*time.Second - time.Since(awStatus.CompletionTime.Time)
×
219
                        }
×
220
                        continue
×
221
                }
222

223
                // No TTL set, cleanup immediately
224
                finishedAWs = append(finishedAWs, sha)
90✔
225
        }
226

227
        // Create missing artifact workflows
228
        for _, sha := range createAWs {
2,407✔
229
                daw := desiredAWs[sha]
23✔
230
                aw, err := r.hydrateArtifactWorkflow(&daw)
23✔
231
                if err != nil {
23✔
232
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "HydrationFailed", "Failed to hydrate artifact workflow for artifact index %d: %v", daw.index, err)
×
233
                        return ctrlResult, errLogAndWrap(log, err, "failed to hydrate artifact workflow")
×
234
                }
×
235

236
                // Set owner references
237
                if err := controllerutil.SetControllerReference(order, aw, r.Scheme); err != nil {
23✔
238
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "HydrationFailed", "Failed to set controller reference for artifact workflow: %v", err)
×
239
                        return ctrlResult, errLogAndWrap(log, err, "failed to set controller reference")
×
240
                }
×
241

242
                // Create artifact workflow
243
                if err := r.Create(ctx, aw); err != nil {
26✔
244
                        if apierrors.IsAlreadyExists(err) {
6✔
245
                                // Already created by a previous reconcile — that's fine
3✔
246
                                continue
3✔
247
                        }
248
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "CreationFailed", "Failed to create artifact workflow for artifact index %d: %v", daw.index, err)
×
249
                        return ctrlResult, errLogAndWrap(log, err, "failed to create artifact workflow")
×
250
                } else {
20✔
251
                        r.Recorder.Eventf(order, corev1.EventTypeNormal, "ArtifactWorkflowCreated", "Created artifact workflow '%s' for artifact index %d", aw.Name, daw.index)
20✔
252
                        log.V(1).Info("Created artifact workflow", "artifactWorkflow", aw.Name)
20✔
253
                }
20✔
254

255
                // Update status
256
                order.Status.ArtifactWorkflows[sha] = arcv1alpha1.OrderArtifactWorkflowStatus{
20✔
257
                        ArtifactIndex: daw.index,
20✔
258
                        WorkflowStatus: arcv1alpha1.WorkflowStatus{
20✔
259
                                Phase: arcv1alpha1.WorkflowUnknown,
20✔
260
                        },
20✔
261
                }
20✔
262
        }
263

264
        // Delete obsolete artifact workflows
265
        for _, sha := range deleteAWs {
2,385✔
266
                // Does not exist anymore, let's clean up!
1✔
267
                if err := r.Delete(ctx, &arcv1alpha1.ArtifactWorkflow{
1✔
268
                        ObjectMeta: awObjectMeta(order, sha),
1✔
269
                }); client.IgnoreNotFound(err) != nil {
1✔
270
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "DeletionFailed", "Failed to delete obsolete artifact workflow '%s': %v", sha, err)
×
271
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
272
                }
×
273

274
                // Update status
275
                delete(order.Status.ArtifactWorkflows, sha)
1✔
276
                log.V(1).Info("Deleted obsolete artifact workflow", "artifactWorkflow", sha)
1✔
277
                r.Recorder.Eventf(order, corev1.EventTypeNormal, "ArtifactWorkflowDeleted", "Deleted obsolete artifact workflow '%s'", sha)
1✔
278
        }
279

280
        // Delete finished artifact workflows
281
        for _, sha := range finishedAWs {
2,474✔
282
                // Finished, let's clean up!
90✔
283
                if err := r.Delete(ctx, &arcv1alpha1.ArtifactWorkflow{
90✔
284
                        ObjectMeta: awObjectMeta(order, sha),
90✔
285
                }); client.IgnoreNotFound(err) != nil {
90✔
286
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "DeletionFailed", "Failed to delete finished artifact workflow '%s': %v", sha, err)
×
287
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
288
                }
×
289

290
                log.V(1).Info("Deleted finished artifact workflow", "artifactWorkflow", sha)
90✔
291
                r.Recorder.Eventf(order, corev1.EventTypeNormal, "ArtifactWorkflowDeleted", "Deleted finished artifact workflow '%s'", sha)
90✔
292
        }
293

294
        anyPhaseChanged := false
2,384✔
295
        for sha, daw := range desiredAWs {
6,435✔
296
                if slices.Contains(createAWs, sha) {
4,074✔
297
                        // If it was just created we skip the update
23✔
298
                        continue
23✔
299
                }
300
                if daw.cron == nil && order.Status.ArtifactWorkflows[sha].Phase.Completed() {
4,118✔
301
                        // We do not need to check for updates if the workflow is completed and is NOT cron
90✔
302
                        continue
90✔
303
                }
304
                aw := arcv1alpha1.ArtifactWorkflow{}
3,938✔
305
                if err := r.Get(ctx, namespacedName(daw.objectMeta.Namespace, daw.objectMeta.Name), &aw); err != nil {
3,938✔
306
                        delete(order.Status.ArtifactWorkflows, sha)
×
307
                        log.V(1).Info("Artifact workflow not found, deleting from status.", "artifactWorkflow", sha)
×
308
                        if err := r.Status().Update(ctx, order); err != nil {
×
309
                                return ctrlResult, errLogAndWrap(log, err, "failed to update status")
×
310
                        }
×
311
                        return ctrlResult, errLogAndWrap(log, err, "failed to get artifact workflow")
×
312
                }
313
                orderAWStatus := order.Status.ArtifactWorkflows[sha]
3,938✔
314
                if orderAWStatus.Phase != aw.Status.Phase ||
3,938✔
315
                        orderAWStatus.Succeeded != aw.Status.Succeeded ||
3,938✔
316
                        orderAWStatus.Failed != aw.Status.Failed ||
3,938✔
317
                        !orderAWStatus.LastScheduled.Equal(aw.Status.LastScheduled) {
5,433✔
318
                        orderAWStatus.WorkflowStatus = aw.Status.WorkflowStatus
1,495✔
319
                        order.Status.ArtifactWorkflows[sha] = orderAWStatus
1,495✔
320
                        anyPhaseChanged = true
1,495✔
321
                }
1,495✔
322
        }
323

324
        // Update status
325
        if len(createAWs) > 0 || len(deleteAWs) > 0 || anyPhaseChanged {
3,846✔
326
                log.V(1).Info("Updating order status")
1,462✔
327
                // Make sure ArtifactIndex is up to date
1,462✔
328
                for sha, daw := range desiredAWs {
3,962✔
329
                        aws := order.Status.ArtifactWorkflows[sha]
2,500✔
330
                        aws.ArtifactIndex = daw.index
2,500✔
331
                        order.Status.ArtifactWorkflows[sha] = aws
2,500✔
332
                }
2,500✔
333
                if err := r.Status().Update(ctx, order); err != nil {
1,520✔
334
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
58✔
335
                }
58✔
336
        }
337

338
        return ctrlResult, nil
2,326✔
339
}
340

341
func (r *OrderReconciler) hydrateArtifactWorkflow(daw *desiredAW) (*arcv1alpha1.ArtifactWorkflow, error) {
23✔
342
        params, err := dawToParameters(daw)
23✔
343
        if err != nil {
23✔
344
                return nil, err
×
345
        }
×
346

347
        // Next we create the ArtifactWorkflow instance
348
        aw := &arcv1alpha1.ArtifactWorkflow{
23✔
349
                ObjectMeta: daw.objectMeta,
23✔
350
                Spec: arcv1alpha1.ArtifactWorkflowSpec{
23✔
351
                        WorkflowTemplateRef: daw.typeSpec.WorkflowTemplateRef,
23✔
352
                        Parameters:          params,
23✔
353
                        SrcSecretRef:        daw.srcEndpoint.Spec.SecretRef,
23✔
354
                        DstSecretRef:        daw.dstEndpoint.Spec.SecretRef,
23✔
355
                        Cron:                daw.cron,
23✔
356
                },
23✔
357
        }
23✔
358

23✔
359
        return aw, nil
23✔
360
}
361

362
func (r *OrderReconciler) computeDesiredAW(ctx context.Context, log logr.Logger, order *arcv1alpha1.Order, artifact *arcv1alpha1.OrderArtifact, i int) (*desiredAW, error) {
4,177✔
363
        log = log.WithValues("artifactIndex", i)
4,177✔
364

4,177✔
365
        // We need the referenced src- and dst-endpoints for the artifact
4,177✔
366
        srcRefName := artifact.SrcRef.Name
4,177✔
367
        if srcRefName == "" {
4,580✔
368
                srcRefName = order.Spec.Defaults.SrcRef.Name
403✔
369
        }
403✔
370
        dstRefName := artifact.DstRef.Name
4,177✔
371
        if dstRefName == "" {
4,701✔
372
                dstRefName = order.Spec.Defaults.DstRef.Name
524✔
373
        }
524✔
374

375
        srcEndpoint := &arcv1alpha1.Endpoint{}
4,177✔
376
        if err := r.Get(ctx, namespacedName(order.Namespace, srcRefName), srcEndpoint); err != nil {
4,189✔
377
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Failed to fetch source endpoint '%s': %v", srcRefName, err)
12✔
378
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for source")
12✔
379
        }
12✔
380
        dstEndpoint := &arcv1alpha1.Endpoint{}
4,165✔
381
        if err := r.Get(ctx, namespacedName(order.Namespace, dstRefName), dstEndpoint); err != nil {
4,175✔
382
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Failed to fetch destination endpoint '%s': %v", dstRefName, err)
10✔
383
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for destination")
10✔
384
        }
10✔
385

386
        // Validate that the endpoint usage is correct
387
        if srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePullOnly && srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
4,169✔
388
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with source usage", srcEndpoint.Name, srcEndpoint.Spec.Usage)
14✔
389
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Source endpoint '%s' has incompatible usage '%s'", srcEndpoint.Name, srcEndpoint.Spec.Usage)
14✔
390
                return nil, errLogAndWrap(log, err, "artifact validation failed")
14✔
391
        }
14✔
392
        if dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePushOnly && dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
4,155✔
393
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with destination usage", dstEndpoint.Name, dstEndpoint.Spec.Usage)
14✔
394
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Destination endpoint '%s' has incompatible usage '%s'", dstEndpoint.Name, dstEndpoint.Spec.Usage)
14✔
395
                return nil, errLogAndWrap(log, err, "artifact validation failed")
14✔
396
        }
14✔
397

398
        // Validate against ArtifactType rules
399
        artifactType := &arcv1alpha1.ArtifactType{}
4,127✔
400
        if err := r.Get(ctx, namespacedName(order.Namespace, artifact.Type), artifactType); client.IgnoreNotFound(err) != nil {
4,127✔
401
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidArtifactType", "Failed to fetch ArtifactType '%s': %v", artifact.Type, err)
×
402
                return nil, errLogAndWrap(log, err, "failed to fetch referenced ArtifactType")
×
403
        }
×
404
        var (
4,127✔
405
                artifactTypeGen  int64
4,127✔
406
                artifactTypeSpec *arcv1alpha1.ArtifactTypeSpec
4,127✔
407
        )
4,127✔
408
        if artifactType.Name == "" { // was not found, let's check ClusterArtifactType
8,113✔
409
                clusterArtifactType := &arcv1alpha1.ClusterArtifactType{}
3,986✔
410
                if err := r.Get(ctx, namespacedName("", artifact.Type), clusterArtifactType); err != nil {
4,045✔
411
                        return nil, errLogAndWrap(log, err, "failed to fetch ArtifactType or ClusterArtifactType")
59✔
412
                }
59✔
413
                artifactTypeSpec = &clusterArtifactType.Spec
3,927✔
414
                artifactTypeGen = clusterArtifactType.Generation
3,927✔
415
                // NOTE: ClusterArtifactTypes can only reference ClusterWorkflowTemplates, so we enforce this here:
3,927✔
416
                artifactTypeSpec.WorkflowTemplateRef.ClusterScope = true
3,927✔
417
        } else {
141✔
418
                artifactTypeSpec = &artifactType.Spec
141✔
419
                artifactTypeGen = artifactType.Generation
141✔
420
        }
141✔
421

422
        if len(artifactTypeSpec.Rules.SrcTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.SrcTypes, srcEndpoint.Spec.Type) {
4,078✔
423
                err := fmt.Errorf("source endpoint type '%s' is not allowed by ArtifactType rules", srcEndpoint.Spec.Type)
10✔
424
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidArtifactType", "Source endpoint type '%s' is not allowed by ArtifactType '%s' rules", srcEndpoint.Spec.Type, artifact.Type)
10✔
425
                return nil, errLogAndWrap(log, err, "artifact validation failed")
10✔
426
        }
10✔
427
        if len(artifactTypeSpec.Rules.DstTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.DstTypes, dstEndpoint.Spec.Type) {
4,058✔
428
                err := fmt.Errorf("destination endpoint type '%s' is not allowed by ArtifactType rules", dstEndpoint.Spec.Type)
×
429
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidArtifactType", "Destination endpoint type '%s' is not allowed by ArtifactType '%s' rules", dstEndpoint.Spec.Type, artifact.Type)
×
430
                return nil, errLogAndWrap(log, err, "artifact validation failed")
×
431
        }
×
432

433
        // Next, we need the secret contents
434
        srcSecret := &corev1.Secret{}
4,058✔
435
        if srcEndpoint.Spec.SecretRef.Name != "" {
8,114✔
436
                if err := r.Get(ctx, namespacedName(order.Namespace, srcEndpoint.Spec.SecretRef.Name), srcSecret); err != nil {
4,056✔
437
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidSecret", "Failed to fetch source secret '%s': %v", srcEndpoint.Spec.SecretRef.Name, err)
×
438
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for source")
×
439
                }
×
440
        }
441

442
        dstSecret := &corev1.Secret{}
4,058✔
443
        if dstEndpoint.Spec.SecretRef.Name != "" {
8,114✔
444
                if err := r.Get(ctx, namespacedName(order.Namespace, dstEndpoint.Spec.SecretRef.Name), dstSecret); err != nil {
4,056✔
445
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidSecret", "Failed to fetch destination secret '%s': %v", dstEndpoint.Spec.SecretRef.Name, err)
×
446
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for destination")
×
447
                }
×
448
        }
449

450
        // Cron schedule if any
451
        cron := artifact.Cron
4,058✔
452
        if cron == nil {
8,116✔
453
                cron = order.Spec.Defaults.Cron
4,058✔
454
        }
4,058✔
455

456
        // Create a hash based on all related data for idempotency and compute the workflow name
457
        h := sha256.New()
4,058✔
458
        data := []any{
4,058✔
459
                order.Namespace,
4,058✔
460
                artifact.Type,
4,058✔
461
                artifact.Spec.Raw,
4,058✔
462
                artifactTypeGen,
4,058✔
463
                srcEndpoint.Name,
4,058✔
464
                dstEndpoint.Name,
4,058✔
465
                order.Status.LastForceAt,
4,058✔
466
                cron,
4,058✔
467
        }
4,058✔
468

4,058✔
469
        if err := json.NewEncoder(h).Encode(data); err != nil {
4,058✔
470
                return nil, errLogAndWrap(log, err, "failed to marshal artifact workflow data")
×
471
        }
×
472

473
        sha := hex.EncodeToString(h.Sum(nil))[:16]
4,058✔
474

4,058✔
475
        // We gave all the information to further process this artifact workflow.
4,058✔
476
        // Let's store it to compare it to the current status!
4,058✔
477
        return &desiredAW{
4,058✔
478
                index:       i,
4,058✔
479
                objectMeta:  awObjectMeta(order, sha),
4,058✔
480
                artifact:    artifact,
4,058✔
481
                typeSpec:    artifactTypeSpec,
4,058✔
482
                srcEndpoint: srcEndpoint,
4,058✔
483
                dstEndpoint: dstEndpoint,
4,058✔
484
                srcSecret:   srcSecret,
4,058✔
485
                dstSecret:   dstSecret,
4,058✔
486
                sha:         sha,
4,058✔
487
                cron:        cron,
4,058✔
488
        }, nil
4,058✔
489
}
490

491
// SetupWithManager sets up the controller with the Manager.
492
func (r *OrderReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
493
        return ctrl.NewControllerManagedBy(mgr).
1✔
494
                For(&arcv1alpha1.Order{}).
1✔
495
                Owns(&arcv1alpha1.ArtifactWorkflow{}).
1✔
496
                Complete(r)
1✔
497
}
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