用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/konflux-ci/release-service --skill create-operation命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | create-operation |
| description | Conventions for adding a new operation to a controller's reconciliation pipeline. |
An operation is a method on the adapter struct that represents one step in the controller's reconciliation pipeline. Operations are executed sequentially by controller.ReconcileHandler() from operator-toolkit. Each operation must be idempotent — safe to re-run at any point if the reconciliation is interrupted and restarted.
Every operation must have this exact signature:
func (a *adapter) EnsureSomethingHappens() (controller.OperationResult, error)
The name must start with Ensure and describe the desired end state, not the action taken. Examples: EnsureFinalizerIsAdded, EnsureReleaseIsCompleted, EnsureManagedPipelineIsProcessed.
Use the result helpers from github.com/konflux-ci/operator-toolkit/controller:
| Helper | When to use |
|---|---|
controller.ContinueProcessing() | Work done or skipped — proceed to next operation |
controller.StopProcessing() | Reconciliation should end (e.g. resource already finished) |
controller.Requeue() | Requeue immediately, stop remaining operations |
controller.RequeueWithError(err) | Requeue due to error |
controller.RequeueOnErrorOrContinue(err) | If err is non-nil, requeue; otherwise continue. Most common for status patches |
controller.RequeueOnErrorOrStop(err) | If err is non-nil, requeue; otherwise stop |
controller.RequeueAfter(delay, err) | Requeue after a specific delay |
Every operation follows this skeleton:
// EnsureXxxIsYyy is an operation that will ensure that <describe the desired state>.
func (a *adapter) EnsureXxxIsYyy() (controller.OperationResult, error) {
// 1. Gate condition — skip if already done or prerequisites not met
if a.release.HasXxxFinished() || !a.release.HasPrerequisiteFinished() {
return controller.ContinueProcessing()
}
// 2. Failure skip — if the release has already failed, mark this phase as skipped
if a.release.IsFailed() {
patch := client.MergeFrom(a.release.DeepCopy())
a.release.MarkXxxSkipped()
return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
}
// 3. Load resources via the loader
resource, err := a.loader.GetSomeResource(a.ctx, a.client, a.release)
if err != nil {
return controller.RequeueWithError(err)
}
// 4. Perform the work (create objects, update state, etc.)
// 5. Patch status and return
patch := client.MergeFrom(a.release.DeepCopy())
a.release.MarkXxxDone()
return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
}
Every operation must start with a gate condition that checks whether the work has already been done. This makes reconciliation safe to restart at any point.
Common gate patterns:
// Already done — skip
if a.release.HasXxxFinished() {
return controller.ContinueProcessing()
}
// Prerequisite not met — skip (will run on next reconcile when prerequisite completes)
if !a.release.HasPrerequisiteFinished() {
return controller.ContinueProcessing()
}
// Combined: already done OR prerequisite not met
if a.release.HasXxxFinished() || !a.release.HasPrerequisiteFinished() {
return controller.ContinueProcessing()
}
// Tracking operation — only runs while actively processing
if !a.release.IsXxxProcessing() || a.release.HasXxxFinished() {
return controller.ContinueProcessing()
}
If the release has already failed (e.g. a previous operation marked it as failed), downstream operations must mark their own phase as skipped and continue. They must NOT stop processing — stopping would leave downstream pipeline conditions permanently unset because status-only patches don't trigger GenerationChangedPredicate:
if a.release.IsFailed() {
patch := client.MergeFrom(a.release.DeepCopy())
a.release.MarkXxxSkipped()
return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
}
Always use the merge-from-deep-copy pattern:
patch := client.MergeFrom(a.release.DeepCopy())
// ... mutate a.release.Status fields ...
return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
Rules:
a.client.Status().Patch() for status subresource updates.a.client.Patch() for metadata updates (finalizers, labels, annotations).DeepCopy() before any mutations — this is the baseline for the merge patch.When an operation creates Kubernetes resources (PipelineRuns, RoleBindings), use handlePipelineCreationError from controllers/release/utils.go to classify errors:
pipelineRun, err := a.createSomePipelineRun(...)
if err != nil {
return pipelineCreationResult(handlePipelineCreationError(a.ctx, a.client, a.release, err,
a.release.MarkXxxProcessing,
a.release.MarkXxxFailed,
"Release processing failed on xxx pipelineRun creation"))
}
If resources were created before the failure (e.g. RoleBindings created before PipelineRun creation fails), pass extraStatusUpdates callbacks to persist their references in the same atomic patch. This allows cleanup operations to find and delete them on the next reconcile:
return pipelineCreationResult(handlePipelineCreationError(a.ctx, a.client, a.release, err,
a.release.MarkXxxProcessing,
a.release.MarkXxxFailed,
"Release processing failed on xxx pipelineRun creation",
func() {
if capturedRoleBinding != nil {
a.release.Status.XxxProcessing.RoleBindings.TenantRoleBinding =
fmt.Sprintf("%s%c%s", capturedRoleBinding.Namespace, types.Separator, capturedRoleBinding.Name)
}
}))
Always load resources through a.loader (the loader.ObjectLoader interface), never directly via a.client.Get(). The loader provides:
After implementing the operation, add it to the operation slice in the controller's Reconcile method (controllers/<resource>/controller.go):
return controller.ReconcileHandler([]controller.Operation{
// ... existing operations ...
adapter.EnsureXxxIsYyy, // Add in the correct position
// ... remaining operations ...
})
Order matters. Operations execute sequentially and any one can short-circuit the pipeline. Place the new operation:
EnsureReleaseIsCompletedTests use Ginkgo/Gomega with envtest. Each test file lives alongside the code it tests in the same package.
For each operation, test:
ContinueProcessing when the work is already done or prerequisites aren't met.IsFailed().After implementing the operation and its tests:
go vet ./controllers/<resource>/
go build ./controllers/<resource>/
go test ./controllers/<resource>/
Use go vet and go build for fast iteration on a single package, then run go test for the full test suite of that package.
Ensure prefix and correct signatureMarkXxxSkipped if applicablea.loader, not a.client.Get()client.MergeFrom(a.release.DeepCopy())handlePipelineCreationError if applicableReconcile method in the correct positiongo vet, go build, and go test pass for the package基于 SOC 职业分类