Skip to content

Commit 6865cec

Browse files
authored
Merge pull request #11463 from fabriziopandini/add-v1beta2-rollout-condition
🌱 Add v1beta2 RollingOut condition
2 parents d7e314f + 3fb816d commit 6865cec

14 files changed

+661
-0
lines changed

api/v1beta1/cluster_types.go

+23
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,29 @@ const (
281281
ClusterRemoteConnectionProbeSucceededV1Beta2Reason = "ProbeSucceeded"
282282
)
283283

284+
// Cluster's RollingOut condition and corresponding reasons that will be used in v1Beta2 API version.
285+
const (
286+
// ClusterRollingOutV1Beta2Condition is the summary of `RollingOut` conditions from ControlPlane, MachineDeployments
287+
// and MachinePools.
288+
ClusterRollingOutV1Beta2Condition = RollingOutV1Beta2Condition
289+
290+
// ClusterRollingOutV1Beta2Reason surfaces when at least one of the Cluster's control plane, MachineDeployments,
291+
// or MachinePools are rolling out.
292+
ClusterRollingOutV1Beta2Reason = RollingOutV1Beta2Reason
293+
294+
// ClusterNotRollingOutV1Beta2Reason surfaces when none of the Cluster's control plane, MachineDeployments,
295+
// or MachinePools are rolling out.
296+
ClusterNotRollingOutV1Beta2Reason = NotRollingOutV1Beta2Reason
297+
298+
// ClusterRollingOutUnknownV1Beta2Reason surfaces when one of the Cluster's control plane, MachineDeployments,
299+
// or MachinePools rolling out condition is unknown, and none true.
300+
ClusterRollingOutUnknownV1Beta2Reason = "RollingOutUnknown"
301+
302+
// ClusterRollingOutInternalErrorV1Beta2Reason surfaces unexpected failures when listing machines
303+
// or computing the RollingOut condition.
304+
ClusterRollingOutInternalErrorV1Beta2Reason = InternalErrorV1Beta2Reason
305+
)
306+
284307
// Cluster's ScalingUp condition and corresponding reasons that will be used in v1Beta2 API version.
285308
const (
286309
// ClusterScalingUpV1Beta2Condition is the summary of `ScalingUp` conditions from ControlPlane, MachineDeployments,

api/v1beta1/machinedeployment_types.go

+15
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,21 @@ const (
148148
MachineDeploymentMachinesUpToDateInternalErrorV1Beta2Reason = InternalErrorV1Beta2Reason
149149
)
150150

151+
// MachineDeployment's RollingOut condition and corresponding reasons that will be used in v1Beta2 API version.
152+
const (
153+
// MachineDeploymentRollingOutV1Beta2Condition is true if there is at least one machine not up-to-date.
154+
MachineDeploymentRollingOutV1Beta2Condition = RollingOutV1Beta2Condition
155+
156+
// MachineDeploymentRollingOutV1Beta2Reason surfaces when there is at least one machine not up-to-date.
157+
MachineDeploymentRollingOutV1Beta2Reason = RollingOutV1Beta2Reason
158+
159+
// MachineDeploymentNotRollingOutV1Beta2Reason surfaces when all the machines are up-to-date.
160+
MachineDeploymentNotRollingOutV1Beta2Reason = NotRollingOutV1Beta2Reason
161+
162+
// MachineDeploymentRollingOutInternalErrorV1Beta2Reason surfaces unexpected failures when listing machines.
163+
MachineDeploymentRollingOutInternalErrorV1Beta2Reason = InternalErrorV1Beta2Reason
164+
)
165+
151166
// MachineDeployment's ScalingUp condition and corresponding reasons that will be used in v1Beta2 API version.
152167
const (
153168
// MachineDeploymentScalingUpV1Beta2Condition is true if actual replicas < desired replicas.

api/v1beta1/v1beta2_condition_consts.go

+13
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ const (
5454
// the same condition type exists.
5555
MachinesUpToDateV1Beta2Condition = "MachinesUpToDate"
5656

57+
// RollingOutV1Beta2Condition reports if an object is rolling out changes to machines; Cluster API usually
58+
// rolls out changes to machines by replacing not up-to-date machines with new ones.
59+
// Note: This condition type is defined to ensure consistent naming of conditions across objects.
60+
// Please use object specific variants of this condition which provides more details for each context where
61+
// the same condition type exists.
62+
RollingOutV1Beta2Condition = "RollingOut"
63+
5764
// ScalingUpV1Beta2Condition reports if an object is scaling up.
5865
// Note: This condition type is defined to ensure consistent naming of conditions across objects.
5966
// Please use object specific variants of this condition which provides more details for each context where
@@ -114,6 +121,12 @@ const (
114121
// UpToDateUnknownV1Beta2Reason applies to a condition surfacing object up-tp-date unknown.
115122
UpToDateUnknownV1Beta2Reason = "UpToDateUnknown"
116123

124+
// RollingOutV1Beta2Reason surfaces when an object is rolling out.
125+
RollingOutV1Beta2Reason = "RollingOut"
126+
127+
// NotRollingOutV1Beta2Reason surfaces when an object is not rolling out.
128+
NotRollingOutV1Beta2Reason = "NotRollingOut"
129+
117130
// ScalingUpV1Beta2Reason surfaces when an object is scaling up.
118131
ScalingUpV1Beta2Reason = "ScalingUp"
119132

controlplane/kubeadm/api/v1beta1/v1beta2_condition_consts.go

+15
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,21 @@ const (
176176
KubeadmControlPlaneMachinesUpToDateInternalErrorV1Beta2Reason = clusterv1.InternalErrorV1Beta2Reason
177177
)
178178

179+
// KubeadmControlPlane's RollingOut condition and corresponding reasons that will be used in v1Beta2 API version.
180+
const (
181+
// KubeadmControlPlaneRollingOutV1Beta2Condition is true if there is at least one machine not up-to-date.
182+
KubeadmControlPlaneRollingOutV1Beta2Condition = clusterv1.RollingOutV1Beta2Condition
183+
184+
// KubeadmControlPlaneRollingOutV1Beta2Reason surfaces when there is at least one machine not up-to-date.
185+
KubeadmControlPlaneRollingOutV1Beta2Reason = clusterv1.RollingOutV1Beta2Reason
186+
187+
// KubeadmControlPlaneNotRollingOutV1Beta2Reason surfaces when all the machines are up-to-date.
188+
KubeadmControlPlaneNotRollingOutV1Beta2Reason = clusterv1.NotRollingOutV1Beta2Reason
189+
190+
// KubeadmControlPlaneRollingOutInternalErrorV1Beta2Reason surfaces unexpected failures when listing machines.
191+
KubeadmControlPlaneRollingOutInternalErrorV1Beta2Reason = clusterv1.InternalErrorV1Beta2Reason
192+
)
193+
179194
// KubeadmControlPlane's ScalingUp condition and corresponding reasons that will be used in v1Beta2 API version.
180195
const (
181196
// KubeadmControlPlaneScalingUpV1Beta2Condition is true if actual replicas < desired replicas.

controlplane/kubeadm/internal/controllers/controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ func patchKubeadmControlPlane(ctx context.Context, patchHelper *patch.Helper, kc
353353
controlplanev1.KubeadmControlPlaneControlPlaneComponentsHealthyV1Beta2Condition,
354354
controlplanev1.KubeadmControlPlaneMachinesReadyV1Beta2Condition,
355355
controlplanev1.KubeadmControlPlaneMachinesUpToDateV1Beta2Condition,
356+
controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition,
356357
controlplanev1.KubeadmControlPlaneScalingUpV1Beta2Condition,
357358
controlplanev1.KubeadmControlPlaneScalingDownV1Beta2Condition,
358359
controlplanev1.KubeadmControlPlaneRemediatingV1Beta2Condition,

controlplane/kubeadm/internal/controllers/status.go

+58
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ func (r *KubeadmControlPlaneReconciler) updateV1Beta2Status(ctx context.Context,
161161

162162
setReplicas(ctx, controlPlane.KCP, controlPlane.Machines)
163163
setInitializedCondition(ctx, controlPlane.KCP)
164+
setRollingOutCondition(ctx, controlPlane.KCP, controlPlane.Machines)
164165
setScalingUpCondition(ctx, controlPlane.KCP, controlPlane.Machines, controlPlane.InfraMachineTemplateIsNotFound, controlPlane.PreflightCheckResults)
165166
setScalingDownCondition(ctx, controlPlane.KCP, controlPlane.Machines, controlPlane.PreflightCheckResults)
166167
setMachinesReadyCondition(ctx, controlPlane.KCP, controlPlane.Machines)
@@ -210,6 +211,63 @@ func setInitializedCondition(_ context.Context, kcp *controlplanev1.KubeadmContr
210211
})
211212
}
212213

214+
func setRollingOutCondition(_ context.Context, kcp *controlplanev1.KubeadmControlPlane, machines collections.Machines) {
215+
// Count machines rolling out and collect reasons why a rollout is happening.
216+
// Note: The code below collects all the reasons for which at least a machine is rolling out; under normal circumstances
217+
// all the machines are rolling out for the same reasons, however, in case of changes to KCP
218+
// before a previous changes is not fully rolled out, there could be machines rolling out for
219+
// different reasons.
220+
rollingOutReplicas := 0
221+
rolloutReasons := sets.Set[string]{}
222+
for _, machine := range machines {
223+
upToDateCondition := v1beta2conditions.Get(machine, clusterv1.MachineUpToDateV1Beta2Condition)
224+
if upToDateCondition == nil || upToDateCondition.Status != metav1.ConditionFalse {
225+
continue
226+
}
227+
rollingOutReplicas++
228+
if upToDateCondition.Message != "" {
229+
rolloutReasons.Insert(strings.Split(upToDateCondition.Message, "; ")...)
230+
}
231+
}
232+
233+
if rollingOutReplicas == 0 {
234+
var message string
235+
v1beta2conditions.Set(kcp, metav1.Condition{
236+
Type: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition,
237+
Status: metav1.ConditionFalse,
238+
Reason: controlplanev1.KubeadmControlPlaneNotRollingOutV1Beta2Reason,
239+
Message: message,
240+
})
241+
return
242+
}
243+
244+
// Rolling out.
245+
message := fmt.Sprintf("Rolling out %d not up-to-date replicas", rollingOutReplicas)
246+
if rolloutReasons.Len() > 0 {
247+
// Surface rollout reasons ensuring that if there is a version change, it goes first.
248+
reasons := rolloutReasons.UnsortedList()
249+
sort.Slice(reasons, func(i, j int) bool {
250+
if strings.HasPrefix(reasons[i], "Version") && !strings.HasPrefix(reasons[j], "Version") {
251+
return true
252+
}
253+
if !strings.HasPrefix(reasons[i], "Version") && strings.HasPrefix(reasons[j], "Version") {
254+
return false
255+
}
256+
return reasons[i] < reasons[j]
257+
})
258+
for i := range reasons {
259+
reasons[i] = fmt.Sprintf("* %s", reasons[i])
260+
}
261+
message += fmt.Sprintf("\n%s", strings.Join(reasons, "\n"))
262+
}
263+
v1beta2conditions.Set(kcp, metav1.Condition{
264+
Type: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition,
265+
Status: metav1.ConditionTrue,
266+
Reason: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Reason,
267+
Message: message,
268+
})
269+
}
270+
213271
func setScalingUpCondition(_ context.Context, kcp *controlplanev1.KubeadmControlPlane, machines collections.Machines, infrastructureObjectNotFound bool, preflightChecks internal.PreflightCheckResults) {
214272
if kcp.Spec.Replicas == nil {
215273
v1beta2conditions.Set(kcp, metav1.Condition{

controlplane/kubeadm/internal/controllers/status_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,99 @@ func Test_setInitializedCondition(t *testing.T) {
121121
}
122122
}
123123

124+
func Test_setRollingOutCondition(t *testing.T) {
125+
upToDateCondition := metav1.Condition{
126+
Type: clusterv1.MachineUpToDateV1Beta2Condition,
127+
Status: metav1.ConditionTrue,
128+
Reason: clusterv1.MachineUpToDateV1Beta2Reason,
129+
}
130+
131+
tests := []struct {
132+
name string
133+
kcp *controlplanev1.KubeadmControlPlane
134+
machines []*clusterv1.Machine
135+
expectCondition metav1.Condition
136+
}{
137+
{
138+
name: "no machines",
139+
kcp: &controlplanev1.KubeadmControlPlane{},
140+
machines: []*clusterv1.Machine{},
141+
expectCondition: metav1.Condition{
142+
Type: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition,
143+
Status: metav1.ConditionFalse,
144+
Reason: controlplanev1.KubeadmControlPlaneNotRollingOutV1Beta2Reason,
145+
},
146+
},
147+
{
148+
name: "all machines are up to date",
149+
kcp: &controlplanev1.KubeadmControlPlane{},
150+
machines: []*clusterv1.Machine{
151+
{ObjectMeta: metav1.ObjectMeta{Name: "m1"}, Status: clusterv1.MachineStatus{V1Beta2: &clusterv1.MachineV1Beta2Status{Conditions: []metav1.Condition{upToDateCondition}}}},
152+
{ObjectMeta: metav1.ObjectMeta{Name: "m2"}, Status: clusterv1.MachineStatus{V1Beta2: &clusterv1.MachineV1Beta2Status{Conditions: []metav1.Condition{upToDateCondition}}}},
153+
},
154+
expectCondition: metav1.Condition{
155+
Type: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition,
156+
Status: metav1.ConditionFalse,
157+
Reason: controlplanev1.KubeadmControlPlaneNotRollingOutV1Beta2Reason,
158+
},
159+
},
160+
{
161+
name: "one up-to-date, two not up-to-date, one reporting up-to-date unknown",
162+
kcp: &controlplanev1.KubeadmControlPlane{},
163+
machines: []*clusterv1.Machine{
164+
{ObjectMeta: metav1.ObjectMeta{Name: "m1"}, Status: clusterv1.MachineStatus{V1Beta2: &clusterv1.MachineV1Beta2Status{Conditions: []metav1.Condition{upToDateCondition}}}},
165+
{ObjectMeta: metav1.ObjectMeta{Name: "m2"}, Status: clusterv1.MachineStatus{V1Beta2: &clusterv1.MachineV1Beta2Status{Conditions: []metav1.Condition{
166+
{
167+
Type: clusterv1.MachineUpToDateV1Beta2Condition,
168+
Status: metav1.ConditionUnknown,
169+
Reason: clusterv1.InternalErrorV1Beta2Reason,
170+
},
171+
}}}},
172+
{ObjectMeta: metav1.ObjectMeta{Name: "m3"}, Status: clusterv1.MachineStatus{V1Beta2: &clusterv1.MachineV1Beta2Status{Conditions: []metav1.Condition{
173+
{
174+
Type: clusterv1.MachineUpToDateV1Beta2Condition,
175+
Status: metav1.ConditionFalse,
176+
Reason: clusterv1.MachineNotUpToDateV1Beta2Reason,
177+
Message: "Version v1.25.0, v1.26.0 required",
178+
},
179+
}}}},
180+
{ObjectMeta: metav1.ObjectMeta{Name: "m4"}, Status: clusterv1.MachineStatus{V1Beta2: &clusterv1.MachineV1Beta2Status{Conditions: []metav1.Condition{
181+
{
182+
Type: clusterv1.MachineUpToDateV1Beta2Condition,
183+
Status: metav1.ConditionFalse,
184+
Reason: clusterv1.MachineNotUpToDateV1Beta2Reason,
185+
Message: "Failure domain failure-domain1, failure-domain2 required; InfrastructureMachine is not up-to-date",
186+
},
187+
}}}},
188+
},
189+
expectCondition: metav1.Condition{
190+
Type: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition,
191+
Status: metav1.ConditionTrue,
192+
Reason: controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Reason,
193+
Message: "Rolling out 2 not up-to-date replicas\n" +
194+
"* Version v1.25.0, v1.26.0 required\n" +
195+
"* Failure domain failure-domain1, failure-domain2 required\n" +
196+
"* InfrastructureMachine is not up-to-date",
197+
},
198+
},
199+
}
200+
for _, tt := range tests {
201+
t.Run(tt.name, func(t *testing.T) {
202+
g := NewWithT(t)
203+
204+
var machines collections.Machines
205+
if tt.machines != nil {
206+
machines = collections.FromMachines(tt.machines...)
207+
}
208+
setRollingOutCondition(ctx, tt.kcp, machines)
209+
210+
condition := v1beta2conditions.Get(tt.kcp, controlplanev1.KubeadmControlPlaneRollingOutV1Beta2Condition)
211+
g.Expect(condition).ToNot(BeNil())
212+
g.Expect(*condition).To(v1beta2conditions.MatchCondition(tt.expectCondition, v1beta2conditions.IgnoreLastTransitionTime(true)))
213+
})
214+
}
215+
}
216+
124217
func Test_setScalingUpCondition(t *testing.T) {
125218
tests := []struct {
126219
name string

internal/controllers/cluster/cluster_controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ func patchCluster(ctx context.Context, patchHelper *patch.Helper, cluster *clust
265265
clusterv1.ClusterMachinesReadyV1Beta2Condition,
266266
clusterv1.ClusterMachinesUpToDateV1Beta2Condition,
267267
clusterv1.ClusterRemoteConnectionProbeV1Beta2Condition,
268+
clusterv1.ClusterRollingOutV1Beta2Condition,
268269
clusterv1.ClusterScalingUpV1Beta2Condition,
269270
clusterv1.ClusterScalingDownV1Beta2Condition,
270271
clusterv1.ClusterRemediatingV1Beta2Condition,

internal/controllers/cluster/cluster_controller_status.go

+72
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (r *Reconciler) updateStatus(ctx context.Context, s *scope) {
6767
setWorkersAvailableCondition(ctx, s.cluster, expv1.MachinePoolList{}, s.descendants.machineDeployments, s.getDescendantsSucceeded)
6868
setMachinesReadyCondition(ctx, s.cluster, allMachines, s.getDescendantsSucceeded)
6969
setMachinesUpToDateCondition(ctx, s.cluster, allMachines, s.getDescendantsSucceeded)
70+
setRollingOutCondition(ctx, s.cluster, s.controlPlane, expv1.MachinePoolList{}, s.descendants.machineDeployments, s.controlPlaneIsNotFound, s.getDescendantsSucceeded)
7071
setScalingUpCondition(ctx, s.cluster, s.controlPlane, expv1.MachinePoolList{}, s.descendants.machineDeployments, s.descendants.machineSets, s.controlPlaneIsNotFound, s.getDescendantsSucceeded)
7172
setScalingDownCondition(ctx, s.cluster, s.controlPlane, expv1.MachinePoolList{}, s.descendants.machineDeployments, s.descendants.machineSets, s.controlPlaneIsNotFound, s.getDescendantsSucceeded)
7273
setRemediatingCondition(ctx, s.cluster, machinesToBeRemediated, unhealthyMachines, s.getDescendantsSucceeded)
@@ -760,6 +761,77 @@ func setRemediatingCondition(ctx context.Context, cluster *clusterv1.Cluster, ma
760761
})
761762
}
762763

764+
func setRollingOutCondition(ctx context.Context, cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, machinePools expv1.MachinePoolList, machineDeployments clusterv1.MachineDeploymentList, controlPlaneIsNotFound bool, getDescendantsSucceeded bool) {
765+
log := ctrl.LoggerFrom(ctx)
766+
767+
// If there was some unexpected errors in getting control plane or listing descendants (this should never happen), surface it.
768+
if (cluster.Spec.ControlPlaneRef != nil && controlPlane == nil && !controlPlaneIsNotFound) || !getDescendantsSucceeded {
769+
v1beta2conditions.Set(cluster, metav1.Condition{
770+
Type: clusterv1.ClusterRollingOutV1Beta2Condition,
771+
Status: metav1.ConditionUnknown,
772+
Reason: clusterv1.ClusterRollingOutInternalErrorV1Beta2Reason,
773+
Message: "Please check controller logs for errors",
774+
})
775+
return
776+
}
777+
778+
if controlPlane == nil && len(machinePools.Items)+len(machineDeployments.Items) == 0 {
779+
v1beta2conditions.Set(cluster, metav1.Condition{
780+
Type: clusterv1.ClusterRollingOutV1Beta2Condition,
781+
Status: metav1.ConditionFalse,
782+
Reason: clusterv1.ClusterNotRollingOutV1Beta2Reason,
783+
})
784+
return
785+
}
786+
787+
ws := make([]aggregationWrapper, 0, len(machinePools.Items)+len(machineDeployments.Items)+1)
788+
if controlPlane != nil {
789+
// control plane is considered only if it is reporting the condition (the contract does not require conditions to be reported)
790+
// Note: this implies that it won't surface as "Conditions RollingOut not yet reported from ...".
791+
if c, err := v1beta2conditions.UnstructuredGet(controlPlane, clusterv1.RollingOutV1Beta2Condition); err == nil && c != nil {
792+
ws = append(ws, aggregationWrapper{cp: controlPlane})
793+
}
794+
}
795+
for _, mp := range machinePools.Items {
796+
ws = append(ws, aggregationWrapper{mp: &mp})
797+
}
798+
for _, md := range machineDeployments.Items {
799+
ws = append(ws, aggregationWrapper{md: &md})
800+
}
801+
802+
rollingOutCondition, err := v1beta2conditions.NewAggregateCondition(
803+
ws, clusterv1.RollingOutV1Beta2Condition,
804+
v1beta2conditions.TargetConditionType(clusterv1.ClusterRollingOutV1Beta2Condition),
805+
// Instruct aggregate to consider RollingOut condition with negative polarity.
806+
v1beta2conditions.NegativePolarityConditionTypes{clusterv1.RollingOutV1Beta2Condition},
807+
// Using a custom merge strategy to override reasons applied during merge and to ensure merge
808+
// takes into account the fact the RollingOut has negative polarity.
809+
v1beta2conditions.CustomMergeStrategy{
810+
MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
811+
v1beta2conditions.TargetConditionHasPositivePolarity(false),
812+
v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
813+
clusterv1.ClusterRollingOutV1Beta2Reason,
814+
clusterv1.ClusterRollingOutUnknownV1Beta2Reason,
815+
clusterv1.ClusterNotRollingOutV1Beta2Reason,
816+
)),
817+
v1beta2conditions.GetPriorityFunc(v1beta2conditions.GetDefaultMergePriorityFunc(clusterv1.RollingOutV1Beta2Condition)),
818+
),
819+
},
820+
)
821+
if err != nil {
822+
log.Error(err, "Failed to aggregate ControlPlane, MachinePool, MachineDeployment, MachineSet's RollingOut conditions")
823+
v1beta2conditions.Set(cluster, metav1.Condition{
824+
Type: clusterv1.ClusterRollingOutV1Beta2Condition,
825+
Status: metav1.ConditionUnknown,
826+
Reason: clusterv1.ClusterRollingOutInternalErrorV1Beta2Reason,
827+
Message: "Please check controller logs for errors",
828+
})
829+
return
830+
}
831+
832+
v1beta2conditions.Set(cluster, *rollingOutCondition)
833+
}
834+
763835
func setScalingUpCondition(ctx context.Context, cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, machinePools expv1.MachinePoolList, machineDeployments clusterv1.MachineDeploymentList, machineSets clusterv1.MachineSetList, controlPlaneIsNotFound bool, getDescendantsSucceeded bool) {
764836
log := ctrl.LoggerFrom(ctx)
765837

0 commit comments

Comments
 (0)