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

opendefensecloud / artifact-conduit / 25494614000

07 May 2026 12:03PM UTC coverage: 84.375% (-0.3%) from 84.698%
25494614000

push

github

web-flow
chore: prevent cached results for e2e tests (#343)

## What
Same as for Solar:
https://github.com/opendefensecloud/solution-arsenal/pull/503

## Why
Go's test cache works by hashing the test binary plus any other state it
can observe. For the e2e tests, the results depend on external world
state that Go can't observe and so can't hash.

## Testing
`make test-e2e`

## Checklist
- [x] ~~Tests added/updated~~ n/a
- [x] No breaking changes (or upgrade path documented above)
- [x] Readable commit history (squashed and cleaned up as desired)
- [ ] AI code review considered and comments resolved

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Updated E2E test execution to ensure consistent test runs without
relying on cached results.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

783 of 928 relevant lines covered (84.38%)

441.29 hits per line

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

83.69
/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
        corev1 "k8s.io/api/core/v1"
17
        apierrors "k8s.io/apimachinery/pkg/api/errors"
18
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
        "k8s.io/apimachinery/pkg/runtime"
20
        "k8s.io/apimachinery/pkg/types"
21
        "k8s.io/client-go/tools/events"
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
        arcv1alpha1 "go.opendefense.cloud/arc/api/arc/v1alpha1"
27
)
28

29
const (
30
        orderFinalizer = "arc.opendefense.cloud/order-finalizer"
31
)
32

33
// OrderReconciler reconciles a Order object
34
type OrderReconciler struct {
35
        client.Client
36
        Scheme   *runtime.Scheme
37
        Recorder events.EventRecorder
38
}
39

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

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

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

1,026✔
68
        // Fetch the Order instance
1,026✔
69
        order := &arcv1alpha1.Order{}
1,026✔
70
        if err := r.Get(ctx, req.NamespacedName, order); err != nil {
1,028✔
71
                if apierrors.IsNotFound(err) {
4✔
72
                        // Object not found, return. Created objects are automatically garbage collected.
2✔
73
                        return ctrlResult, nil
2✔
74
                }
2✔
75

76
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
77
        }
78

79
        // Update last reconcile time
80
        order.Status.LastReconcileAt = metav1.Now()
1,024✔
81

1,024✔
82
        // Handle deletion: cleanup artifact workflows, then remove finalizer
1,024✔
83
        if !order.DeletionTimestamp.IsZero() {
1,026✔
84
                log.V(1).Info("Order is being deleted")
2✔
85
                r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "Deleting", "Delete", "Order is being deleted, cleaning up artifact workflows")
2✔
86

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

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

116
                return ctrlResult, nil
1✔
117
        }
118

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

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

159
        // Make sure status is initialized
160
        if order.Status.ArtifactWorkflows == nil {
1,096✔
161
                order.Status.ArtifactWorkflows = map[string]arcv1alpha1.OrderArtifactWorkflowStatus{}
95✔
162
        }
95✔
163

164
        // Before we compare to our status, let's fetch all necessary information
165
        // to compute desired state:
166
        desiredAWs := map[string]desiredAW{}
1,001✔
167
        for i, artifact := range order.Spec.Artifacts {
2,476✔
168
                daw, err := r.computeDesiredAW(ctx, log, order, &artifact, i)
1,475✔
169
                if err != nil {
1,594✔
170
                        r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "ComputationFailed", "Compute", "Failed to compute desired artifact workflow for artifact index %d: %v", i, err)
119✔
171
                        order.Status.Message = fmt.Sprintf("Failed to compute desired artifact workflow for artifact index %d: %v", i, err)
119✔
172
                        if err := r.Status().Update(ctx, order); err != nil {
120✔
173
                                return ctrlResult, errLogAndWrap(log, err, "failed to update status")
1✔
174
                        }
1✔
175

176
                        return ctrlResult, errLogAndWrap(log, err, "failed to compute desired artifact workflow")
118✔
177
                }
178
                desiredAWs[daw.sha] = *daw
1,356✔
179
        }
180
        order.Status.Message = "" // Clear any previous error message
882✔
181

882✔
182
        // List missing artifact workflows
882✔
183
        var createAWs []string
882✔
184
        for sha := range desiredAWs {
2,232✔
185
                if _, exists := order.Status.ArtifactWorkflows[sha]; exists {
2,673✔
186
                        continue
1,323✔
187
                }
188

189
                createAWs = append(createAWs, sha)
27✔
190
        }
