Skip to content

Commit 53359bb

Browse files
committed
chore: pr review feedback
Signed-off-by: Andy Stoneberg <astonebe@redhat.com>
1 parent 4f3f291 commit 53359bb

18 files changed

+353
-167
lines changed

pkg/lint/check/result/result.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ const (
2222
// an ImpactedObject's ObjectMeta.Annotations, the EnhancedVerboseFormatter renders
2323
// it as a sub-bullet beneath the object reference.
2424
AnnotationObjectContext = "result.opendatahub.io/context"
25+
26+
// AnnotationObjectCRDName is an optional per-object annotation key for the CRD
27+
// fully-qualified name (e.g., "notebooks.kubeflow.org"). Set from ResourceType.CRDFQN()
28+
// when building impacted objects. Used by buildCRDFQNByKind for authoritative plural
29+
// forms, avoiding naive derivation from Kind. Especially useful for multi-kind results
30+
// where the result-level AnnotationResourceCRDName cannot represent all types.
31+
AnnotationObjectCRDName = "result.opendatahub.io/crd-name"
2532
)
2633

2734
const (
@@ -41,10 +48,10 @@ type Impact string
4148

4249
// Impact levels for diagnostic conditions, ordered by severity (highest first).
4350
const (
44-
ImpactAborting Impact = "aborting" // Upgrade MUST NOT proceed
45-
ImpactBlocking Impact = "blocking" // Upgrade CANNOT proceed
46-
ImpactAdvisory Impact = "advisory" // Upgrade CAN proceed with warning
47-
ImpactNone Impact = "" // No impact (omitted from JSON/YAML)
51+
ImpactProhibited Impact = "prohibited" // Upgrade is NOT POSSIBLE
52+
ImpactBlocking Impact = "blocking" // Upgrade CANNOT proceed
53+
ImpactAdvisory Impact = "advisory" // Upgrade CAN proceed with warning
54+
ImpactNone Impact = "" // No impact (omitted from JSON/YAML)
4855
)
4956

5057
// Condition represents a diagnostic condition with severity level.
@@ -78,16 +85,16 @@ func (c Condition) Validate() error {
7885
// False/Unknown status must have impact specified.
7986
if c.Impact == ImpactNone || c.Impact == "" {
8087
return fmt.Errorf(
81-
"condition with Status=%q must have Impact specified (Aborting, Blocking, or Advisory), got Impact=%q",
88+
"condition with Status=%q must have Impact specified (Prohibited, Blocking, or Advisory), got Impact=%q",
8289
c.Status, c.Impact,
8390
)
8491
}
8592

8693
// Validate impact values.
87-
if c.Impact != ImpactAborting && c.Impact != ImpactBlocking && c.Impact != ImpactAdvisory {
94+
if c.Impact != ImpactProhibited && c.Impact != ImpactBlocking && c.Impact != ImpactAdvisory {
8895
return fmt.Errorf(
8996
"invalid Impact=%q, must be %q, %q, or %q",
90-
c.Impact, ImpactAborting, ImpactBlocking, ImpactAdvisory,
97+
c.Impact, ImpactProhibited, ImpactBlocking, ImpactAdvisory,
9198
)
9299
}
93100

@@ -261,8 +268,8 @@ func (r *DiagnosticResult) GetImpact() Impact {
261268

262269
for _, cond := range r.Status.Conditions {
263270
switch cond.Impact {
264-
case ImpactAborting:
265-
return ImpactAborting
271+
case ImpactProhibited:
272+
return ImpactProhibited
266273
case ImpactBlocking:
267274
maxImpact = ImpactBlocking
268275
case ImpactAdvisory:

pkg/lint/check/result/result_test.go

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ func TestDiagnosticResult_Validate_EmptyConditionReason(t *testing.T) {
307307
g.Expect(err.Error()).To(ContainSubstring("has empty reason"))
308308
}
309309

310-
func TestConditionValidate_AbortingWithStatusFalse(t *testing.T) {
310+
func TestConditionValidate_ProhibitedWithStatusFalse(t *testing.T) {
311311
g := NewWithT(t)
312312

313313
cond := result.Condition{
@@ -316,13 +316,13 @@ func TestConditionValidate_AbortingWithStatusFalse(t *testing.T) {
316316
Status: metav1.ConditionFalse,
317317
Reason: "Inconsistent",
318318
},
319-
Impact: result.ImpactAborting,
319+
Impact: result.ImpactProhibited,
320320
}
321321

322322
g.Expect(cond.Validate()).ToNot(HaveOccurred())
323323
}
324324

325-
func TestConditionValidate_AbortingWithStatusUnknown(t *testing.T) {
325+
func TestConditionValidate_ProhibitedWithStatusUnknown(t *testing.T) {
326326
g := NewWithT(t)
327327

328328
cond := result.Condition{
@@ -331,13 +331,13 @@ func TestConditionValidate_AbortingWithStatusUnknown(t *testing.T) {
331331
Status: metav1.ConditionUnknown,
332332
Reason: "CannotDetermine",
333333
},
334-
Impact: result.ImpactAborting,
334+
Impact: result.ImpactProhibited,
335335
}
336336

337337
g.Expect(cond.Validate()).ToNot(HaveOccurred())
338338
}
339339

340-
func TestConditionValidate_AbortingWithStatusTrue_Invalid(t *testing.T) {
340+
func TestConditionValidate_ProhibitedWithStatusTrue_Invalid(t *testing.T) {
341341
g := NewWithT(t)
342342

343343
cond := result.Condition{
@@ -346,7 +346,7 @@ func TestConditionValidate_AbortingWithStatusTrue_Invalid(t *testing.T) {
346346
Status: metav1.ConditionTrue,
347347
Reason: "Consistent",
348348
},
349-
Impact: result.ImpactAborting,
349+
Impact: result.ImpactProhibited,
350350
}
351351

352352
err := cond.Validate()
@@ -839,7 +839,7 @@ func TestSetCondition_MultipleConditionTypes(t *testing.T) {
839839

840840
// GetImpact tests
841841

842-
func TestGetImpact_ReturnsAbortingAsHighest(t *testing.T) {
842+
func TestGetImpact_ReturnsProhibitedAsHighest(t *testing.T) {
843843
g := NewWithT(t)
844844

845845
dr := result.New("workload", "kueue", "data-integrity", "test")
@@ -858,27 +858,27 @@ func TestGetImpact_ReturnsAbortingAsHighest(t *testing.T) {
858858
check.WithImpact(result.ImpactBlocking),
859859
))
860860
dr.SetCondition(check.NewCondition(
861-
"Aborting",
861+
"Prohibited",
862862
metav1.ConditionFalse,
863863
check.WithReason("Reason"),
864-
check.WithMessage("aborting finding"),
865-
check.WithImpact(result.ImpactAborting),
864+
check.WithMessage("prohibited finding"),
865+
check.WithImpact(result.ImpactProhibited),
866866
))
867867

868-
g.Expect(dr.GetImpact()).To(Equal(result.ImpactAborting))
868+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactProhibited))
869869
}
870870

871-
func TestGetImpact_AbortingShortCircuits(t *testing.T) {
871+
func TestGetImpact_ProhibitedShortCircuits(t *testing.T) {
872872
g := NewWithT(t)
873873

874-
// Aborting condition first — should return immediately without checking others.
874+
// Prohibited condition first — should return immediately without checking others.
875875
dr := result.New("workload", "kueue", "data-integrity", "test")
876876
dr.SetCondition(check.NewCondition(
877-
"Aborting",
877+
"Prohibited",
878878
metav1.ConditionFalse,
879879
check.WithReason("Reason"),
880-
check.WithMessage("aborting finding"),
881-
check.WithImpact(result.ImpactAborting),
880+
check.WithMessage("prohibited finding"),
881+
check.WithImpact(result.ImpactProhibited),
882882
))
883883
dr.SetCondition(check.NewCondition(
884884
"Passing",
@@ -887,10 +887,10 @@ func TestGetImpact_AbortingShortCircuits(t *testing.T) {
887887
check.WithMessage("all good"),
888888
))
889889

890-
g.Expect(dr.GetImpact()).To(Equal(result.ImpactAborting))
890+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactProhibited))
891891
}
892892

893-
func TestGetImpact_BlockingWithoutAborting(t *testing.T) {
893+
func TestGetImpact_BlockingWithoutProhibited(t *testing.T) {
894894
g := NewWithT(t)
895895

896896
dr := result.New("component", "kserve", "removal", "test")
@@ -912,6 +912,21 @@ func TestGetImpact_BlockingWithoutAborting(t *testing.T) {
912912
g.Expect(dr.GetImpact()).To(Equal(result.ImpactBlocking))
913913
}
914914

915+
func TestGetImpact_AdvisoryWithoutBlockingOrProhibited(t *testing.T) {
916+
g := NewWithT(t)
917+
918+
dr := result.New("component", "dashboard", "migration", "test")
919+
dr.SetCondition(check.NewCondition(
920+
"Advisory",
921+
metav1.ConditionFalse,
922+
check.WithReason("Reason"),
923+
check.WithMessage("advisory finding"),
924+
check.WithImpact(result.ImpactAdvisory),
925+
))
926+
927+
g.Expect(dr.GetImpact()).To(Equal(result.ImpactAdvisory))
928+
}
929+
915930
func TestGetImpact_NoneWhenAllPassing(t *testing.T) {
916931
g := NewWithT(t)
917932

pkg/lint/check/verbose_formatter.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@ func writeQualifiedObjects(out io.Writer, objects []qualifiedObject, indent stri
217217
}
218218

219219
// buildCRDFQNByKind returns a map from Kind to CRD FQN for the impacted objects.
220-
// When all objects share the same kind and the AnnotationResourceCRDName annotation
221-
// is set, the annotation value is used for the most accurate plural form.
222-
// Otherwise, the CRD FQN is derived from each object's TypeMeta.
220+
// Resolution priority per kind:
221+
// 1. Per-object AnnotationObjectCRDName (authoritative, set from ResourceType.CRDFQN())
222+
// 2. Result-level AnnotationResourceCRDName (single-kind results only)
223+
// 3. Naive derivation from TypeMeta (fallback)
223224
func buildCRDFQNByKind(dr *result.DiagnosticResult) map[string]string {
224225
fqnByKind := make(map[string]string)
225226

@@ -228,11 +229,19 @@ func buildCRDFQNByKind(dr *result.DiagnosticResult) map[string]string {
228229
continue
229230
}
230231

232+
// Prefer per-object annotation set from ResourceType.CRDFQN().
233+
if name, ok := obj.Annotations[result.AnnotationObjectCRDName]; ok && name != "" {
234+
fqnByKind[obj.Kind] = name
235+
236+
continue
237+
}
238+
231239
fqnByKind[obj.Kind] = DeriveCRDFQNFromTypeMeta([]metav1.PartialObjectMetadata{obj})
232240
}
233241

234-
// When there is exactly one kind and the annotation is set, prefer the annotation
235-
// for the most accurate plural form (e.g. "inferenceservices" not "inferenceservicss").
242+
// When there is exactly one kind and the result-level annotation is set,
243+
// prefer it over naive derivation (backward compatibility for callers
244+
// that use SetImpactedObjects/AddImpactedObjects).
236245
if len(fqnByKind) == 1 {
237246
if name, ok := dr.Annotations[result.AnnotationResourceCRDName]; ok && name != "" {
238247
for kind := range fqnByKind {
@@ -275,8 +284,8 @@ func DeriveCRDFQNFromTypeMeta(objects []metav1.PartialObjectMetadata) string {
275284
return ""
276285
}
277286

278-
// Kubernetes CRD plural convention: lowercase kind + "s"
279-
// (e.g. Notebook → notebooks, InferenceService → inferenceservices, RayCluster → rayclusters).
287+
// Naive plural as fallback — callers should set AnnotationResourceCRDName
288+
// via ResourceType.CRDFQN() for authoritative plural forms.
280289
plural := strings.ToLower(kind) + "s"
281290

282291
// Extract API group from APIVersion (e.g. "kubeflow.org/v1" → "kubeflow.org").

pkg/lint/check/verbose_formatter_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,36 @@ func TestEnhancedVerboseFormatter_MixedKinds(t *testing.T) {
292292
g.Expect(buf.String()).To(Equal(expected))
293293
}
294294

295+
func TestEnhancedVerboseFormatter_MixedKindsIgnoresAnnotation(t *testing.T) {
296+
g := NewWithT(t)
297+
298+
// When multiple kinds are present, the AnnotationResourceCRDName annotation
299+
// should be ignored — it only applies to single-kind results.
300+
dr := result.New("workload", "kueue", "data-integrity", "test description")
301+
dr.Annotations[result.AnnotationResourceCRDName] = "notebooks.kubeflow.org"
302+
dr.ImpactedObjects = []metav1.PartialObjectMetadata{
303+
{
304+
TypeMeta: metav1.TypeMeta{Kind: "Notebook", APIVersion: "kubeflow.org/v1"},
305+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns-a", Name: "my-nb"},
306+
},
307+
{
308+
TypeMeta: metav1.TypeMeta{Kind: "RayCluster", APIVersion: "ray.io/v1"},
309+
ObjectMeta: metav1.ObjectMeta{Namespace: "ns-a", Name: "my-ray"},
310+
},
311+
}
312+
313+
formatter := &check.EnhancedVerboseFormatter{}
314+
315+
var buf bytes.Buffer
316+
formatter.FormatVerboseOutput(&buf, dr)
317+
318+
output := buf.String()
319+
320+
// RayCluster should use derived FQN, not the annotation value.
321+
g.Expect(output).To(ContainSubstring("rayclusters.ray.io/my-ray"))
322+
g.Expect(output).To(ContainSubstring("notebooks.kubeflow.org/my-nb"))
323+
}
324+
295325
func TestEnhancedVerboseFormatter_MixedKindsSortedByCRDThenName(t *testing.T) {
296326
g := NewWithT(t)
297327

pkg/lint/checks/components/kueue/kueue_operator_installed.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424

2525
// OperatorInstalledCheck validates the Red Hat build of Kueue operator installation status against the Kueue
2626
// component management state:
27-
// - Managed: aborting — no supported upgrade path from embedded Kueue, must migrate to RHBoK first
27+
// - Managed: prohibited — no supported upgrade path from embedded Kueue, must migrate to RHBoK first
2828
// - Unmanaged + operator absent: blocking — Unmanaged requires the Red Hat build of Kueue operator
2929
// - Unmanaged + operator present: pass
3030
type OperatorInstalledCheck struct {
@@ -81,7 +81,7 @@ func (c *OperatorInstalledCheck) Validate(ctx context.Context, target check.Targ
8181
})
8282
}
8383

84-
// validateManaged flags Kueue Managed state as a fatal condition.
84+
// validateManaged flags Kueue Managed state as a prohibited condition.
8585
// There is no supported upgrade path from embedded Kueue — migration to the
8686
// Red Hat build of Kueue operator is required before upgrading.
8787
func (c *OperatorInstalledCheck) validateManaged(
@@ -93,7 +93,7 @@ func (c *OperatorInstalledCheck) validateManaged(
9393
metav1.ConditionFalse,
9494
check.WithReason(check.ReasonVersionIncompatible),
9595
check.WithMessage(msgManagedNotSupported),
96-
check.WithImpact(result.ImpactAborting),
96+
check.WithImpact(result.ImpactProhibited),
9797
check.WithRemediation(managementStateRemediation),
9898
))
9999
}

pkg/lint/checks/components/kueue/kueue_operator_installed_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestOperatorInstalledCheck_UnmanagedNotInstalled(t *testing.T) {
8282
g.Expect(result.Status.Conditions[0].Impact).To(Equal(resultpkg.ImpactBlocking))
8383
}
8484

85-
// Managed + operator NOT installed = aborting.
85+
// Managed + operator NOT installed = prohibited.
8686
func TestOperatorInstalledCheck_ManagedNotInstalled(t *testing.T) {
8787
g := NewWithT(t)
8888
ctx := t.Context()
@@ -105,10 +105,10 @@ func TestOperatorInstalledCheck_ManagedNotInstalled(t *testing.T) {
105105
"Reason": Equal(check.ReasonVersionIncompatible),
106106
"Message": ContainSubstring("migration to the Red Hat build of Kueue operator is required"),
107107
}))
108-
g.Expect(result.Status.Conditions[0].Impact).To(Equal(resultpkg.ImpactAborting))
108+
g.Expect(result.Status.Conditions[0].Impact).To(Equal(resultpkg.ImpactProhibited))
109109
}
110110

111-
// Managed + operator installed = aborting.
111+
// Managed + operator installed = prohibited.
112112
func TestOperatorInstalledCheck_ManagedInstalled(t *testing.T) {
113113
g := NewWithT(t)
114114
ctx := t.Context()
@@ -131,7 +131,7 @@ func TestOperatorInstalledCheck_ManagedInstalled(t *testing.T) {
131131
"Reason": Equal(check.ReasonVersionIncompatible),
132132
"Message": ContainSubstring("migration to the Red Hat build of Kueue operator is required"),
133133
}))
134-
g.Expect(result.Status.Conditions[0].Impact).To(Equal(resultpkg.ImpactAborting))
134+
g.Expect(result.Status.Conditions[0].Impact).To(Equal(resultpkg.ImpactProhibited))
135135
g.Expect(result.Annotations).To(HaveKeyWithValue("operator.opendatahub.io/installed-version", "kueue-operator.v0.6.0"))
136136
}
137137

0 commit comments

Comments
 (0)