From d4cd804bb4a28286678eb7748e85c6b6f00ca44d Mon Sep 17 00:00:00 2001 From: Andres Llausas Date: Tue, 24 Feb 2026 12:53:42 -0500 Subject: [PATCH 1/4] Adding output of impacted isvcs when --verbose flag is set. Name, Namespace, and Deployment mode is printed out. Signed-off-by: Andres Llausas --- pkg/lint/checks/workloads/kserve/impacted.go | 71 ++++++++++++++++++ .../checks/workloads/kserve/impacted_test.go | 73 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/pkg/lint/checks/workloads/kserve/impacted.go b/pkg/lint/checks/workloads/kserve/impacted.go index 433e8509..ae7f4b8a 100644 --- a/pkg/lint/checks/workloads/kserve/impacted.go +++ b/pkg/lint/checks/workloads/kserve/impacted.go @@ -3,6 +3,8 @@ package kserve import ( "context" "fmt" + "io" + "sort" "strconv" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -11,6 +13,7 @@ import ( "github.com/opendatahub-io/odh-cli/pkg/constants" "github.com/opendatahub-io/odh-cli/pkg/lint/check" "github.com/opendatahub-io/odh-cli/pkg/lint/check/result" + "github.com/opendatahub-io/odh-cli/pkg/printer/table" "github.com/opendatahub-io/odh-cli/pkg/resources" "github.com/opendatahub-io/odh-cli/pkg/util/client" "github.com/opendatahub-io/odh-cli/pkg/util/components" @@ -164,3 +167,71 @@ func (c *ImpactedWorkloadsCheck) Validate( return dr, nil } + +// inferenceServiceRow represents a row in the InferenceService detail table. +type inferenceServiceRow struct { + Name string `mapstructure:"NAME"` + Namespace string `mapstructure:"NAMESPACE"` + DeploymentMode string `mapstructure:"DEPLOYMENT MODE"` +} + +// FormatVerboseOutput provides custom formatting for InferenceServices in verbose mode. +// Displays a detailed table showing Name, Namespace, and DeploymentMode for each InferenceService. +func (c *ImpactedWorkloadsCheck) FormatVerboseOutput(out io.Writer, dr *result.DiagnosticResult) { + // Collect InferenceServices from impacted objects + var isvcs []inferenceServiceRow + + for _, obj := range dr.ImpactedObjects { + if obj.Kind != "InferenceService" { + continue + } + + deploymentMode := obj.Annotations[annotationDeploymentMode] + if deploymentMode == "" { + // Check for runtime annotation (for removed runtime ISVCs) + if runtime := obj.Annotations["serving.kserve.io/runtime"]; runtime != "" { + deploymentMode = "RawDeployment" + } else { + deploymentMode = "Unknown" + } + } + + isvcs = append(isvcs, inferenceServiceRow{ + Name: obj.Name, + Namespace: obj.Namespace, + DeploymentMode: deploymentMode, + }) + } + + if len(isvcs) == 0 { + return + } + + // Sort by namespace, then by name + sort.Slice(isvcs, func(i, j int) bool { + if isvcs[i].Namespace != isvcs[j].Namespace { + return isvcs[i].Namespace < isvcs[j].Namespace + } + + return isvcs[i].Name < isvcs[j].Name + }) + + // Render table with InferenceService details + renderer := table.NewRenderer[inferenceServiceRow]( + table.WithWriter[inferenceServiceRow](out), + table.WithHeaders[inferenceServiceRow]("NAME", "NAMESPACE", "DEPLOYMENT MODE"), + table.WithTableOptions[inferenceServiceRow](table.DefaultTableOptions...), + ) + + for _, isvc := range isvcs { + if err := renderer.Append(isvc); err != nil { + _, _ = fmt.Fprintf(out, " Error rendering InferenceService: %v\n", err) + + return + } + } + + if err := renderer.Render(); err != nil { + _, _ = fmt.Fprintf(out, " Error rendering table: %v\n", err) + } +} diff --git a/pkg/lint/checks/workloads/kserve/impacted_test.go b/pkg/lint/checks/workloads/kserve/impacted_test.go index 40afa9a1..9938db55 100644 --- a/pkg/lint/checks/workloads/kserve/impacted_test.go +++ b/pkg/lint/checks/workloads/kserve/impacted_test.go @@ -1,6 +1,7 @@ package kserve_test import ( + "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -1253,3 +1254,75 @@ func TestImpactedWorkloadsCheck_AcceleratorSR_MixedAnnotations(t *testing.T) { // 2 SRs + 1 ISVC = 3 impacted objects g.Expect(result.Annotations).To(HaveKeyWithValue(check.AnnotationImpactedWorkloadCount, "3")) } + +func TestImpactedWorkloadsCheck_FormatVerboseOutput(t *testing.T) { + g := NewWithT(t) + + // Create a diagnostic result with multiple InferenceServices + dr := resultpkg.New("workload", "kserve", "impacted-workloads", "Test description") + dr.ImpactedObjects = []metav1.PartialObjectMetadata{ + { + TypeMeta: resources.InferenceService.TypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-b", + Name: "isvc-modelmesh", + Annotations: map[string]string{ + annotationDeploymentMode: "ModelMesh", + }, + }, + }, + { + TypeMeta: resources.InferenceService.TypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-a", + Name: "isvc-serverless", + Annotations: map[string]string{ + annotationDeploymentMode: "Serverless", + }, + }, + }, + { + TypeMeta: resources.ServingRuntime.TypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-a", + Name: "some-runtime", + }, + }, + } + + chk := kserve.NewImpactedWorkloadsCheck() + var buf strings.Builder + chk.FormatVerboseOutput(&buf, dr) + + output := buf.String() + + // Verify table headers are present + g.Expect(output).To(ContainSubstring("NAME")) + g.Expect(output).To(ContainSubstring("NAMESPACE")) + g.Expect(output).To(ContainSubstring("DEPLOYMENT MODE")) + + // Verify InferenceServices are present (sorted by namespace then name) + g.Expect(output).To(ContainSubstring("isvc-serverless")) + g.Expect(output).To(ContainSubstring("ns-a")) + g.Expect(output).To(ContainSubstring("Serverless")) + + g.Expect(output).To(ContainSubstring("isvc-modelmesh")) + g.Expect(output).To(ContainSubstring("ns-b")) + g.Expect(output).To(ContainSubstring("ModelMesh")) + + // Verify ServingRuntime is NOT included (only InferenceServices) + g.Expect(output).ToNot(ContainSubstring("some-runtime")) +} + +func TestImpactedWorkloadsCheck_FormatVerboseOutput_EmptyResult(t *testing.T) { + g := NewWithT(t) + + dr := resultpkg.New("workload", "kserve", "impacted-workloads", "Test description") + + chk := kserve.NewImpactedWorkloadsCheck() + var buf strings.Builder + chk.FormatVerboseOutput(&buf, dr) + + // Empty result should produce no output + g.Expect(buf.String()).To(BeEmpty()) +} From 44075c38a8591dbe866c2b82c2215eb76f7ca273 Mon Sep 17 00:00:00 2001 From: Andres Llausas Date: Tue, 24 Feb 2026 13:21:53 -0500 Subject: [PATCH 2/4] Added filtering flag to only display serverless or modelmesh isvcs in verbose output. Default to all. Signed-off-by: Andres Llausas --- pkg/lint/checks/workloads/kserve/impacted.go | 27 ++++++++++++++++++++ pkg/lint/command.go | 25 ++++++++++++++++++ pkg/lint/constants.go | 15 ++++++----- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/pkg/lint/checks/workloads/kserve/impacted.go b/pkg/lint/checks/workloads/kserve/impacted.go index ae7f4b8a..90ff89d6 100644 --- a/pkg/lint/checks/workloads/kserve/impacted.go +++ b/pkg/lint/checks/workloads/kserve/impacted.go @@ -51,6 +51,10 @@ const ( // ImpactedWorkloadsCheck lists InferenceServices and ServingRuntimes using deprecated deployment modes. type ImpactedWorkloadsCheck struct { check.BaseCheck + + // deploymentModeFilter filters InferenceServices by deployment mode in verbose output. + // Valid values: "all" (default), "serverless", "modelmesh". + deploymentModeFilter string } func NewImpactedWorkloadsCheck() *ImpactedWorkloadsCheck { @@ -64,9 +68,16 @@ func NewImpactedWorkloadsCheck() *ImpactedWorkloadsCheck { CheckDescription: "Lists InferenceServices and ServingRuntimes using deprecated deployment modes (ModelMesh, Serverless), removed ServingRuntimes, or ServingRuntimes referencing deprecated AcceleratorProfiles that will be impacted in RHOAI 3.x", CheckRemediation: "Migrate InferenceServices from Serverless/ModelMesh to RawDeployment mode, update ServingRuntimes to supported versions, and review AcceleratorProfile references before upgrading", }, + deploymentModeFilter: "all", // Default to showing all deployment modes } } +// SetDeploymentModeFilter sets the filter for InferenceService display by deployment mode. +// Valid values: "all", "serverless", "modelmesh". +func (c *ImpactedWorkloadsCheck) SetDeploymentModeFilter(filter string) { + c.deploymentModeFilter = filter +} + // CanApply returns whether this check should run for the given target. // Only applies when upgrading FROM 2.x TO 3.x and KServe or ModelMesh is Managed. func (c *ImpactedWorkloadsCheck) CanApply(ctx context.Context, target check.Target) (bool, error) { @@ -177,6 +188,7 @@ type inferenceServiceRow struct { // FormatVerboseOutput provides custom formatting for InferenceServices in verbose mode. // Displays a detailed table showing Name, Namespace, and DeploymentMode for each InferenceService. +// Filters InferenceServices based on the deploymentModeFilter setting. func (c *ImpactedWorkloadsCheck) FormatVerboseOutput(out io.Writer, dr *result.DiagnosticResult) { // Collect InferenceServices from impacted objects var isvcs []inferenceServiceRow @@ -196,6 +208,21 @@ func (c *ImpactedWorkloadsCheck) FormatVerboseOutput(out io.Writer, dr *result.D } } + // Apply deployment mode filter + if c.deploymentModeFilter != "all" { + filterMode := "" + switch c.deploymentModeFilter { + case "serverless": + filterMode = deploymentModeServerless + case "modelmesh": + filterMode = deploymentModeModelMesh + } + + if deploymentMode != filterMode { + continue + } + } + isvcs = append(isvcs, inferenceServiceRow{ Name: obj.Name, Namespace: obj.Namespace, diff --git a/pkg/lint/command.go b/pkg/lint/command.go index a7a86a47..6648b8de 100644 --- a/pkg/lint/command.go +++ b/pkg/lint/command.go @@ -3,6 +3,7 @@ package lint import ( "context" "fmt" + "slices" "github.com/blang/semver/v4" "github.com/spf13/pflag" @@ -47,6 +48,10 @@ type Command struct { // If set, runs in upgrade mode (assesses upgrade readiness to target version). TargetVersion string + // ISVCDeploymentMode filters InferenceService display by deployment mode. + // Valid values: "all" (default), "serverless", "modelmesh". + ISVCDeploymentMode string + // parsedTargetVersion is the parsed semver version (upgrade mode only) parsedTargetVersion *semver.Version @@ -135,6 +140,7 @@ func (c *Command) AddFlags(fs *pflag.FlagSet) { fs.BoolVarP(&c.Verbose, "verbose", "v", false, flagDescVerbose) fs.BoolVar(&c.Debug, "debug", false, flagDescDebug) fs.DurationVar(&c.Timeout, "timeout", c.Timeout, flagDescTimeout) + fs.StringVar(&c.ISVCDeploymentMode, "isvc-deployment-mode", "all", flagDescISVCDeploymentMode) // Throttling settings fs.Float32Var(&c.QPS, "qps", c.QPS, flagDescQPS) @@ -174,6 +180,12 @@ func (c *Command) Validate() error { return fmt.Errorf("validating shared options: %w", err) } + // Validate ISVC deployment mode filter + validModes := []string{"all", "serverless", "modelmesh"} + if !slices.Contains(validModes, c.ISVCDeploymentMode) { + return fmt.Errorf("invalid isvc-deployment-mode: %s (must be one of: all, serverless, modelmesh)", c.ISVCDeploymentMode) + } + return nil } @@ -222,6 +234,16 @@ func (c *Command) Run(ctx context.Context) error { return c.runUpgradeMode(ctx, currentVersion) } +// configureCheckSettings applies command-level settings to specific checks. +func (c *Command) configureCheckSettings() { + // Apply ISVC deployment mode filter to the KServe impacted workloads check + for _, chk := range c.registry.ListAll() { + if isvcCheck, ok := chk.(*kserveworkloads.ImpactedWorkloadsCheck); ok { + isvcCheck.SetDeploymentModeFilter(c.ISVCDeploymentMode) + } + } +} + // runLintMode validates current cluster state. // //nolint:unparam // keep explicit error return value @@ -243,6 +265,9 @@ func (c *Command) runLintMode(_ context.Context, currentVersion *semver.Version) func (c *Command) runUpgradeMode(ctx context.Context, currentVersion *semver.Version) error { c.IO.Errorf("Assessing upgrade readiness: %s → %s\n", currentVersion.String(), c.TargetVersion) + // Configure check-specific settings + c.configureCheckSettings() + // Execute checks using target version for applicability filtering c.IO.Errorf("Running upgrade compatibility checks...") executor := check.NewExecutor(c.registry, c.IO) diff --git a/pkg/lint/constants.go b/pkg/lint/constants.go index 029b3a41..b645a1fb 100644 --- a/pkg/lint/constants.go +++ b/pkg/lint/constants.go @@ -2,13 +2,14 @@ package lint // Flag descriptions for the lint command. const ( - flagDescTargetVersion = "target version for upgrade readiness checks (e.g., 2.25.0, 3.0.0)" - flagDescOutput = "output format (table|json|yaml)" - flagDescVerbose = "show impacted objects and summary information" - flagDescDebug = "show detailed diagnostic logs for troubleshooting" - flagDescTimeout = "operation timeout (e.g., 10m, 30m)" - flagDescQPS = "Kubernetes API QPS limit (queries per second)" - flagDescBurst = "Kubernetes API burst capacity" + flagDescTargetVersion = "target version for upgrade readiness checks (e.g., 2.25.0, 3.0.0)" + flagDescOutput = "output format (table|json|yaml)" + flagDescVerbose = "show impacted objects and summary information" + flagDescDebug = "show detailed diagnostic logs for troubleshooting" + flagDescTimeout = "operation timeout (e.g., 10m, 30m)" + flagDescQPS = "Kubernetes API QPS limit (queries per second)" + flagDescBurst = "Kubernetes API burst capacity" + flagDescISVCDeploymentMode = "filter InferenceService display by deployment mode (all|serverless|modelmesh)" ) const flagDescChecks = `check selector patterns (glob patterns or categories): From 0fb4829be9ca1aac24fd776606aca71ac2aea57d Mon Sep 17 00:00:00 2001 From: Andres Llausas Date: Tue, 24 Feb 2026 13:23:47 -0500 Subject: [PATCH 3/4] Retrigger ci Signed-off-by: Andres Llausas From e508ceb1b56a436526c7df3732b7b84425bd065b Mon Sep 17 00:00:00 2001 From: Andres Llausas Date: Tue, 24 Feb 2026 13:36:19 -0500 Subject: [PATCH 4/4] Fix: Correct isvc-deployment-mode validation in lint command Signed-off-by: Andres Llausas --- pkg/lint/command.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/lint/command.go b/pkg/lint/command.go index 6648b8de..106e9c0b 100644 --- a/pkg/lint/command.go +++ b/pkg/lint/command.go @@ -120,8 +120,9 @@ func NewCommand( registry.MustRegister(trainingoperatorworkloads.NewImpactedWorkloadsCheck()) c := &Command{ - SharedOptions: shared, - registry: registry, + SharedOptions: shared, + registry: registry, + ISVCDeploymentMode: "all", } // Apply functional options