191

192
        // Find obsolete artifact workflows
193
        var deleteAWs []string
882✔
194
        for sha := range order.Status.ArtifactWorkflows {
2,206✔
195
                if _, exists := desiredAWs[sha]; exists {
2,647✔
196
                        continue
1,323✔
197
                }
198

199
                deleteAWs = append(deleteAWs, sha)
1✔
200
        }
201

202
        // Find finished artifact workflows to clean up
203
        var finishedAWs []string
882✔
204
        for sha := range order.Status.ArtifactWorkflows {
2,206✔
205
                awStatus := order.Status.ArtifactWorkflows[sha]
1,324✔
206

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

212
                // Do not clean up workflows that are still running or pending
213
                switch awStatus.Phase {
1,322✔
214
                case arcv1alpha1.WorkflowSucceeded:
44✔
215
                case arcv1alpha1.WorkflowFailed:
×
216
                case arcv1alpha1.WorkflowError:
4✔
217
                default:
1,274✔
218
                        continue
1,274✔
219
                }
220

221
                // Get ArtifactWorkflow object and check TTLs.
222
                artifactWorkflow := &arcv1alpha1.ArtifactWorkflow{}
48✔
223
                if err := r.Get(ctx, types.NamespacedName{Namespace: order.Namespace, Name: awName(order, sha)}, artifactWorkflow); err != nil && !apierrors.IsNotFound(err) {
48✔
224
                        r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "Invalid", "Fetch", "Failed to fetch ArtifactWorkflow: %v", sha)
×
225
                        return ctrlResult, errLogAndWrap(log, err, "")
×
226
                }
×
227
                if artifactWorkflow.Name != "" {
59✔
228
                        // Cleanup finished workflows if TTLAfterFinished is set.
11✔
229
                        if awStatus.Phase == arcv1alpha1.WorkflowSucceeded {
19✔
230
                                // If TTL is set, check if it has expired
8✔
231
                                if artifactWorkflow.Spec.TTLAfterFinished != nil {
11✔
232
                                        if artifactWorkflow.Spec.TTLAfterFinished.Seconds() == 0 {
3✔
233
                                                // If TTL is zero keep the workflow.
×
234
                                                continue
×
235
                                        }
236
                                        if time.Since(awStatus.CompletionTime.Time) < artifactWorkflow.Spec.TTLAfterFinished.Duration {
4✔
237
                                                // If TTL is set but not expired keep the workflow.
1✔
238
                                                // Requeue when the next TTL expires
1✔
239
                                                ctrlResult.RequeueAfter = artifactWorkflow.Spec.TTLAfterFinished.Duration - time.Since(awStatus.CompletionTime.Time)
1✔
240
                                                continue
1✔
241
                                        }
242
                                }
243
                        }
244

245
                        // Cleanup failed workflows if TTLAfterFailed is set.
246
                        if awStatus.Phase == arcv1alpha1.WorkflowFailed || awStatus.Phase == arcv1alpha1.WorkflowError {
13✔
247
                                // If TTL is set, check if it has expired
3✔
248
                                if artifactWorkflow.Spec.TTLAfterFailed != nil {
6✔
249
                                        if artifactWorkflow.Spec.TTLAfterFailed.Seconds() == 0 {
3✔
250
                                                // If TTL is zero keep the workflow.
×
251
                                                continue
×
252
                                        }
253
                                        if time.Since(awStatus.FailureTime.Time) < artifactWorkflow.Spec.TTLAfterFailed.Duration {
4✔
254
                                                // If TTL is set but not expired keep the workflow.
1✔
255
                                                ctrlResult.RequeueAfter = artifactWorkflow.Spec.TTLAfterFailed.Duration - time.Since(awStatus.FailureTime.Time)
1✔
256
                                                continue
1✔
257
                                        }
258
                                } else {
×
259
                                        // If no TTL is set keep the workflow.
×
260
                                        continue
×
261
                                }
262
                        }
263
                }
264

265
                // Cleanup finished or not existing workflows
266
                finishedAWs = append(finishedAWs, sha)
46✔
267
        }
268

269
        // Create missing artifact workflows
