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

opendefensecloud / artifact-conduit / 20812361575

08 Jan 2026 09:40AM UTC coverage: 84.169% (+0.04%) from 84.127%
20812361575

Pull #167

github

web-flow
Merge 88d1a95dd into f2ce51643
Pull Request #167: Feature/cluster endpoint

50 of 57 new or added lines in 2 files covered. (87.72%)

6 existing lines in 2 files now uncovered.

755 of 897 relevant lines covered (84.17%)

881.29 hits per line

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

83.38
/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
        srcEndpointSpec *arcv1alpha1.EndpointSpec
44
        dstEndpointSpec *arcv1alpha1.EndpointSpec
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=clusterendpoints,verbs=get;list;watch
53
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=artifacttypes,verbs=get;list;watch
54
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=clusterartifacttypes,verbs=get;list;watch
55
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=artifactworkflows,verbs=get;list;watch;create;update;patch;delete
56
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=orders,verbs=get;list;watch;create;update;patch;delete
57
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=orders/status,verbs=get;update;patch
58
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=orders/finalizers,verbs=update
59
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
60
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
61

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

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

77
        // Update last reconcile time
78
        order.Status.LastReconcileAt = metav1.Now()
1,872✔
79

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

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

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

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

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

156
        // Make sure status is initialized
157
        if order.Status.ArtifactWorkflows == nil {
1,945✔
158
                order.Status.ArtifactWorkflows = map[string]arcv1alpha1.OrderArtifactWorkflowStatus{}
93✔
159
        }
93✔
160

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

1,714✔
178
        // List missing artifact workflows
1,714✔
179
        createAWs := []string{}
1,714✔
180
        for sha := range desiredAWs {
4,461✔
181
                _, exists := order.Status.ArtifactWorkflows[sha]
2,747✔
182
                if exists {
5,473✔
183
                        continue
2,726✔
184
                }
185
                createAWs = append(createAWs, sha)
21✔
186
        }
187

188
        // Find obsolete artifact workflows
189
        deleteAWs := []string{}
1,714✔
190
        for sha := range order.Status.ArtifactWorkflows {
4,442✔
191
                _, exists := desiredAWs[sha]
2,728✔
192
                if exists {
5,454✔
193
                        continue
2,726✔
194
                }
195
                deleteAWs = append(deleteAWs, sha)
2✔
196
        }
197

198
        // Find finished artifact workflows to clean up
199
        finishedAWs := []string{}
1,714✔
200
        for sha := range order.Status.ArtifactWorkflows {
4,442✔
201
                awStatus := order.Status.ArtifactWorkflows[sha]
2,728✔
202

2,728✔
203
                // Only consider succeeded workflows for TTL cleanup
2,728✔
204
                if awStatus.Phase != arcv1alpha1.WorkflowSucceeded {
5,375✔
205
                        continue
2,647✔
206
                }
207

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

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

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

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

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

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

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

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

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

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

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

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

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

339
        return ctrlResult, nil
1,669✔
340
}
341

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

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

21✔
360
        return aw, nil
21✔
361
}
362

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

2,891✔
366
        // We need the referenced src- and dst-endpoints for the artifact
2,891✔
367
        srcRefName := artifact.SrcRef.Name
2,891✔
368
        if srcRefName == "" {
3,292✔
369
                srcRefName = order.Spec.Defaults.SrcRef.Name
401✔
370
        }
401✔
371
        dstRefName := artifact.DstRef.Name
2,891✔
372
        if dstRefName == "" {
3,419✔
373
                dstRefName = order.Spec.Defaults.DstRef.Name
528✔
374
        }
528✔
375

376
        srcEPMeta, srcEPSpec, err := r.GetAnyEndpoint(ctx, order.Namespace, srcRefName)
2,891✔
377
        if err != nil {
2,903✔
378
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Failed to fetch source endpoint '%s': %v", srcRefName, err)
12✔
379
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for source")
12✔
380
        }
12✔
381

382
        dstEPMeta, dstEPSpec, err := r.GetAnyEndpoint(ctx, order.Namespace, dstRefName)
2,879✔
383
        if err != nil {
2,891✔
384
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Failed to fetch destination endpoint '%s': %v", dstRefName, err)
12✔
385
                return nil, errLogAndWrap(log, err, "failed to fetch endpoint for destination")
12✔
386
        }
12✔
387

388
        // Validate that the endpoint usage is correct
389
        if srcEPSpec.Usage != arcv1alpha1.EndpointUsagePullOnly && srcEPSpec.Usage != arcv1alpha1.EndpointUsageAll {
2,881✔
390
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with source usage", srcEPMeta.Name, srcEPSpec.Usage)
14✔
391
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Source endpoint '%s' has incompatible usage '%s'", srcEPMeta.Name, srcEPSpec.Usage)
14✔
392
                return nil, errLogAndWrap(log, err, "artifact validation failed")
14✔
393
        }
