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

opendefensecloud / artifact-conduit / 21902989333

11 Feb 2026 11:18AM UTC coverage: 83.946% (+0.1%) from 83.835%
21902989333

push

github

web-flow
fix(deps): update kubernetes packages to v0.35.1 (#205)

This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [k8s.io/api](https://redirect.github.com/kubernetes/api) | `v0.35.0` →
`v0.35.1` |
![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fapi/v0.35.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fapi/v0.35.0/v0.35.1?slim=true)
|
|
[k8s.io/apimachinery](https://redirect.github.com/kubernetes/apimachinery)
| `v0.35.0` → `v0.35.1` |
![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fapimachinery/v0.35.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fapimachinery/v0.35.0/v0.35.1?slim=true)
|
| [k8s.io/apiserver](https://redirect.github.com/kubernetes/apiserver) |
`v0.35.0` → `v0.35.1` |
![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fapiserver/v0.35.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fapiserver/v0.35.0/v0.35.1?slim=true)
|
| [k8s.io/client-go](https://redirect.github.com/kubernetes/client-go) |
`v0.35.0` → `v0.35.1` |
![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fclient-go/v0.35.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fclient-go/v0.35.0/v0.35.1?slim=true)
|
|
[k8s.io/code-generator](https://redirect.github.com/kubernetes/code-generator)
| `v0.35.0` → `v0.35.1` |
![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fcode-generator/v0.35.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fcode-generator/v0.35.0/v0.35.1?slim=true)
|

---

### Release Notes

<details>
<summary>kubernetes/api (k8s.io/api)</summary>

###
[`v0.35.1`](https://redirect.github.com/kubernetes/api/compare/v0.35.0...v0.35.1)

[Compare
Source]... (continued)

753 of 897 relevant lines covered (83.95%)

356.4 hits per line

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

82.72
/pkg/controller/artifactworkflow_controller.go
1
// Copyright 2025 BWI GmbH and Artefact Conduit contributors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package controller
5

6
import (
7
        "bytes"
8
        "context"
9
        "fmt"
10
        "io"
11
        "slices"
12

13
        wfv1alpha1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
14
        "github.com/go-logr/logr"
15
        "github.com/jastBytes/sprint"
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/kubernetes"
22
        "k8s.io/client-go/tools/record"
23
        ctrl "sigs.k8s.io/controller-runtime"
24
        "sigs.k8s.io/controller-runtime/pkg/builder"
25
        "sigs.k8s.io/controller-runtime/pkg/client"
26
        "sigs.k8s.io/controller-runtime/pkg/handler"
27
        "sigs.k8s.io/controller-runtime/pkg/predicate"
28
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
29

30
        arcv1alpha1 "go.opendefense.cloud/arc/api/arc/v1alpha1"
31
)
32

33
const (
34
        artifactWorkflowFinalizer = "arc.opendefense.cloud/artifact-workflow-finalizer"
35
)
36

37
// ArtifactWorkflowReconciler reconciles a ArtifactWorkflow object
38
type ArtifactWorkflowReconciler struct {
39
        client.Client
40
        ClientSet kubernetes.Interface
41
        Scheme    *runtime.Scheme
42
        Recorder  record.EventRecorder
43
}
44

45
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=clusterartifacttypes,verbs=get;list;watch
46
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=artifactworkflows/status,verbs=get;update;patch
47
//+kubebuilder:rbac:groups=arc.opendefense.cloud,resources=artifactworkflows/finalizers,verbs=update
48
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
49
//+kubebuilder:rbac:groups=argoproj.io,resources=workflows,verbs=get;list;watch;create;update;patch;delete
50
//+kubebuilder:rbac:groups=argoproj.io,resources=cronworkflows,verbs=get;list;watch;create;update;patch;delete
51
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
52
//+kubebuilder:rbac:groups="",resources=pods;pods/log,verbs=get;list
53

54
// Reconcile moves the current state of the cluster closer to the desired state
55
func (r *ArtifactWorkflowReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
1,310✔
56
        log := ctrl.LoggerFrom(ctx)
1,310✔
57
        ctrlResult := ctrl.Result{}
1,310✔
58

1,310✔
59
        aw := &arcv1alpha1.ArtifactWorkflow{}
1,310✔
60
        if err := r.Get(ctx, req.NamespacedName, aw); err != nil {
1,315✔
61
                if apierrors.IsNotFound(err) {
10✔
62
                        // Object not found, return.
5✔
63
                        return ctrlResult, nil
5✔
64
                }
5✔
65

66
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
67
        }
68

69
        // Two different behaviors are implemented depending on whether we have
70
        // a "one-shot" order or cron order, so let's instantiate the corresponding handler:
71
        var handler WorkflowHandler
1,305✔
72
        if aw.Spec.Cron != nil {
1,333✔
73
                handler = NewCronWorkflowHandler(r, log, aw)
28✔
74
        } else {
1,305✔
75
                handler = NewSingleWorkflowHandler(r, log, aw)
1,277✔
76
        }
1,277✔
77

78
        // Update last reconcile time
79
        aw.Status.LastReconcileAt = metav1.Now()
1,305✔
80

1,305✔
81
        // Handle deletion
1,305✔
82
        if !aw.DeletionTimestamp.IsZero() {
1,310✔
83
                log.V(1).Info("ArtifactWorkflow is being deleted")
5✔
84
                // Cleanup workflow, if exists
5✔
85
                if err := handler.DeleteArgoResources(ctx); err != nil {
5✔
86
                        return ctrlResult, errLogAndWrap(log, err, "workflow deletion failed")
×
87
                }
×
88

89
                // Remove finalizer
90
                if slices.Contains(aw.Finalizers, artifactWorkflowFinalizer) {
10✔
91
                        log.V(1).Info("Removing finalizer from ArtifactWorkflow")
5✔
92
                        aw.Finalizers = slices.DeleteFunc(aw.Finalizers, func(f string) bool {
10✔
93
                                return f == artifactWorkflowFinalizer
5✔
94
                        })
5✔
95
                        if err := r.Update(ctx, aw); err != nil {
5✔
96
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer")
×
97
                        }
×
98
                }
99

100
                return ctrlResult, nil
5✔
101
        }
102

103
        // Add finalizer if not present and not deleting
104
        if aw.DeletionTimestamp.IsZero() {
2,600✔
105
                if !slices.Contains(aw.Finalizers, artifactWorkflowFinalizer) {
1,329✔
106
                        log.V(1).Info("Adding finalizer to ArtifactWorkflow")
29✔
107
                        aw.Finalizers = append(aw.Finalizers, artifactWorkflowFinalizer)
29✔
108
                        if err := r.Update(ctx, aw); err != nil {
29✔
109
                                return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer")
×
110
                        }
×
111
                        // Return without requeue; the Update event will trigger reconciliation again
112
                        return ctrlResult, nil
29✔
113
                }
114
        }
115

116
        // Handle force reconcile annotation
117
        forceAt, err := GetForceAtAnnotationValue(aw)
1,271✔
118
        if err != nil {
1,271✔
119
                log.V(1).Error(err, "Invalid force reconcile annotation, ignoring")
×
120
        }
×
121
        if !forceAt.IsZero() && (aw.Status.LastForceAt.IsZero() || forceAt.After(aw.Status.LastForceAt.Time)) {
1,272✔
122
                log.V(1).Info("Force reconcile requested")
1✔
123
                r.Recorder.Event(aw, corev1.EventTypeNormal, "ForceReconcile", "Force reconcile requested via annotation")
1✔
124
                // Delete existing workflow, if any
1✔
125
                if err := handler.DeleteArgoResources(ctx); err != nil {
1✔
126
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete existing workflow for force reconcile")
×
127
                }
×
128
                // Reset phase so workflow gets recreated, and update last force time
129
                aw.Status.Phase = arcv1alpha1.WorkflowUnknown
1✔
130
                aw.Status.LastForceAt = metav1.Now()
1✔
131
                if err := r.Status().Update(ctx, aw); err != nil {
1✔
132
                        return ctrlResult, errLogAndWrap(log, err, "failed to update last force time")
×
133
                }
×
134
                // Return without requeue; the update event will trigger reconciliation again
135
                return ctrlResult, nil
1✔
136
        }
137

138
        if aw.Status.Phase == arcv1alpha1.WorkflowUnknown {
2,008✔
139
                return ctrlResult, handler.CreateArgoResources(ctx)
738✔
140
        }
738✔
141

142
        if aw.Status.Phase.InProgress() || aw.Spec.Cron != nil {
1,060✔
143
                return ctrlResult, handler.CheckArgoResources(ctx)
528✔
144
        }
528✔
145

146
        return ctrlResult, nil
4✔
147
}
148

149
func (r *ArtifactWorkflowReconciler) setStatusFromWorkflow(ctx context.Context, log logr.Logger, aw *arcv1alpha1.ArtifactWorkflow, wf *wfv1alpha1.Workflow) bool {
523✔
150
        if aw.Status.Phase == arcv1alpha1.WorkflowPhase(wf.Status.Phase) {
528✔
151
                return false // nothing updated
5✔
152
        }
5✔
153
        aw.Status.Phase = arcv1alpha1.WorkflowPhase(wf.Status.Phase)
518✔
154

518✔
155
        switch aw.Status.Phase {
518✔
156
        case arcv1alpha1.WorkflowSucceeded, arcv1alpha1.WorkflowStopped:
5✔
157
                aw.Status.CompletionTime = metav1.Now()
5✔
158
        case arcv1alpha1.WorkflowError, arcv1alpha1.WorkflowFailed:
6✔
159
                aw.Status.Message = wf.Status.Message
6✔
160
                r.generateWorkflowStatusMessage(ctx, wf, log, aw)
6✔
161
        default:
507✔
162
        }
163

164
        return true
518✔
165
}
166

167
func (r *ArtifactWorkflowReconciler) generateWorkflowStatusMessage(ctx context.Context, wf *wfv1alpha1.Workflow, log logr.Logger, aw *arcv1alpha1.ArtifactWorkflow) {
6✔
168
        failedNodes := []struct {
6✔
169
                Name    string
6✔
170
                Pod     string
6✔
171
                Message string
6✔
172
        }{}
6✔
173
        for _, node := range wf.Status.Nodes {
9✔
174
                if (node.Phase == wfv1alpha1.NodeFailed || node.Phase == wfv1alpha1.NodeError) && node.Type == wfv1alpha1.NodeTypePod {
5✔
175
                        nr := struct {
2✔
176
                                Name    string
2✔
177
                                Pod     string
2✔
178
                                Message string
2✔
179
                        }{
2✔
180
                                Name:    node.DisplayName,
2✔
181
                                Pod:     generatePodNameFromNodeStatus(node),
2✔
182
                                Message: node.Message,
2✔
183
                        }
2✔
184
                        failedNodes = append(failedNodes, nr)
2✔
185
                }
2✔
186
        }
187

188
        for _, nr := range failedNodes {
8✔
189
                logs, err := r.fetchPodLogs(ctx, aw.Namespace, nr.Pod)
2✔
190
                if err != nil {
2✔
191
                        log.V(1).Info("failed to fetch pod logs", "pod", nr.Pod, "error", err)
×
192
                        aw.Status.Message += fmt.Sprintf("Step '%s' failed:\n%s\n\n", nr.Name, nr.Message)
×
193

×
194
                        continue
×
195
                }
196
                aw.Status.Message += fmt.Sprintf("Step '%s' failed:\n%s\nLogs:\n%s\n\n", nr.Name, nr.Message, logs)
2✔
197
        }
198
}
199

200
func (r *ArtifactWorkflowReconciler) fetchPodLogs(ctx context.Context, namespace, podName string) (string, error) {
2✔
201
        podLogOptions := corev1.PodLogOptions{
2✔
202
                Container: "main", // Assuming the main container
2✔
203
                Follow:    false,
2✔
204
                TailLines: sprint.ToPointer(int64(30)), // Fetch last 30 lines
2✔
205
        }
2✔
206
        req := r.ClientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOptions)
2✔
207
        podLogs, err := req.Stream(ctx)
2✔
208
        if err != nil {
2✔
209
                return "", err
×
210
        }
×
211
        defer sprint.PanicOnErrorFunc(podLogs.Close) // Close the stream when done
2✔
212

2✔
213
        buf := new(bytes.Buffer)
2✔
214
        _, err = io.Copy(buf, podLogs)
2✔
215
        if err != nil {
2✔
216
                return "", err
×
217
        }
×
218

219
        return buf.String(), nil
2✔
220
}
221

222
func (r *ArtifactWorkflowReconciler) retrieveSecrets(ctx context.Context, aw *arcv1alpha1.ArtifactWorkflow) (*corev1.Secret, *corev1.Secret, error) {
738✔
223
        srcSecret := corev1.Secret{}
738✔
224
        if aw.Spec.SrcSecretRef.Name != "" {
1,138✔
225
                if err := r.Get(ctx, namespacedName(aw.Namespace, aw.Spec.SrcSecretRef.Name), &srcSecret); err != nil {
400✔
226
                        r.Recorder.Event(aw, corev1.EventTypeWarning, "InvalidSecret", fmt.Sprintf("Failed to fetch source secret '%s': %v", aw.Spec.SrcSecretRef.Name, err))
×
227
                        return nil, nil, fmt.Errorf("failed to fetch secret for source: %w", err)
×
228
                }
×
229
        }
230

231
        dstSecret := corev1.Secret{}
738✔
232
        if aw.Spec.DstSecretRef.Name != "" {
1,138✔
233
                if err := r.Get(ctx, namespacedName(aw.Namespace, aw.Spec.DstSecretRef.Name), &dstSecret); err != nil {
400✔
234
                        r.Recorder.Event(aw, corev1.EventTypeWarning, "InvalidSecret", fmt.Sprintf("Failed to fetch destination secret '%s': %v", aw.Spec.DstSecretRef.Name, err))
×
235
                        return nil, nil, fmt.Errorf("failed to fetch secret for destination: %w", err)
×
236
                }
×
237
        }
238

239
        return &srcSecret, &dstSecret, nil
738✔
240
}
241

242
// SetupWithManager sets up the controller with the Manager.
243
func (r *ArtifactWorkflowReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
244
        return ctrl.NewControllerManagedBy(mgr).
1✔
245
                For(&arcv1alpha1.ArtifactWorkflow{}).
1✔
246
                Owns(&wfv1alpha1.Workflow{}).
1✔
247
                Owns(&wfv1alpha1.CronWorkflow{}).
1✔
248
                Watches(
1✔
249
                        &wfv1alpha1.Workflow{},
1✔
250
                        handler.EnqueueRequestsFromMapFunc(r.findArtifactWorkflowsForWorkflowOwnedByCronWorkflow()),
1✔
251
                        builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
1✔
252
                ).
1✔
253
                Complete(r)
1✔
254
}
1✔
255

256
// findArtifactWorkflowsForWorkflowOwnedByCronWorkflow returns a function that finds ArtifactWorkflows
257
// that own CronWorkflows that own the given Workflow. This handles the ownership chain:
258
// ArtifactWorkflow -> CronWorkflow -> Workflow
259
func (r *ArtifactWorkflowReconciler) findArtifactWorkflowsForWorkflowOwnedByCronWorkflow() func(context.Context, client.Object) []ctrl.Request {
1✔
260
        return func(ctx context.Context, wf client.Object) []ctrl.Request {
53✔
261
                // Find the CronWorkflow that owns this Workflow
52✔
262
                ownerReferences := wf.GetOwnerReferences()
52✔
263
                var cronWorkflowName, cronWorkflowNamespace string
52✔
264

52✔
265
                for _, owner := range ownerReferences {
104✔
266
                        if owner.Kind == "CronWorkflow" && owner.APIVersion == wfv1alpha1.SchemeGroupVersion.String() {
57✔
267
                                cronWorkflowName = owner.Name
5✔
268
                                cronWorkflowNamespace = wf.GetNamespace() // Owner is in same namespace
5✔
269

5✔
270
                                break
5✔
271
                        }
272
                }
273

274
                if cronWorkflowName == "" {
99✔
275
                        // Workflow is not owned by a CronWorkflow
47✔
276
                        return []reconcile.Request{}
47✔
277
                }
47✔
278

279
                // Find the ArtifactWorkflow that owns this CronWorkflow
280
                cwf := &wfv1alpha1.CronWorkflow{}
5✔
281
                if err := r.Get(ctx, types.NamespacedName{Name: cronWorkflowName, Namespace: cronWorkflowNamespace}, cwf); err != nil {
5✔
282
                        // CronWorkflow not found or error occurred
×
283
                        return []reconcile.Request{}
×
284
                }
×
285

286
                var artifactWorkflowName string
5✔
287
                for _, owner := range cwf.GetOwnerReferences() {
10✔
288
                        if owner.Kind == "ArtifactWorkflow" && owner.APIVersion == arcv1alpha1.SchemeGroupVersion.String() {
10✔
289
                                artifactWorkflowName = owner.Name
5✔
290
                                break
5✔
291
                        }
292
                }
293

294
                if artifactWorkflowName == "" {
5✔
295
                        // CronWorkflow is not owned by an ArtifactWorkflow
×
296
                        return []reconcile.Request{}
×
297
                }
×
298

299
                return []reconcile.Request{
5✔
300
                        {
5✔
301
                                NamespacedName: types.NamespacedName{
5✔
302
                                        Name:      artifactWorkflowName,
5✔
303
                                        Namespace: cronWorkflowNamespace,
5✔
304
                                },
5✔
305
                        },
5✔
306
                }
5✔
307
        }
308
}
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