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