270
        for _, sha := range createAWs {
909✔
271
                daw := desiredAWs[sha]
27✔
272
                aw, err := r.hydrateArtifactWorkflow(&daw)
27✔
273
                if err != nil {
27✔
274
                        r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "HydrationFailed", "Hydrate", "Failed to hydrate artifact workflow for artifact index %d: %v", daw.index, err)
×
275
                        return ctrlResult, errLogAndWrap(log, err, "failed to hydrate artifact workflow")
×
276
                }
×
277

278
                // Set owner references
279
                if err := controllerutil.SetControllerReference(order, aw, r.Scheme); err != nil {
27✔
280
                        r.Recorder.Eventf(order, aw, corev1.EventTypeWarning, "HydrationFailed", "Hydrate", "Failed to set controller reference for artifact workflow: %v", err)
×
281
                        return ctrlResult, errLogAndWrap(log, err, "failed to set controller reference")
×
282
                }
×
283

284
                // Create artifact workflow
285
                if err := r.Create(ctx, aw); err != nil {
31✔
286
                        if apierrors.IsAlreadyExists(err) {
8✔
287
                                // Already created by a previous reconcile — that's fine
4✔
288
                                continue
4✔
289
                        }
290
                        r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create artifact workflow for artifact index %d: %v", daw.index, err)
×
291

×
292
                        return ctrlResult, errLogAndWrap(log, err, "failed to create artifact workflow")
×
293
                } else {
23✔
294
                        r.Recorder.Eventf(order, aw, corev1.EventTypeNormal, "Created", "Create", "Created artifact workflow '%s' for artifact index %d", aw.Name, daw.index)
23✔
295
                        log.V(1).Info("Created artifact workflow", "artifactWorkflow", aw.Name)
23✔
296
                }
23✔
297

298
                // Update status
299
                order.Status.ArtifactWorkflows[sha] = arcv1alpha1.OrderArtifactWorkflowStatus{
23✔
300
                        ArtifactIndex: daw.index,
23✔
301
                        WorkflowStatus: arcv1alpha1.WorkflowStatus{
23✔
302
                                Phase: arcv1alpha1.WorkflowUnknown,
23✔
303
                        },
23✔
304
                }
23✔
305
        }
306

307
        // Delete obsolete artifact workflows
