用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/opendatahub-io/odh-cli --skill lint-check命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | lint-check |
| description | Create a new lint check for the odh-cli lint command |
This skill streamlines creating new lint checks for kubectl odh lint.
Before implementing, gather the following from the user:
check.CheckType* when applicable, or a package-level string constant for custom typesreturn true, nilversion.IsUpgradeFrom2xTo3x(target.CurrentVersion, target.TargetVersion)version.IsVersion3x(target.CurrentVersion) || version.IsVersion3x(target.TargetVersion)From the gathered information:
<group>.<kind>.<type> (e.g., components.kserve.serverless-removal)<Group> :: <Kind> :: <Description> (e.g., Components :: KServe :: Serverless Removal (3.x))Use the appropriate builder for each check group. These handle resource fetching, error handling, and annotation population automatically.
validate.Component(c, target)For checks that validate DSC component configuration. CheckKind() must match the DSC spec key (e.g., "kserve", "dashboard", lowercase).
Chainable methods:
.InState(states...) — only run when component has one of these management states; otherwise returns a "not configured" pass.WithApplicationsNamespace() — loads applications namespace from DSCI into req.ApplicationsNamespaceTerminal methods:
.Run(ctx, func(ctx, req *validate.ComponentRequest) error) — full control over result.Complete(ctx, func(ctx, req) ([]result.Condition, error)) — just return conditionsComponentRequest fields: Target (embedded), Result, DSC, ManagementState, ApplicationsNamespace.
validate.Workloads(c, target, resources.X) / validate.WorkloadsMetadata(c, target, resources.X)For checks that list and validate workload instances. Use WorkloadsMetadata when only name/namespace/labels/annotations/finalizers are needed; use Workloads when spec/status fields are required.
Chainable methods:
.Filter(func(item) (bool, error)) — keep only matching itemsTerminal methods:
.Run(ctx, func(ctx, req *validate.WorkloadRequest[T]) error) — full control.Complete(ctx, func(ctx, req) ([]result.Condition, error)) — just return conditionsWorkloadRequest[T] fields: Target (embedded), Result, Items []T.
Auto-populates ImpactedObjects from filtered items if the callback does not set them. CRD not found is treated as an empty list (not an error).
validate.DSCI(c, target)For service checks that validate DSCInitialization configuration.
Terminal method:
.Run(ctx, func(dr *result.DiagnosticResult, dsci *unstructured.Unstructured) error) — note: callback has no ctx parametervalidate.Operator(c, target)For dependency checks that validate OLM operator presence.
Chainable methods:
.WithNames(names...) — override subscription name matching (default: c.CheckKind()).WithChannels(channels...) — restrict to specific channels.WithConditionBuilder(func(found bool, version string) result.Condition) — custom condition logicTerminal method:
.Run(ctx) — no callback; uses the condition builderCreate conditions with check.NewCondition(conditionType, status, opts...):
check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionFalse,
check.WithReason(check.ReasonVersionIncompatible),
check.WithMessage("Feature X is enabled (state: %s) but removed in RHOAI %s", state, tv),
check.WithImpact(result.ImpactBlocking),
check.WithRemediation("Disable feature X before upgrading"),
)
Options:
check.WithReason(reason) — condition reason (required, panics if empty)check.WithMessage(format, args...) — printf-style messagecheck.WithImpact(impact) — override auto-derived impactcheck.WithRemediation(text) — actionable fix guidanceImpact auto-derivation:
Status=True → ImpactNone (requirement met)Status=False → ImpactAdvisory (warning, upgrade CAN proceed)Status=Unknown → ImpactAdvisoryUse check.WithImpact(result.ImpactBlocking) explicitly for conditions that block upgrades.
Condition Types: ConditionTypeValidated, ConditionTypeAvailable, ConditionTypeReady, ConditionTypeCompatible, ConditionTypeConfigured, ConditionTypeAuthorized, ConditionTypeMigrationRequired
Success Reasons: ReasonRequirementsMet, ReasonResourceFound, ReasonResourceAvailable, ReasonConfigurationValid, ReasonVersionCompatible, ReasonPermissionGranted, ReasonComponentRenamed, ReasonMigrationPending, ReasonNoMigrationRequired
Failure Reasons: ReasonResourceNotFound, ReasonResourceUnavailable, ReasonConfigurationInvalid, ReasonVersionIncompatible, ReasonPermissionDenied, ReasonQuotaExceeded, ReasonDependencyUnavailable, ReasonDeprecated, ReasonWorkloadsImpacted, ReasonFeatureRemoved, ReasonConfigurationUnmanaged
Unknown Reasons: ReasonCheckExecutionFailed, ReasonCheckSkipped, ReasonAPIAccessDenied, ReasonInsufficientData
Check Types: check.CheckTypeRemoval, check.CheckTypeInstalled, check.CheckTypeImpactedWorkloads, check.CheckTypeConfigMigration, check.CheckTypeAcceleratorProfileMigration — or define a package-level const checkType = "your-type" for custom types.
Annotations: check.AnnotationComponentManagementState, check.AnnotationCheckTargetVersion, check.AnnotationImpactedWorkloadCount
After gathering information and receiving user approval:
CRITICAL: Before creating any files, check if they already exist:
pkg/lint/checks/<group>/<kind>/<type>.go existspkg/lint/checks/<group>/<kind>/<type>_test.go existsIf any file exists, ask the user:
<path> already exists. What would you like to do?"
Do NOT proceed with file creation until conflicts are resolved.
Create pkg/lint/checks/<group>/<kind>/<type>.go using the appropriate template for the check group.
package <kind>
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/lint/check/validate"
"github.com/opendatahub-io/odh-cli/pkg/util/client"
"github.com/opendatahub-io/odh-cli/pkg/util/components"
"github.com/opendatahub-io/odh-cli/pkg/util/jq"
"github.com/opendatahub-io/odh-cli/pkg/util/version"
)
type <CheckName>Check struct {
check.BaseCheck
}
func New<CheckName>Check() *<CheckName>Check {
return &<CheckName>Check{
BaseCheck: check.BaseCheck{
CheckGroup: check.GroupComponent,
Kind: constants.Component<Kind>, // or a string literal like "kueue"
Type: check.CheckType<Type>, // or a package-level const
CheckID: "components.<kind>.<type>",
CheckName: "Components :: <Kind> :: <Description>",
CheckDescription: "<description>",
CheckRemediation: "<remediation>",
},
}
}
func (c *<CheckName>Check) CanApply(ctx context.Context, target check.Target) (bool, error) {
// Version logic based on user input
return true, nil
}
func Validate(
ctx context.Context,
target check.Target,
) (*result.DiagnosticResult, ) {
validate.Component(c, target).
Run(ctx, {
tv := version.MajorMinorLabel(req.TargetVersion)
req.Result.SetCondition(check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionTrue,
check.WithReason(check.ReasonVersionCompatible),
check.WithMessage(, tv),
))
})
}
package <kind>
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/lint/check/validate"
"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"
"github.com/opendatahub-io/odh-cli/pkg/util/version"
)
type <CheckName>Check struct {
check.BaseCheck
}
func New<CheckName>Check() *<CheckName>Check {
return &<CheckName>Check{
BaseCheck: check.BaseCheck{
CheckGroup: check.GroupWorkload,
Kind: "<kind>",
Type: check.CheckTypeImpactedWorkloads,
CheckID: "workloads.<kind>.<type>",
CheckName: "Workloads :: <Kind> :: <Description>",
CheckDescription: "<description>",
CheckRemediation: "<remediation>",
},
}
}
func (c *<CheckName>Check) CanApply(ctx context.Context, target check.Target) (bool, error) {
// Version logic based on user input
return true, nil
}
func (c *<CheckName>Check) Validate(
ctx context.Context,
target check.Target,
) (*result.DiagnosticResult, error) {
validate.WorkloadsMetadata(c, target, resources.<ResourceType>).
Filter( (, ) {
,
}).
Complete(ctx, ([]result.Condition, ) {
tv := version.MajorMinorLabel(req.TargetVersion)
count := (req.Items)
count == {
[]result.Condition{
check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionTrue,
check.WithReason(check.ReasonVersionCompatible),
check.WithMessage(, tv),
),
},
}
[]result.Condition{
check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionFalse,
check.WithReason(check.ReasonWorkloadsImpacted),
check.WithMessage(, count, tv),
check.WithImpact(result.ImpactBlocking),
check.WithRemediation(c.CheckRemediation),
),
},
})
}
package <kind>
import (
"context"
"errors"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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/lint/check/validate"
"github.com/opendatahub-io/odh-cli/pkg/util/jq"
"github.com/opendatahub-io/odh-cli/pkg/util/version"
)
type <CheckName>Check struct {
check.BaseCheck
}
func New<CheckName>Check() *<CheckName>Check {
return &<CheckName>Check{
BaseCheck: check.BaseCheck{
CheckGroup: check.GroupService,
Kind: "<kind>",
Type: check.CheckTypeRemoval,
CheckID: "services.<kind>.<type>",
CheckName: "Services :: <Kind> :: <Description>",
CheckDescription: "<description>",
CheckRemediation: "<remediation>",
},
}
}
func (c *<CheckName>Check) CanApply(_ context.Context, target check.Target) (bool, error) {
return version.IsUpgradeFrom2xTo3x(target.CurrentVersion, target.TargetVersion), nil
}
func (c *<CheckName>Check) Validate(
ctx context.Context,
target check.Target,
) (*result.DiagnosticResult, error) {
tv := version.MajorMinorLabel(target.TargetVersion)
validate.DSCI(c, target).Run(ctx, {
state, err := jq.Query[](dsci, )
{
errors.Is(err, jq.ErrNotFound):
dr.SetCondition(check.NewCondition(
check.ConditionTypeConfigured,
metav1.ConditionFalse,
check.WithReason(check.ReasonResourceNotFound),
check.WithMessage(),
))
err != :
fmt.Errorf(, err)
state == constants.ManagementStateManaged:
dr.SetCondition(check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionFalse,
check.WithReason(check.ReasonVersionIncompatible),
check.WithMessage(, state, tv),
check.WithImpact(result.ImpactBlocking),
check.WithRemediation(c.CheckRemediation),
))
:
dr.SetCondition(check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionTrue,
check.WithReason(check.ReasonVersionCompatible),
check.WithMessage(, state, tv),
))
}
})
}
Dependency checks typically use c.NewResult() directly (no builder) or validate.Operator() for OLM checks.
package <kind>
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/util/version"
)
type <CheckName>Check struct {
check.BaseCheck
}
func New<CheckName>Check() *<CheckName>Check {
return &<CheckName>Check{
BaseCheck: check.BaseCheck{
CheckGroup: check.GroupDependency,
Kind: "<kind>",
Type: "<check-type>",
CheckID: "dependencies.<kind>.<type>",
CheckName: "Dependencies :: <Kind> :: <Description>",
CheckDescription: "<description>",
},
}
}
func (c *<CheckName>Check) CanApply(_ context.Context, target check.Target) (bool, error) {
return version.IsVersion3x(target.CurrentVersion) || version.IsVersion3x(target.TargetVersion), nil
}
func (c *<CheckName>Check) Validate(
ctx context.Context,
target check.Target,
) (*result.DiagnosticResult, error) {
dr := c.NewResult()
tv := version.MajorMinorLabel(target.TargetVersion)
// Perform validation (e.g., version detection, API checks)
// ...
dr.SetCondition(check.NewCondition(
check.ConditionTypeCompatible,
metav1.ConditionTrue,
check.WithReason(check.ReasonVersionCompatible),
check.WithMessage("Dependency meets RHOAI %s requirements", tv),
))
return dr,
}
For OLM-based dependency checks, use the Operator builder instead:
func (c *<CheckName>Check) Validate(
ctx context.Context,
target check.Target,
) (*result.DiagnosticResult, error) {
return validate.Operator(c, target).
WithNames("operator-name-1", "operator-name-2").
Run(ctx)
}
Add to pkg/lint/command.go in the NewCommand() function:
registry.MustRegister(<kind>.New<CheckName>Check())
Add the import if the package is new. Follow the existing registration order: Dependencies → Services → Components → Workloads.
Create pkg/lint/checks/<group>/<kind>/<type>_test.go:
package <kind>_test
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/opendatahub-io/odh-cli/pkg/lint/check"
resultpkg "github.com/opendatahub-io/odh-cli/pkg/lint/check/result"
"github.com/opendatahub-io/odh-cli/pkg/lint/check/testutil"
"github.com/opendatahub-io/odh-cli/pkg/lint/checks/<group>/<kind>"
"github.com/opendatahub-io/odh-cli/pkg/resources"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
)
//nolint:gochecknoglobals // Test fixture - shared across test functions
var listKinds = map[schema.GroupVersionResource]string{
// Include every resource the check lists or fetches.
// For component checks:
resources.DataScienceCluster.GVR(): resources.DataScienceCluster.ListKind(),
// For workload checks, also include the workload resource:
// resources.<WorkloadType>.GVR(): resources.<WorkloadType>.ListKind(),
}
func Test<CheckName>Check_PassCase(t *testing.T) {
g := NewWithT(t)
ctx := t.Context()
target := testutil.NewTarget(t, testutil.TargetConfig{
ListKinds: listKinds,
Objects: []*unstructured.Unstructured{testutil.NewDSC(map[string]string{"<kind>": "Managed"})},
CurrentVersion: "2.17.0",
TargetVersion: "3.0.0",
})
chk := <kind>.New<CheckName>Check()
result, err := chk.Validate(ctx, target)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result.Status.Conditions).To(HaveLen())
g.Expect(result.Status.Conditions[].Condition).To(MatchFields(IgnoreExtras, Fields{
: Equal(check.ConditionTypeCompatible),
: Equal(metav1.ConditionTrue),
: Equal(check.ReasonVersionCompatible),
}))
}
{
g := NewWithT(t)
ctx := t.Context()
target := testutil.NewTarget(t, testutil.TargetConfig{
ListKinds: listKinds,
Objects: []*unstructured.Unstructured{},
CurrentVersion: ,
TargetVersion: ,
})
chk := <kind>.New<CheckName>Check()
result, err := chk.Validate(ctx, target)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result.Status.Conditions).To(HaveLen())
g.Expect(result.Status.Conditions[].Condition).To(MatchFields(IgnoreExtras, Fields{
: Equal(check.ConditionTypeCompatible),
: Equal(metav1.ConditionFalse),
: Equal(check.ReasonVersionIncompatible),
}))
g.Expect(result.Status.Conditions[].Impact).To(Equal(resultpkg.ImpactBlocking))
}
{
g := NewWithT(t)
chk := <kind>.New<CheckName>Check()
target := testutil.NewTarget(t, testutil.TargetConfig{ListKinds: listKinds})
canApply, err := chk.CanApply(t.Context(), target)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(canApply).To(BeFalse())
target = testutil.NewTarget(t, testutil.TargetConfig{
ListKinds: listKinds,
CurrentVersion: ,
TargetVersion: ,
})
canApply, err = chk.CanApply(t.Context(), target)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(canApply).To(BeTrue())
}
{
g := NewWithT(t)
chk := <kind>.New<CheckName>Check()
g.Expect(chk.ID()).To(Equal())
g.Expect(chk.Name()).ToNot(BeEmpty())
g.Expect(chk.Group()).To(Equal(check.Group<Group>))
g.Expect(chk.Description()).ToNot(BeEmpty())
}
Test helpers:
testutil.NewTarget(t, cfg) — builds a check.Target from fake clientstestutil.NewDSC(map[string]string{...}) — creates a DSC with component management statestestutil.NewDSCI(namespace) — creates a DSCI with applications namespaceListKinds requirement: Every resource the check lists must be registered in ListKinds (maps GVR to list kind string). Use resources.X.GVR() and resources.X.ListKind().
OLM dependency tests use operatorfake.NewSimpleClientset() via TargetConfig.OLM instead of ListKinds/Objects.
Run:
make fmt
make lint
make test
Status=False auto-derives ImpactAdvisory. Use check.WithImpact(result.ImpactBlocking) explicitly for conditions that block upgrades.Status=False/Unknown without an impact (ImpactNone) causes a panic in NewCondition. The auto-derivation handles this, but WithImpact(result.ImpactNone) on a failing condition will panic.CheckKind() must match DSC spec key — For component checks, Kind must be the lowercase DSC component key (e.g., "kserve" not "KServe"). The validate.Component builder uses this to look up the management state.InState() returns a pass — When the component is not in any of the specified states, the builder returns a passing "not configured" result, not a failure.ctx — The validate.DSCI callback signature is func(dr *result.DiagnosticResult, dsci *unstructured.Unstructured) error (no context parameter).ListKinds in tests — Every resource type the check lists must be registered in ListKinds, otherwise the fake client returns wrong list kinds. Workload checks that also read DSC in CanApply need both the workload GVR and resources.DataScienceCluster in ListKinds.package <kind>_test (external test package), not package <kind>.check.BaseCheck — From pkg/lint/check. Never implement ID/Name/Description/Group manuallyvalidate.Component, validate.Workloads, validate.WorkloadsMetadata, validate.DSCI, or validate.Operator as appropriate for the check groupjq.Query[T]() for field access. Never use unstructured.Nested*() methodscheck.NewCondition — With appropriate WithReason, WithMessage, WithImpact, WithRemediation optionspkg/lint/check/constants.go and pkg/lint/check/condition.gopkg/resources/types.gopkg/lint/command.go, following the canonical group ordermake fmt && make lint && make testdocs/lint/architecture.mddocs/lint/writing-checks.mddocs/testing.mddocs/coding/conventions.md