14✔
394
        if dstEPSpec.Usage != arcv1alpha1.EndpointUsagePushOnly && dstEPSpec.Usage != arcv1alpha1.EndpointUsageAll {
2,867✔
395
                err := fmt.Errorf("endpoint '%s' usage '%s' is not compatible with destination usage", dstEPMeta.Name, dstEPSpec.Usage)
14✔
396
                r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidEndpoint", "Destination endpoint '%s' has incompatible usage '%s'", dstEPMeta.Name, dstEPSpec.Usage)
14✔
397
                return nil, errLogAndWrap(log, err, "artifact validation failed")
14✔
398
        }
14✔
399

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

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

435
        // Next, we need the secret contents
436
        srcSecret := &corev1.Secret{}
2,753✔
437
        if srcEPSpec.SecretRef.Name != "" {
5,502✔
438
                if err := r.Get(ctx, namespacedName(order.Namespace, srcEPSpec.SecretRef.Name), srcSecret); err != nil {
2,749✔
NEW
439
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidSecret", "Failed to fetch source secret '%s': %v", srcEPSpec.SecretRef.Name, err)
×
440
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for source")
×
441
                }
×
442
        }
443

444
        dstSecret := &corev1.Secret{}
2,753✔
445
        if dstEPSpec.SecretRef.Name != "" {
5,502✔
446
                if err := r.Get(ctx, namespacedName(order.Namespace, dstEPSpec.SecretRef.Name), dstSecret); err != nil {
2,749✔
NEW
447
                        r.Recorder.Eventf(order, corev1.EventTypeWarning, "InvalidSecret", "Failed to fetch destination secret '%s': %v", dstEPSpec.SecretRef.Name, err)
×
448
                        return nil, errLogAndWrap(log, err, "failed to fetch secret for destination")
×
449
                }
×
450
        }
451

452
        // Cron schedule if any
453
        cron := artifact.Cron
2,753✔
454
        if cron == nil {
5,506✔
455
                cron = order.Spec.Defaults.Cron
2,753✔
456
        }
2,753✔
457

458
        // Create a hash based on all related data for idempotency and compute the workflow name
459
        h := sha256.New()
2,753✔
460
        data := []any{
2,753✔
461
                order.Namespace,
2,753✔
462
                artifact.Type, artifact.Spec.Raw, artifactTypeGen,
2,753✔
463
                srcEPMeta.Name,
2,753✔
464
                dstEPMeta.Name,
2,753✔
465
                order.Status.LastForceAt,
2,753✔
466
                cron,
2,753✔
467
        }
2,753✔
468
        jsonData, err := json.Marshal(data)
2,753✔
469
        if err != nil {
2,753✔
470
                return nil, errLogAndWrap(log, err, "failed to marshal artifact workflow data")
×
471
        }
×
472
        h.Write(jsonData)
2,753✔
473
        sha := hex.EncodeToString(h.Sum(nil))[:16]
2,753✔
474

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

491
func (r *OrderReconciler) GetAnyEndpoint(ctx context.Context, namespace, name string) (*metav1.ObjectMeta, *arcv1alpha1.EndpointSpec, error) {
5,770✔
492
        nsEndpoint := &arcv1alpha1.Endpoint{}
5,770✔
493

5,770✔
494
        err := r.Get(ctx, namespacedName(namespace, name), nsEndpoint)
5,770✔
495
        if client.IgnoreNotFound(err) != nil {
5,770✔
NEW
496
                return nil, nil, fmt.Errorf("failed to fetch namespaced endpoint '%s': %w", name, err)
×
NEW
497
        }
×
498

499
        if apierrors.IsNotFound(err) {
5,794✔
500
                clusterEndpoint := &arcv1alpha1.ClusterEndpoint{}
24✔
501

24✔
502
                if err := r.Get(ctx, namespacedName("", name), clusterEndpoint); err != nil {
48✔
503
                        return nil, nil, fmt.Errorf("failed to fetch cluster or namespaced endpoint '%s': %w", name, err)
24✔
504
                }
24✔
505

NEW
506
                return &clusterEndpoint.ObjectMeta, &clusterEndpoint.Spec, nil
×
507

508
        }
509
        return &nsEndpoint.ObjectMeta, &nsEndpoint.Spec, nil
5,746✔
510
}
511

512
// SetupWithManager sets up the controller with the Manager.
513
func (r *OrderReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
514
        return ctrl.NewControllerManagedBy(mgr).
1✔
515
                For(&arcv1alpha1.Order{}).
1✔
516
                Owns(&arcv1alpha1.ArtifactWorkflow{}).
1✔
517
                Complete(r)
1✔
518
}
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