308
        for _, sha := range deleteAWs {
883✔
309
                // Does not exist anymore, let's clean up!
1✔
310
                aw := &arcv1alpha1.ArtifactWorkflow{
1✔
311
                        ObjectMeta: awObjectMeta(order, sha),
1✔
312
                }
1✔
313
                if err := r.Delete(ctx, aw); client.IgnoreNotFound(err) != nil {
1✔
314
                        r.Recorder.Eventf(order, aw, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete obsolete artifact workflow '%s': %v", sha, err)
×
315
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
316
                }
×
317

318
                // Update status
319
                delete(order.Status.ArtifactWorkflows, sha)
1✔
320
                log.V(1).Info("Deleted obsolete artifact workflow", "artifactWorkflow", sha)
1✔
321
                r.Recorder.Eventf(order, aw, corev1.EventTypeNormal, "Deleted", "Delete", "Deleted obsolete artifact workflow '%s'", sha)
1✔
322
        }
323

324
        // Delete finished artifact workflows
325
        for _, sha := range finishedAWs {
928✔
326
                // Finished, let's clean up!
46✔
327
                aw := &arcv1alpha1.ArtifactWorkflow{
46✔
328
                        ObjectMeta: awObjectMeta(order, sha),
46✔
329
                }
46✔
330
                if err := r.Delete(ctx, aw); client.IgnoreNotFound(err) != nil {
46✔
331
                        r.Recorder.Eventf(order, aw, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete finished artifact workflow '%s': %v", sha, err)
×
332
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete artifact workflow")
×
333
                }
×
334

335
                log.V(1).Info("Deleted finished artifact workflow", "artifactWorkflow", sha)
46✔
336
                r.Recorder.Eventf(order, aw, corev1.EventTypeNormal, "Deleted", "Delete", "Deleted finished artifact workflow '%s'", sha)
46✔
337
        }
338

339
        anyPhaseChanged := false
882✔
340
        for sha, daw := range desiredAWs {
2,232✔
341
                if slices.Contains(createAWs, sha) {
1,377✔
342
                        // If it was just created we skip the update
27✔
343
                        continue
27✔
344
                }
345
                if daw.cron == nil && order.Status.ArtifactWorkflows[sha].Phase.Completed() {
1,371✔
346
                        // We do not need to check for updates if the workflow is completed and is NOT cron
48✔
347
                        continue
48✔
348
                }
349
                aw := arcv1alpha1.ArtifactWorkflow{}
1,275✔
350
                if err := r.Get(ctx, namespacedName(daw.objectMeta.Namespace, daw.objectMeta.Name), &aw); err != nil {
1,275✔
351
                        delete(order.Status.ArtifactWorkflows, sha)
×
352
                        log.V(1).Info("Artifact workflow not found, deleting from status.", "artifactWorkflow", sha)
×
353
                        if err := r.Status().Update(ctx, order); err != nil {
×
354
                                return ctrlResult, errLogAndWrap(log, err, "failed to update status")
×
355
                        }
×
356

357
                        return ctrlResult, errLogAndWrap(log, err, "failed to get artifact workflow")
×
358
                }
359
                orderAWStatus := order.Status.ArtifactWorkflows[sha]
1,275✔
360
                if orderAWStatus.Phase != aw.Status.Phase ||
1,275✔
361
                        orderAWStatus.Succeeded != aw.Status.Succeeded ||
1,275✔
362
                        orderAWStatus.Failed != aw.Status.Failed ||
1,275✔
363
                        !orderAWStatus.LastScheduled.Equal(aw.Status.LastScheduled) {
1,803✔
364
                        orderAWStatus.WorkflowStatus = aw.Status.WorkflowStatus
528✔
365
                        order.Status.ArtifactWorkflows[sha] = orderAWStatus
528✔
366
                        anyPhaseChanged = true
528✔
367
                }
528✔
368
        }
369

370
        // Update status
371
        if len(createAWs) > 0 || len(deleteAWs) > 0 || anyPhaseChanged {
1,404✔
372
                log.V(1).Info("Updating order status")
522✔
373
                // Make sure ArtifactIndex is up to date
522✔
374
                for sha, daw := range desiredAWs {
1,330✔
375
                        aws := order.Status.ArtifactWorkflows[sha]
808✔
376
                        aws.ArtifactIndex = daw.index
808✔
377
                        order.Status.ArtifactWorkflows[sha] = aws
808✔
378
                }
808✔
379
                if err := r.Status().Update(ctx, order); err != nil {
548✔
380
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
26✔
381
                }
26✔
382
        }
383

384
        return ctrlResult, nil
856✔
385
}
386

387
func (r *OrderReconciler) hydrateArtifactWorkflow(daw *desiredAW) (*arcv1alpha1.ArtifactWorkflow, error) {
27✔
388
        params, err := dawToParameters(daw)
27✔
389
        if err != nil {
27✔
390
                return nil, err
×
391
        }
×
392

393
        // Next we create the ArtifactWorkflow instance
394
        aw := &arcv1alpha1.ArtifactWorkflow{
27✔
395
                ObjectMeta: daw.objectMeta,
27✔
396
                Spec: arcv1alpha1.ArtifactWorkflowSpec{
27✔
397
                        WorkflowTemplateRef:         daw.typeSpec.WorkflowTemplateRef,
27✔
398
                        Parameters:                  params,
27✔
399
                        SrcSecretRef:                daw.srcEndpoint.Spec.SecretRef,
27✔
400
                        DstSecretRef:                daw.dstEndpoint.Spec.SecretRef,
27✔
401
                        Cron:                        daw.cron,
27✔
402
                        ArtifactWorkflowTTLSettings: daw.typeSpec.ArtifactWorkflowTTLSettings,
27✔
403
                },
27✔
404
        }
27✔
405

27✔
406
        return aw, nil
27✔
407
}
408

409
func (r *OrderReconciler) computeDesiredAW(ctx context.Context, log logr.Logger, order *arcv1alpha1.Order, artifact *arcv1alpha1.OrderArtifact, i int) (*desiredAW, error) {
1,475✔
410
        log = log.WithValues("artifactIndex", i)
1,475✔
411

1,475✔
412
        // We need the referenced src- and dst-endpoints for the artifact
1,475✔
413
        srcRefName := artifact.SrcRef.Name
1,475✔
414
        if srcRefName == "" {
1,651✔
415
                srcRefName = order.Spec.Defaults.SrcRef.Name
176✔
416
        }
176✔
417
        dstRefName := artifact.DstRef.Name
1,475✔
418
        if dstRefName == "" {
1,712✔
419
                dstRefName = order.Spec.Defaults.DstRef.Name
237✔
420
        }
237✔
421

422
        srcEndpoint := &arcv1alpha1.Endpoint{}
1,475✔
423
        if err := r.Get(ctx, namespacedName(order.Namespace, srcRefName), srcEndpoint); err != nil {
1,488✔
424
                r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "InvalidEndpoint", "FetchEndpoint", "Failed to fetch source endpoint '%s': %v", srcRefName, err)
13✔
425
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for source")
13✔
426
        }
13✔
427
        dstEndpoint := &arcv1alpha1.Endpoint{}
1,462✔
428
        if err := r.Get(ctx, namespacedName(order.Namespace, dstRefName), dstEndpoint); err != nil {
1,474✔
429
                r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "InvalidEndpoint", "FetchEndpoint", "Failed to fetch destination endpoint '%s': %v", dstRefName, err)
12✔
430
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for destination")
12✔
431
        }
