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

opendefensecloud / artifact-conduit / 26758376015

01 Jun 2026 01:36PM UTC coverage: 84.052% (+0.1%) from 83.944%
26758376015

push

github

web-flow
chore(deps): update golang version sync (#346)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | | patch | `1.26.2` →
`1.26.3` |
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | golang | patch |
`1.26.2` → `1.26.3` |
| golang | stage | pinDigest |  → `2d6c802` |

---

### Release Notes

<details>
<summary>golang/go (go)</summary>

###
[`v1.26.3`](https://redirect.github.com/golang/go/compare/go1.26.2...go1.26.3)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 5am on Monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/opendefensecloud/artifact-conduit).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNTkuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJvay10by1oZWxtIiwib2stdG8taW1hZ2UiXX0=-->

780 of 928 relevant lines covered (84.05%)

1298.68 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=events.k8s.io,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) {
2,970✔
65
        log := ctrl.LoggerFrom(ctx)
2,970✔
66
        ctrlResult := ctrl.Result{}
2,970✔
67

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

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

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

2,967✔
82
        // Handle deletion: cleanup artifact workflows, then remove finalizer
2,967✔
83
        if !order.DeletionTimestamp.IsZero() {
2,969✔
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() {
5,930✔
121
                if !slices.Contains(order.Finalizers, orderFinalizer) {
2,985✔
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)
2,945✔
134
        if err != nil {
2,945✔
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)) {
2,947✔
138
                log.V(1).Info("Force reconcile requested")
2✔
139
                r.Recorder.Eventf(order, nil, corev1.EventTypeNormal, "ForceReconcile", "Reconcile", "Force reconcile requested via annotation")
2✔
140
                // Delete existing artifact workflows to force re-creation
2✔
141
                for sha := range order.Status.ArtifactWorkflows {
4✔
142
                        // Remove Secret and ArtifactWorkflow
2✔
143
                        aw := &arcv1alpha1.ArtifactWorkflow{
2✔
144
                                ObjectMeta: awObjectMeta(order, sha),
2✔
145
                        }
2✔
146
                        _ = r.Delete(ctx, aw) // Ignore errors
2✔
147
                        delete(order.Status.ArtifactWorkflows, sha)
2✔
148
                        r.Recorder.Eventf(order, aw, corev1.EventTypeNormal, "ForceReconcile", "Reconcile", "Deleted artifact workflow '%s' with sha %s", aw.Name, sha)
2✔
149
                }
2✔
150
                // Update last force time
151
                order.Status.LastForceAt = metav1.Now()
2✔
152
                if err := r.Status().Update(ctx, order); err != nil {
3✔
153
                        return ctrlResult, errLogAndWrap(log, err, "failed to update last force time")
1✔
154
                }
1✔
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 {
3,030✔
161
                order.Status.ArtifactWorkflows = map[string]arcv1alpha1.OrderArtifactWorkflowStatus{}
87✔
162
        }
87✔
163

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

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

2,832✔
182
        // List missing artifact workflows
2,832✔
183
        var createAWs []string
2,832✔
184
        for sha := range desiredAWs {
7,482✔
185
                if _, exists := order.Status.ArtifactWorkflows[sha]; exists {
9,275✔
186
                        continue
4,625✔
187
                }
188

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

192
        // Find obsolete artifact workflows
193
        var deleteAWs []string
2,832✔
194
        for sha := range order.Status.ArtifactWorkflows {
7,458✔
195
                if _, exists := desiredAWs[sha]; exists {
9,251✔
196
                        continue
4,625✔
197
                }
198

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

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

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

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

221
                // Get ArtifactWorkflow object and check TTLs.
222
                artifactWorkflow := &arcv1alpha1.ArtifactWorkflow{}
89✔
223
                if err := r.Get(ctx, types.NamespacedName{Namespace: order.Namespace, Name: awName(order, sha)}, artifactWorkflow); err != nil && !apierrors.IsNotFound(err) {
89✔
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 != "" {
98✔
228
                        // Cleanup finished workflows if TTLAfterFinished is set.
9✔
229
                        if awStatus.Phase == arcv1alpha1.WorkflowSucceeded {
15✔
230
                                // If TTL is set, check if it has expired
6✔
231
                                if artifactWorkflow.Spec.TTLAfterFinished != nil {
9✔
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 {
11✔
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)
87✔
267
        }
268

269
        // Create missing artifact workflows
270
        for _, sha := range createAWs {
2,857✔
271
                daw := desiredAWs[sha]
25✔
272
                aw, err := r.hydrateArtifactWorkflow(&daw)
25✔
273
                if err != nil {
25✔
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 {
25✔
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 {
27✔
286
                        if apierrors.IsAlreadyExists(err) {
4✔
287
                                // Already created by a previous reconcile — that's fine
2✔
288
                                continue
2✔
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 {
2,833✔
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 {
2,919✔
326
                // Finished, let's clean up!
87✔
327
                aw := &arcv1alpha1.ArtifactWorkflow{
87✔
328
                        ObjectMeta: awObjectMeta(order, sha),
87✔
329
                }
87✔
330
                if err := r.Delete(ctx, aw); client.IgnoreNotFound(err) != nil {
87✔
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)
87✔
336
                r.Recorder.Eventf(order, aw, corev1.EventTypeNormal, "Deleted", "Delete", "Deleted finished artifact workflow '%s'", sha)
87✔
337
        }
338

339
        anyPhaseChanged := false
2,832✔
340
        for sha, daw := range desiredAWs {
7,482✔
341
                if slices.Contains(createAWs, sha) {
4,675✔
342
                        // If it was just created we skip the update
25✔
343
                        continue
25✔
344
                }
345
                if daw.cron == nil && order.Status.ArtifactWorkflows[sha].Phase.Completed() {
4,714✔
346
                        // We do not need to check for updates if the workflow is completed and is NOT cron
89✔
347
                        continue
89✔
348
                }
349
                aw := arcv1alpha1.ArtifactWorkflow{}
4,536✔
350
                if err := r.Get(ctx, namespacedName(daw.objectMeta.Namespace, daw.objectMeta.Name), &aw); err != nil {
4,536✔
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]
4,536✔
360
                if orderAWStatus.Phase != aw.Status.Phase ||
4,536✔
361
                        orderAWStatus.Succeeded != aw.Status.Succeeded ||
4,536✔
362
                        orderAWStatus.Failed != aw.Status.Failed ||
4,536✔
363
                        !orderAWStatus.LastScheduled.Equal(aw.Status.LastScheduled) {
6,323✔
364
                        orderAWStatus.WorkflowStatus = aw.Status.WorkflowStatus
1,787✔
365
                        order.Status.ArtifactWorkflows[sha] = orderAWStatus
1,787✔
366
                        anyPhaseChanged = true
1,787✔
367
                }
1,787✔
368
        }
369

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

384
        return ctrlResult, nil
2,772✔
385
}
386

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

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

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

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

4,764✔
412
        // We need the referenced src- and dst-endpoints for the artifact
4,764✔
413
        srcRefName := artifact.SrcRef.Name
4,764✔
414
        if srcRefName == "" {
5,166✔
415
                srcRefName = order.Spec.Defaults.SrcRef.Name
402✔
416
        }
402✔
417
        dstRefName := artifact.DstRef.Name
4,764✔
418
        if dstRefName == "" {
5,296✔
419
                dstRefName = order.Spec.Defaults.DstRef.Name
532✔
420
        }
532✔
421

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

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

12✔
438
                return nil, errLogAndWrap(log, err, "artifact validation failed")
12✔
439
        }
12✔
440
        if dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsagePushOnly && dstEndpoint.Spec.Usage != arcv1alpha1.EndpointUsageAll {
4,744✔
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{}
4,718✔
449
        if err := r.Get(ctx, namespacedName(order.Namespace, artifact.Type), artifactType); client.IgnoreNotFound(err) != nil {
4,718✔
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 (
4,718✔
454
                artifactTypeGen  int64
4,718✔
455
                artifactTypeSpec *arcv1alpha1.ArtifactTypeSpec
4,718✔
456
        )
4,718✔
457
        if artifactType.Name == "" { // was not found, let's check ClusterArtifactType
9,305✔
458
                clusterArtifactType := &arcv1alpha1.ClusterArtifactType{}
4,587✔
459
                if err := r.Get(ctx, namespacedName("", artifact.Type), clusterArtifactType); err != nil {
4,641✔
460
                        return nil, errLogAndWrap(log, err, "failed to fetch ArtifactType or ClusterArtifactType")
54✔
461
                }
54✔
462
                artifactTypeSpec = &clusterArtifactType.Spec
4,533✔
463
                artifactTypeGen = clusterArtifactType.Generation
4,533✔
464
                // NOTE: ClusterArtifactTypes can only reference ClusterWorkflowTemplates, so we enforce this here:
4,533✔
465
                artifactTypeSpec.WorkflowTemplateRef.ClusterScope = true
4,533✔
466
        } else {
131✔
467
                artifactTypeSpec = &artifactType.Spec
131✔
468
                artifactTypeGen = artifactType.Generation
131✔
469
        }
131✔
470

471
        if len(artifactTypeSpec.Rules.SrcTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.SrcTypes, srcEndpoint.Spec.Type) {
4,675✔
472
                err := fmt.Errorf("source endpoint type '%s' is not allowed by ArtifactType rules", srcEndpoint.Spec.Type)
11✔
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)
11✔
474

11✔
475
                return nil, errLogAndWrap(log, err, "artifact validation failed")
11✔
476
        }
11✔
477
        if len(artifactTypeSpec.Rules.DstTypes) > 0 && !slices.Contains(artifactTypeSpec.Rules.DstTypes, dstEndpoint.Spec.Type) {
4,653✔
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{}
4,653✔
486
        if srcEndpoint.Spec.SecretRef.Name != "" {
9,304✔
487
                if err := r.Get(ctx, namespacedName(order.Namespace, srcEndpoint.Spec.SecretRef.Name), srcSecret); err != nil {
4,651✔
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{}
4,653✔
494
        if dstEndpoint.Spec.SecretRef.Name != "" {
9,304✔
495
                if err := r.Get(ctx, namespacedName(order.Namespace, dstEndpoint.Spec.SecretRef.Name), dstSecret); err != nil {
4,651✔
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
4,653✔
503
        if cron == nil {
9,301✔
504
                cron = order.Spec.Defaults.Cron
4,648✔
505
        }
4,648✔
506

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

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

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

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