12✔
432

433
        // Validate that the endpoint usage is correct
434
        if srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePullOnly && srcEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
1,463✔
435
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with source usage", srcEndpoint.Name, srcEndpoint.Spec.Usage)
13✔
436
                r.Recorder.Eventf(order, srcEndpoint, corev1.EventTypeWarning, "InvalidEndpoint", "ValidateEndpoint", "Source endpoint '%s' has incompatible usage '%s'", srcEndpoint.Name, srcEndpoint.Spec.Usage)
13✔
437

13✔
438
                return nil, errLogAndWrap(log, err, "artifact validation failed")
13✔
439
        }
13✔
440
        if dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePushOnly && dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
1,450✔
441
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with destination usage", dstEndpoint.Name, dstEndpoint.Spec.Usage)
13✔
442
                r.Recorder.Eventf(order, dstEndpoint, corev1.EventTypeWarning, "InvalidEndpoint", "ValidateEndpoint", "Destination endpoint '%s' has incompatible usage '%s'", dstEndpoint.Name, dstEndpoint.Spec.Usage)
13✔
443

13✔
444
                return nil, errLogAndWrap(log, err, "artifact validation failed")
13✔
445
        }
13✔
446

447
        // Validate against ArtifactType rules
448
        artifactType := &arcv1alpha1.ArtifactType{}
1,424✔
449
        if err := r.Get(ctx, namespacedName(order.Namespace, artifact.Type), artifactType); client.IgnoreNotFound(err) != nil {
1,424✔
450
                r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "InvalidArtifactType", "FetchArtifactType", "Failed to fetch ArtifactType '%s': %v", artifact.Type, err)
×
451
                return nil, errLogAndWrap(log, err, "failed to fetch referenced ArtifactType")
×
452
        }
×
453
        var (
1,424✔
454
                artifactTypeGen  int64
1,424✔
455
                artifactTypeSpec *arcv1alpha1.ArtifactTypeSpec
1,424✔
456
        )
1,424✔
457
        if artifactType.Name == "" { // was not found, let's check ClusterArtifactType
2,798✔
458
                clusterArtifactType := &arcv1alpha1.ClusterArtifactType{}
1,374✔
459
                if err := r.Get(ctx, namespacedName("", artifact.Type), clusterArtifactType); err != nil {
1,432✔
460
                        return nil, errLogAndWrap(log, err, "failed to fetch ArtifactType or ClusterArtifactType")
58✔
461
                }
58✔
462
                artifactTypeSpec = &clusterArtifactType.Spec
1,316✔
463
                artifactTypeGen = clusterArtifactType.Generation
1,316✔
464
                // NOTE: ClusterArtifactTypes can only reference ClusterWorkflowTemplates, so we enforce this here:
1,316✔
465
                artifactTypeSpec.WorkflowTemplateRef.ClusterScope = true
1,316✔
466
        } else {
50✔
467
                artifactTypeSpec = &artifactType.Spec
50✔
468
                artifactTypeGen = artifactType.Generation
50✔
469
        }
50✔
470

471
        if len(artifactTypeSpec.Rules.SrcTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.SrcTypes, srcEndpoint.Spec.Type) {
1,376✔
472
                err := fmt.Errorf("source endpoint type '%s' is not allowed by ArtifactType rules", srcEndpoint.Spec.Type)
10✔
473
                r.Recorder.Eventf(order, artifactType, corev1.EventTypeWarning, "InvalidArtifactType", "ValidateArtifactType", "Source endpoint type '%s' is not allowed by ArtifactType '%s' rules", srcEndpoint.Spec.Type, artifact.Type)
10✔
474

10✔
475
                return nil, errLogAndWrap(log, err, "artifact validation failed")
10✔
476
        }
10✔
477
        if len(artifactTypeSpec.Rules.DstTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.DstTypes, dstEndpoint.Spec.Type) {
1,356✔
478
                err := fmt.Errorf("destination endpoint type '%s' is not allowed by ArtifactType rules", dstEndpoint.Spec.Type)
×
479
                r.Recorder.Eventf(order, artifactType, corev1.EventTypeWarning, "InvalidArtifactType", "ValidateArtifactType", "Destination endpoint type '%s' is not allowed by ArtifactType '%s' rules", dstEndpoint.Spec.Type, artifact.Type)
×
480

×
481
                return nil, errLogAndWrap(log, err, "artifact validation failed")
×
482
        }
×
483

484
        // Next, we need the secret contents
485
        srcSecret := &corev1.Secret{}
1,356✔
486
        if srcEndpoint.Spec.SecretRef.Name != "" {
2,679✔
487
                if err := r.Get(ctx, namespacedName(order.Namespace, srcEndpoint.Spec.SecretRef.Name), srcSecret); err != nil {
1,323✔
488
                        r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "InvalidSecret", "FetchSecret", "Failed to fetch source secret '%s': %v", srcEndpoint.Spec.SecretRef.Name, err)
×
489
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for source")
×
490
                }
×
491
        }
492

493
        dstSecret := &corev1.Secret{}
1,356✔
494
        if dstEndpoint.Spec.SecretRef.Name != "" {
2,679✔
495
                if err := r.Get(ctx, namespacedName(order.Namespace, dstEndpoint.Spec.SecretRef.Name), dstSecret); err != nil {
1,323✔
496
                        r.Recorder.Eventf(order, nil, corev1.EventTypeWarning, "InvalidSecret", "FetchSecret", "Failed to fetch destination secret '%s': %v", dstEndpoint.Spec.SecretRef.Name, err)
×
497
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for destination")
×
498
                }
×
499
        }
500

501
        // Cron schedule if any
502
        cron := artifact.Cron
1,356✔
503
        if cron == nil {
2,708✔
504
                cron = order.Spec.Defaults.Cron
1,352✔
505
        }
1,352✔
506

507
        // Create a hash based on all related data for idempotency and compute the workflow name
508
        h := sha256.New()
1,356✔
509
        data := []any{
1,356✔
510
                order.Namespace,
1,356✔
511
                artifact.Type,
1,356✔
512
                artifact.Spec.Raw,
1,356✔
513
                artifactTypeGen,
1,356✔
514
                srcEndpoint.Name,
1,356✔
515
                dstEndpoint.Name,
1,356✔
516
                order.Status.LastForceAt,
1,356✔
517
                cron,
1,356✔
518
        }
1,356✔
519

1,356✔
520
        if err := json.NewEncoder(h).Encode(data); err != nil {
1,356✔
521
                return nil, errLogAndWrap(log, err, "failed to marshal artifact workflow data")
×
522
        }
×
523

524
        sha := hex.EncodeToString(h.Sum(nil))[:16]
1,356✔
525

1,356✔
526
        // We gave all the information to further process this artifact workflow.
1,356✔
527
        // Let's store it to compare it to the current status!
1,356✔
528
        return &desiredAW{
1,356✔
529
                index:       i,
1,356✔
530
                objectMeta:  awObjectMeta(order, sha),
1,356✔
531
                artifact:    artifact,
1,356✔
532
                typeSpec:    artifactTypeSpec,
1,356✔
533
                srcEndpoint: srcEndpoint,
1,356✔
534
                dstEndpoint: dstEndpoint,
1,356✔
535
                srcSecret:   srcSecret,
1,356✔
536
                dstSecret:   dstSecret,
1,356✔
537
                sha:         sha,
1,356✔
538
                cron:        cron,
1,356✔
539
        }, nil
1,356✔
540
}
541

542
// SetupWithManager sets up the controller with the Manager.
543
func (r *OrderReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
544
        return ctrl.NewControllerManagedBy(mgr).
1✔
545
                For(&arcv1alpha1.Order{}).
1✔
546
                Owns(&arcv1alpha1.ArtifactWorkflow{}).
1✔
547
                Complete(r)
1✔
548
}
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