feat: full kubeclaw operator — CRDs, controllers, deploy manifests, Helm chart

- 7 CRDs (Agent, AgentConfig, AgentGroup, Channel, Provider, WorkflowTemplate, CompliancePolicy)
- 3 controllers (Agent, WorkflowTemplate, CompliancePolicy)
- Generated deepcopy methods (controller-gen v0.16.5)
- Dockerfile (multi-stage Go build)
- Nix build-image.nix (dockerTools.streamLayeredImage)
- Helm chart with RBAC, deployment, service account
- Deploy manifests (RBAC + controller deployment)
- Sample manifests for all 7 CRDs
- Quickstart script
- Successfully deployed to k3s on n97-mini:
  - Controller running and reconciling
  - All 7 CRDs registered
  - Agent CR creates Pod (ErrImagePull expected — no agent image yet)
  - Controller logs show reconciliation working
This commit is contained in:
cos Agent 2026-05-12 17:08:31 -07:00
parent 7607086664
commit ece81f651c
47 changed files with 4038 additions and 13 deletions

13
Dockerfile Normal file
View file

@ -0,0 +1,13 @@
FROM golang:1.24-alpine AS builder
WORKDIR /workspace
COPY go.mod go.sum* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o manager ./cmd/manager
FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]

View file

@ -4,8 +4,9 @@ Copyright 2025 Scott Scoble.
Licensed under the MIT License.
*/
// Package v1alpha1 contains API Schema definitions for the scobleclaw v1alpha1 API group.
// +groupName=scobleclaw.ai
// Package v1alpha1 contains API Schema definitions for the kubeclaw v1alpha1 API group.
// +groupName=kubeclaw.scoble.me
// +kubebuilder:object:generate=true
package v1alpha1
import (
@ -15,7 +16,7 @@ import (
var (
GroupVersion = schema.GroupVersion{
Group: "scobleclaw.ai",
Group: "kubeclaw.scoble.me",
Version: "v1alpha1",
}

View file

@ -8,6 +8,7 @@ package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// WorkflowTemplateSpec defines a workflow template that agents can instantiate.
@ -22,14 +23,14 @@ type WorkflowTemplateSpec struct {
Steps []WorkflowStep `json:"steps"`
// ParametersSchema defines the JSON Schema for allowed parameters.
// +kubebuilder:validation:Schemaless
// +optional
// +kubebuilder:pruning:PreserveUnknownFields
ParametersSchema map[string]interface{} `json:"parametersSchema"`
ParametersSchema runtime.RawExtension `json:"parametersSchema,omitempty"`
// OutputSchema defines the expected output shape (JSON Schema).
// +kubebuilder:validation:Schemaless
// +optional
// +kubebuilder:pruning:PreserveUnknownFields
OutputSchema map[string]interface{} `json:"outputSchema"`
OutputSchema runtime.RawExtension `json:"outputSchema,omitempty"`
// ComplianceRequirements lists compliance frameworks this template satisfies.
// +optional

File diff suppressed because it is too large Load diff

BIN
bin/manager Executable file

Binary file not shown.

View file

@ -0,0 +1,18 @@
apiVersion: v2
name: kubeclaw
description: OpenClaw operator for Kubernetes — run Scobleclaw agents as K8s workloads
type: application
version: 0.1.0
appVersion: "0.1.0"
keywords:
- openclaw
- agent
- llm
- ai
- operator
home: https://github.com/sscoble/kubeclaw
sources:
- https://github.com/sscoble/kubeclaw
maintainers:
- name: Scott Scoble
email: scott@scoble.me

View file

@ -0,0 +1,104 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: agentconfigs.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: AgentConfig
listKind: AgentConfigList
plural: agentconfigs
singular: agentconfig
shortNames:
- acfg
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
workspace:
type: object
properties:
repo:
type: string
branch:
type: string
memory:
type: object
properties:
path:
type: string
autoCommit:
type: boolean
heartbeat:
type: object
properties:
intervalSeconds:
type: integer
prompt:
type: string
sandbox:
type: string
enum: [inherit, require, none]
thinking:
type: string
enum: ["off", "low", "medium", "high"]
model:
type: string
fallbacks:
type: array
items:
type: string
toolsAllow:
type: array
items:
type: string
toolsDeny:
type: array
items:
type: string
env:
type: array
items:
type: object
properties:
name:
type: string
value:
type: string
valueFrom:
type: object
properties:
secretKeyRef:
type: object
properties:
name:
type: string
key:
type: string
status:
type: object
properties:
ready:
type: boolean
usedByAgents:
type: array
items:
type: string

View file

@ -0,0 +1,107 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: agentgroups.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: AgentGroup
listKind: AgentGroupList
plural: agentgroups
singular: agentgroup
shortNames:
- agg
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Members
type: integer
jsonPath: .status.memberCount
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [members]
properties:
sharedConfigRef:
type: string
sharedProviders:
type: array
items:
type: string
sharedChannels:
type: array
items:
type: string
members:
type: array
items:
type: object
required: [name, agentId]
properties:
name:
type: string
agentId:
type: string
model:
type: string
thinking:
type: string
configOverrides:
type: object
properties:
model:
type: string
thinking:
type: string
allowedTemplates:
type: array
items:
type: string
resources:
type: object
properties:
requests:
type: object
properties:
cpu:
type: string
memory:
type: string
limits:
type: object
properties:
cpu:
type: string
memory:
type: string
status:
type: object
properties:
ready:
type: boolean
memberCount:
type: integer
memberReady:
type: integer
agentRefs:
type: array
items:
type: string

View file

@ -0,0 +1,147 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: agents.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: Agent
listKind: AgentList
plural: agents
singular: agent
shortNames:
- ag
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: AgentID
type: string
jsonPath: .spec.agentId
- name: Model
type: string
jsonPath: .spec.model
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [agentId]
properties:
agentId:
type: string
description: OpenClaw agent identifier (main, engineering, etc.)
model:
type: string
thinking:
type: string
agentConfigRef:
type: string
description: Reference to an AgentConfig resource
workspace:
type: object
properties:
configMapRef:
type: string
secretRefs:
type: array
items:
type: string
pvcName:
type: string
providers:
type: array
items:
type: string
channelRefs:
type: array
items:
type: string
channels:
type: array
items:
type: object
required: [type, botTokenSecretRef]
properties:
type:
type: string
botTokenSecretRef:
type: object
required: [name, key]
properties:
name:
type: string
key:
type: string
guildID:
type: string
defaultChannel:
type: string
defaultTemplate:
type: string
allowedTemplates:
type: array
items:
type: string
allowedDataSources:
type: array
items:
type: string
allowedDestinations:
type: array
items:
type: string
compliancePolicyRefs:
type: array
items:
type: string
resources:
type: object
properties:
requests:
type: object
properties:
cpu:
type: string
memory:
type: string
limits:
type: object
properties:
cpu:
type: string
memory:
type: string
image:
type: string
replicas:
type: integer
status:
type: object
properties:
ready:
type: boolean
phase:
type: string
deployments:
type: array
items:
type: string
lastTransition:
type: string
format: date-time

View file

@ -0,0 +1,75 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: channels.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: Channel
listKind: ChannelList
plural: channels
singular: channel
shortNames:
- ch
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
jsonPath: .spec.type
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [type, botTokenSecretRef]
properties:
type:
type: string
description: Channel type (discord, slack, telegram)
botTokenSecretRef:
type: object
required: [name, key]
properties:
name:
type: string
key:
type: string
webhookURL:
type: string
guildID:
type: string
defaultChannel:
type: string
allowedChannels:
type: array
items:
type: string
status:
type: object
properties:
ready:
type: boolean
connected:
type: boolean
condition:
type: string
lastConnected:
type: string
format: date-time

View file

@ -0,0 +1,106 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: compliancepolicies.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: CompliancePolicy
listKind: CompliancePolicyList
plural: compliancepolicies
singular: compliancepolicy
shortNames:
- cp
- compol
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Framework
type: string
jsonPath: .spec.framework
- name: Escalation
type: string
jsonPath: .spec.escalation.mode
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [displayName, framework, rules, escalation]
properties:
displayName:
type: string
framework:
type: string
rules:
type: array
items:
type: object
required: [name, description, severity, constraint]
properties:
name:
type: string
description:
type: string
severity:
type: string
enum: [block, warn, log]
constraint:
type: string
escalation:
type: object
required: [mode]
properties:
mode:
type: string
enum: [auto_deny, human_review, warn_only]
humanChannelRef:
type: string
timeoutSeconds:
type: integer
targetSelector:
type: object
properties:
matchTemplates:
type: array
items:
type: string
matchNamespaces:
type: array
items:
type: string
matchLabels:
type: object
additionalProperties:
type: string
prompt:
type: string
status:
type: object
properties:
ready:
type: boolean
activeTemplates:
type: integer
totalJudgments:
type: integer
format: int64
lastJudgment:
type: string
format: date-time

View file

@ -0,0 +1,86 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: providers.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: Provider
listKind: ProviderList
plural: providers
singular: provider
shortNames:
- prov
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
jsonPath: .spec.type
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [type]
properties:
type:
type: string
description: Provider type (ollama, openai, anthropic, deepseek)
baseUrl:
type: string
description: Provider API endpoint URL
apiKeySecretRef:
type: object
properties:
name:
type: string
key:
type: string
models:
type: array
items:
type: object
required: [id]
properties:
id:
type: string
default:
type: boolean
cost:
type: object
properties:
input:
type: number
output:
type: number
extraHeaders:
type: object
additionalProperties:
type: string
status:
type: object
properties:
ready:
type: boolean
condition:
type: string
lastChecked:
type: string
format: date-time

View file

@ -0,0 +1,106 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: workflowtemplates.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: WorkflowTemplate
listKind: WorkflowTemplateList
plural: workflowtemplates
singular: workflowtemplate
shortNames:
- wt
- wft
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Audit
type: string
jsonPath: .spec.auditLevel
- name: Steps
type: integer
jsonPath: .spec.steps
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [displayName, description, steps, parametersSchema, outputSchema, auditLevel]
properties:
displayName:
type: string
description:
type: string
steps:
type: array
items:
type: object
required: [name, action, target]
properties:
name:
type: string
action:
type: string
target:
type: string
condition:
type: string
onFailure:
type: string
enum: [stop, skip, continue]
parametersSchema:
type: object
x-kubernetes-preserve-unknown-fields: true
outputSchema:
type: object
x-kubernetes-preserve-unknown-fields: true
complianceRequirements:
type: array
items:
type: string
requiresApproval:
type: array
items:
type: string
allowedDataSources:
type: array
items:
type: string
allowedDestinations:
type: array
items:
type: string
auditLevel:
type: string
enum: [minimal, standard, full]
status:
type: object
properties:
ready:
type: boolean
validated:
type: boolean
usedByAgents:
type: array
items:
type: string
lastInstantiated:
type: string
format: date-time

View file

@ -0,0 +1,39 @@
{{- define "kubeclaw.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "kubeclaw.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- define "kubeclaw.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "kubeclaw.labels" -}}
helm.sh/chart: {{ include "kubeclaw.chart" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{ include "kubeclaw.selectorLabels" . }}
{{- end }}
{{- define "kubeclaw.selectorLabels" -}}
app.kubernetes.io/name: {{ include "kubeclaw.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/part-of: scobleclaw
{{- end }}
{{- define "kubeclaw.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "kubeclaw.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "kubeclaw.fullname" . }}
labels: {{- include "kubeclaw.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels: {{- include "kubeclaw.selectorLabels" . | nindent 6 }}
template:
metadata:
labels: {{- include "kubeclaw.labels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "kubeclaw.serviceAccountName" . }}
containers:
- name: manager
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --health-probe-bind-address=:8081
- --metrics-bind-address=:8080
ports:
- name: metrics
containerPort: 8080
- name: health
containerPort: 8081
livenessProbe:
httpGet:
path: /healthz
port: health
readinessProbe:
httpGet:
path: /readyz
port: health
resources: {{- toYaml .Values.resources | nindent 12 }}

View file

@ -0,0 +1,40 @@
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "kubeclaw.fullname" . }}
labels: {{- include "kubeclaw.labels" . | nindent 4 }}
rules:
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents", "agentconfigs", "agentgroups", "channels", "providers"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["workflowtemplates", "compliancepolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents/status", "agentconfigs/status", "agentgroups/status", "channels/status", "providers/status", "workflowtemplates/status", "compliancepolicies/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents/finalizers"]
verbs: ["update"]
- apiGroups: [""]
resources: ["pods", "configmaps", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "kubeclaw.fullname" . }}
labels: {{- include "kubeclaw.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "kubeclaw.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "kubeclaw.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "kubeclaw.serviceAccountName" . }}
labels: {{- include "kubeclaw.labels" . | nindent 4 }}

View file

@ -0,0 +1,31 @@
# kubeclaw Helm chart values
replicaCount: 1
image:
repository: ghcr.io/sscoble/kubeclaw
pullPolicy: IfNotPresent
tag: latest
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
name: kubeclaw-controller
rbac:
create: true
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}

101
cmd/manager/main.go Normal file
View file

@ -0,0 +1,101 @@
/*
Copyright 2025 Scott Scoble.
Licensed under the MIT License.
*/
package main
import (
"flag"
"os"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
kubeclawv1alpha1 "github.com/sscoble/kubeclaw/api/v1alpha1"
"github.com/sscoble/kubeclaw/internal/controller"
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(kubeclawv1alpha1.AddToScheme(scheme))
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager.")
opts := zap.Options{Development: true}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: ctrl.Options{}.Metrics,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "kubeclaw.scoble.me",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// Set up Agent reconciler
if err = (&controller.AgentReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Agent")
os.Exit(1)
}
// Set up WorkflowTemplate reconciler
if err = (&controller.WorkflowTemplateReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "WorkflowTemplate")
os.Exit(1)
}
// Set up CompliancePolicy reconciler
if err = (&controller.CompliancePolicyReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CompliancePolicy")
os.Exit(1)
}
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

View file

@ -0,0 +1,110 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: agentconfigs.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: AgentConfig
listKind: AgentConfigList
plural: agentconfigs
singular: agentconfig
shortNames:
- acfg
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
workspace:
type: object
properties:
repo:
type: string
branch:
type: string
memory:
type: object
properties:
path:
type: string
autoCommit:
type: boolean
heartbeat:
type: object
properties:
intervalSeconds:
type: integer
prompt:
type: string
sandbox:
type: object
properties:
enabled:
type: boolean
allowedExec:
type: array
items:
type: string
thinking:
type: string
enum: ["off", "low", "medium", "high"]
model:
type: string
fallbacks:
type: array
items:
type: string
toolsAllow:
type: array
items:
type: string
toolsDeny:
type: array
items:
type: string
env:
type: array
items:
type: object
properties:
name:
type: string
value:
type: string
valueFrom:
type: object
properties:
secretKeyRef:
type: object
properties:
name:
type: string
key:
type: string
status:
type: object
properties:
ready:
type: boolean
usedByAgents:
type: array
items:
type: string

View file

@ -0,0 +1,107 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: agentgroups.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: AgentGroup
listKind: AgentGroupList
plural: agentgroups
singular: agentgroup
shortNames:
- agg
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Members
type: integer
jsonPath: .status.memberCount
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [members]
properties:
sharedConfigRef:
type: string
sharedProviders:
type: array
items:
type: string
sharedChannels:
type: array
items:
type: string
members:
type: array
items:
type: object
required: [name, agentId]
properties:
name:
type: string
agentId:
type: string
model:
type: string
thinking:
type: string
configOverrides:
type: object
properties:
model:
type: string
thinking:
type: string
allowedTemplates:
type: array
items:
type: string
resources:
type: object
properties:
requests:
type: object
properties:
cpu:
type: string
memory:
type: string
limits:
type: object
properties:
cpu:
type: string
memory:
type: string
status:
type: object
properties:
ready:
type: boolean
memberCount:
type: integer
memberReady:
type: integer
agentRefs:
type: array
items:
type: string

View file

@ -0,0 +1,147 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: agents.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: Agent
listKind: AgentList
plural: agents
singular: agent
shortNames:
- ag
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: AgentID
type: string
jsonPath: .spec.agentId
- name: Model
type: string
jsonPath: .spec.model
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [agentId]
properties:
agentId:
type: string
description: OpenClaw agent identifier (main, engineering, etc.)
model:
type: string
thinking:
type: string
agentConfigRef:
type: string
description: Reference to an AgentConfig resource
workspace:
type: object
properties:
configMapRef:
type: string
secretRefs:
type: array
items:
type: string
pvcName:
type: string
providers:
type: array
items:
type: string
channelRefs:
type: array
items:
type: string
channels:
type: array
items:
type: object
required: [type, botTokenSecretRef]
properties:
type:
type: string
botTokenSecretRef:
type: object
required: [name, key]
properties:
name:
type: string
key:
type: string
guildID:
type: string
defaultChannel:
type: string
defaultTemplate:
type: string
allowedTemplates:
type: array
items:
type: string
allowedDataSources:
type: array
items:
type: string
allowedDestinations:
type: array
items:
type: string
compliancePolicyRefs:
type: array
items:
type: string
resources:
type: object
properties:
requests:
type: object
properties:
cpu:
type: string
memory:
type: string
limits:
type: object
properties:
cpu:
type: string
memory:
type: string
image:
type: string
replicas:
type: integer
status:
type: object
properties:
ready:
type: boolean
phase:
type: string
deployments:
type: array
items:
type: string
lastTransition:
type: string
format: date-time

View file

@ -0,0 +1,75 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: channels.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: Channel
listKind: ChannelList
plural: channels
singular: channel
shortNames:
- ch
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
jsonPath: .spec.type
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [type, botTokenSecretRef]
properties:
type:
type: string
description: Channel type (discord, slack, telegram)
botTokenSecretRef:
type: object
required: [name, key]
properties:
name:
type: string
key:
type: string
webhookURL:
type: string
guildID:
type: string
defaultChannel:
type: string
allowedChannels:
type: array
items:
type: string
status:
type: object
properties:
ready:
type: boolean
connected:
type: boolean
condition:
type: string
lastConnected:
type: string
format: date-time

View file

@ -0,0 +1,106 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: compliancepolicies.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: CompliancePolicy
listKind: CompliancePolicyList
plural: compliancepolicies
singular: compliancepolicy
shortNames:
- cp
- compol
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Framework
type: string
jsonPath: .spec.framework
- name: Escalation
type: string
jsonPath: .spec.escalation.mode
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [displayName, framework, rules, escalation]
properties:
displayName:
type: string
framework:
type: string
rules:
type: array
items:
type: object
required: [name, description, severity, constraint]
properties:
name:
type: string
description:
type: string
severity:
type: string
enum: [block, warn, log]
constraint:
type: string
escalation:
type: object
required: [mode]
properties:
mode:
type: string
enum: [auto_deny, human_review, warn_only]
humanChannelRef:
type: string
timeoutSeconds:
type: integer
targetSelector:
type: object
properties:
matchTemplates:
type: array
items:
type: string
matchNamespaces:
type: array
items:
type: string
matchLabels:
type: object
additionalProperties:
type: string
prompt:
type: string
status:
type: object
properties:
ready:
type: boolean
activeTemplates:
type: integer
totalJudgments:
type: integer
format: int64
lastJudgment:
type: string
format: date-time

View file

@ -0,0 +1,86 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: providers.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: Provider
listKind: ProviderList
plural: providers
singular: provider
shortNames:
- prov
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
jsonPath: .spec.type
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [type]
properties:
type:
type: string
description: Provider type (ollama, openai, anthropic, deepseek)
baseUrl:
type: string
description: Provider API endpoint URL
apiKeySecretRef:
type: object
properties:
name:
type: string
key:
type: string
models:
type: array
items:
type: object
required: [id]
properties:
id:
type: string
default:
type: boolean
cost:
type: object
properties:
input:
type: number
output:
type: number
extraHeaders:
type: object
additionalProperties:
type: string
status:
type: object
properties:
ready:
type: boolean
condition:
type: string
lastChecked:
type: string
format: date-time

View file

@ -0,0 +1,106 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: workflowtemplates.kubeclaw.scoble.me
labels:
app.kubernetes.io/name: kubeclaw
app.kubernetes.io/part-of: scobleclaw
spec:
group: kubeclaw.scoble.me
names:
kind: WorkflowTemplate
listKind: WorkflowTemplateList
plural: workflowtemplates
singular: workflowtemplate
shortNames:
- wt
- wft
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Audit
type: string
jsonPath: .spec.auditLevel
- name: Steps
type: integer
jsonPath: .spec.steps
- name: Ready
type: boolean
jsonPath: .status.ready
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [displayName, description, steps, parametersSchema, outputSchema, auditLevel]
properties:
displayName:
type: string
description:
type: string
steps:
type: array
items:
type: object
required: [name, action, target]
properties:
name:
type: string
action:
type: string
target:
type: string
condition:
type: string
onFailure:
type: string
enum: [stop, skip, continue]
parametersSchema:
type: object
x-kubernetes-preserve-unknown-fields: true
outputSchema:
type: object
x-kubernetes-preserve-unknown-fields: true
complianceRequirements:
type: array
items:
type: string
requiresApproval:
type: array
items:
type: string
allowedDataSources:
type: array
items:
type: string
allowedDestinations:
type: array
items:
type: string
auditLevel:
type: string
enum: [minimal, standard, full]
status:
type: object
properties:
ready:
type: boolean
validated:
type: boolean
usedByAgents:
type: array
items:
type: string
lastInstantiated:
type: string
format: date-time

View file

@ -0,0 +1,49 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubeclaw-controller
namespace: scobleclaw
labels:
app: kubeclaw
component: controller
spec:
replicas: 1
selector:
matchLabels:
app: kubeclaw
component: controller
template:
metadata:
labels:
app: kubeclaw
component: controller
spec:
serviceAccountName: kubeclaw-controller
containers:
- name: manager
image: kubeclaw/controller:latest
imagePullPolicy: Never
args:
- --health-probe-bind-address=:8081
- --metrics-bind-address=:8080
ports:
- name: metrics
containerPort: 8080
- name: health
containerPort: 8081
livenessProbe:
httpGet:
path: /healthz
port: health
readinessProbe:
httpGet:
path: /readyz
port: health
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi

46
config/deploy/rbac.yaml Normal file
View file

@ -0,0 +1,46 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubeclaw-controller
namespace: scobleclaw
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubeclaw-controller
rules:
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents", "agentconfigs", "agentgroups", "channels", "providers",
"workflowtemplates", "compliancepolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents/status", "agentconfigs/status", "agentgroups/status",
"channels/status", "providers/status",
"workflowtemplates/status", "compliancepolicies/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents/finalizers"]
verbs: ["update"]
- apiGroups: [""]
resources: ["pods", "configmaps", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubeclaw-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubeclaw-controller
subjects:
- kind: ServiceAccount
name: kubeclaw-controller
namespace: scobleclaw

40
config/rbac/role.yaml Normal file
View file

@ -0,0 +1,40 @@
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "kubeclaw.fullname" . }}
labels: {{- include "kubeclaw.labels" . | nindent 4 }}
rules:
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents", "agentconfigs", "agentgroups", "channels", "providers"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["workflowtemplates", "compliancepolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents/status", "agentconfigs/status", "agentgroups/status", "channels/status", "providers/status", "workflowtemplates/status", "compliancepolicies/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["agents/finalizers"]
verbs: ["update"]
- apiGroups: [""]
resources: ["pods", "configmaps", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "kubeclaw.fullname" . }}
labels: {{- include "kubeclaw.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "kubeclaw.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "kubeclaw.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View file

@ -0,0 +1,13 @@
# Example: LLM provider pointing to a local Ollama instance
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: Provider
metadata:
name: ollama-cloud
spec:
type: ollama
baseUrl: https://ollama.example.com
models:
- id: deepseek-v4-pro:cloud
default: true
- id: deepseek-v4-flash:cloud
- id: qwen3-coder-next:cloud

View file

@ -0,0 +1,11 @@
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: Channel
metadata:
name: discord-main
spec:
type: discord
botTokenSecretRef:
name: discord-token
key: token
guildID: "1169414363476672584"
defaultChannel: "1496038116128198676"

View file

@ -0,0 +1,11 @@
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: AgentConfig
metadata:
name: default-config
spec:
model: deepseek-v4-pro:cloud
thinking: "off"
heartbeat:
intervalSeconds: 1800
sandbox:
enabled: false

View file

@ -0,0 +1,40 @@
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: WorkflowTemplate
metadata:
name: search-memory
spec:
displayName: "Search Memory"
description: "Search the agent's memory store for relevant past context"
steps:
- name: search
action: search_memory
target: memory_store
- name: format-results
action: format_output
target: stdout
parametersSchema:
type: object
properties:
query:
type: string
description: "Search query"
maxResults:
type: integer
default: 5
required: ["query"]
outputSchema:
type: object
properties:
results:
type: array
items:
type: object
properties:
path:
type: string
content:
type: string
score:
type: number
auditLevel: standard
complianceRequirements: ["SOC2"]

View file

@ -0,0 +1,18 @@
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: CompliancePolicy
metadata:
name: soc2-data-access
spec:
displayName: "SOC2 Data Access Policy"
framework: SOC2
rules:
- name: data-source-check
description: "Ensure data sources are in the allowed list"
severity: block
constraint: "data_source in allowed_sources"
- name: audit-trail
description: "Require full audit trail for data writes"
severity: warn
constraint: "audit_level >= standard"
escalation:
mode: auto_deny

View file

@ -0,0 +1,25 @@
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: Agent
metadata:
name: main-agent
labels:
team: core
spec:
agentId: main
model: deepseek-v4-pro:cloud
agentConfigRef: default-config
providers:
- ollama-cloud
channelRefs:
- discord-main
allowedTemplates:
- search-memory
allowedDataSources:
- memory_store
- vault
allowedDestinations:
- stdout
- discord
compliancePolicyRefs:
- soc2-data-access
replicas: 1

View file

@ -0,0 +1,20 @@
apiVersion: kubeclaw.scoble.me/v1alpha1
kind: AgentGroup
metadata:
name: scobleclaw-team
spec:
sharedProviders:
- ollama-cloud
sharedChannels:
- discord-main
members:
- name: cos
agentId: main
model: deepseek-v4-pro:cloud
- name: engineering
agentId: engineering
model: qwen3-coder-next:cloud
- name: trust
agentId: trust
model: deepseek-v4-flash:cloud
thinking: low

65
go.mod
View file

@ -1,14 +1,67 @@
module github.com/sscoble/kubeclaw
go 1.24
go 1.24.0
require (
github.com/onsi/ginkgo/v2 v2.23.3
github.com/onsi/gomega v1.37.0
k8s.io/api v0.33.0
k8s.io/apiextensions-apiserver v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
sigs.k8s.io/controller-runtime v0.20.4
sigs.k8s.io/yaml v1.4.0
)
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.23.3 // indirect
github.com/onsi/gomega v1.37.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.9.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

196
go.sum Normal file
View file

@ -0,0 +1,196 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs=
k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

4
hack/boilerplate.go.txt Normal file
View file

@ -0,0 +1,4 @@
/*
Copyright 2025 Scott Scoble.
Licensed under the MIT License.
*/

View file

@ -0,0 +1,190 @@
/*
Copyright 2025 Scott Scoble. Licensed under the MIT License.
*/
package controller
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
kubeclawv1alpha1 "github.com/sscoble/kubeclaw/api/v1alpha1"
)
// AgentReconciler reconciles Agent objects.
type AgentReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("reconciling Agent", "name", req.Name, "namespace", req.Namespace)
agent := &kubeclawv1alpha1.Agent{}
if err := r.Get(ctx, req.NamespacedName, agent); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Handle deletion
if !agent.DeletionTimestamp.IsZero() {
if controllerutil.ContainsFinalizer(agent, "kubeclaw.scoble.me/finalizer") {
podName := fmt.Sprintf("kubeclaw-agent-%s", agent.Spec.AgentID)
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: req.Namespace}}
if err := r.Delete(ctx, pod); err != nil && !errors.IsNotFound(err) {
return ctrl.Result{}, err
}
controllerutil.RemoveFinalizer(agent, "kubeclaw.scoble.me/finalizer")
if err := r.Update(ctx, agent); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// Ensure finalizer
if !controllerutil.ContainsFinalizer(agent, "kubeclaw.scoble.me/finalizer") {
controllerutil.AddFinalizer(agent, "kubeclaw.scoble.me/finalizer")
if err := r.Update(ctx, agent); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}
// Resolve AgentConfig
if agent.Spec.AgentConfigRef != "" {
acfg := &kubeclawv1alpha1.AgentConfig{}
if err := r.Get(ctx, types.NamespacedName{Name: agent.Spec.AgentConfigRef, Namespace: req.Namespace}, acfg); err != nil {
logger.Error(err, "cannot fetch AgentConfig", "ref", agent.Spec.AgentConfigRef)
agent.Status.Phase = "ConfigError"
_ = r.Status().Update(ctx, agent)
return ctrl.Result{}, err
}
if agent.Spec.Model == "" && acfg.Spec.Model != "" {
agent.Spec.Model = acfg.Spec.Model
}
if agent.Spec.Thinking == "" && acfg.Spec.Thinking != "" {
agent.Spec.Thinking = acfg.Spec.Thinking
}
}
image := agent.Spec.Image
if image == "" {
image = "ghcr.io/sscoble/kubeclaw-agent:latest"
}
podName := fmt.Sprintf("kubeclaw-agent-%s", agent.Spec.AgentID)
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: req.Namespace,
Labels: map[string]string{
"app.kubernetes.io/name": "kubeclaw-agent",
"app.kubernetes.io/instance": podName,
"app.kubernetes.io/part-of": "scobleclaw",
"kubeclaw.scoble.me/agent-id": agent.Spec.AgentID,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "agent",
Image: image,
Env: r.buildEnv(agent),
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("500m"),
corev1.ResourceMemory: resource.MustParse("256Mi"),
},
},
}},
},
}
// Apply resource overrides
if agent.Spec.Resources != nil {
if cpu, ok := agent.Spec.Resources.Requests["cpu"]; ok {
if q, err := resource.ParseQuantity(cpu); err == nil {
pod.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU] = q
}
}
if mem, ok := agent.Spec.Resources.Requests["memory"]; ok {
if q, err := resource.ParseQuantity(mem); err == nil {
pod.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = q
}
}
if cpu, ok := agent.Spec.Resources.Limits["cpu"]; ok {
if q, err := resource.ParseQuantity(cpu); err == nil {
pod.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = q
}
}
if mem, ok := agent.Spec.Resources.Limits["memory"]; ok {
if q, err := resource.ParseQuantity(mem); err == nil {
pod.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = q
}
}
}
// Create or update
foundPod := &corev1.Pod{}
err := r.Get(ctx, types.NamespacedName{Name: podName, Namespace: req.Namespace}, foundPod)
if err != nil && errors.IsNotFound(err) {
logger.Info("creating Pod", "name", podName)
if err := r.Create(ctx, pod); err != nil {
return ctrl.Result{}, err
}
} else if err == nil {
foundPod.Spec = pod.Spec
foundPod.Labels = pod.Labels
if err := r.Update(ctx, foundPod); err != nil {
return ctrl.Result{}, err
}
}
agent.Status.Phase = "Running"
agent.Status.ReadyReplicas = 1
if err := r.Status().Update(ctx, agent); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *AgentReconciler) buildEnv(agent *kubeclawv1alpha1.Agent) []corev1.EnvVar {
env := []corev1.EnvVar{{Name: "KUBECLAW_AGENT_ID", Value: agent.Spec.AgentID}}
if agent.Spec.Model != "" {
env = append(env, corev1.EnvVar{Name: "KUBECLAW_MODEL", Value: agent.Spec.Model})
}
if agent.Spec.Thinking != "" {
env = append(env, corev1.EnvVar{Name: "KUBECLAW_THINKING", Value: agent.Spec.Thinking})
}
if agent.Spec.DefaultTemplate != "" {
env = append(env, corev1.EnvVar{Name: "KUBECLAW_DEFAULT_TEMPLATE", Value: agent.Spec.DefaultTemplate})
}
if len(agent.Spec.AllowedTemplates) > 0 {
env = append(env, corev1.EnvVar{Name: "KUBECLAW_ALLOWED_TEMPLATES", Value: joinSlice(agent.Spec.AllowedTemplates, ",")})
}
return env
}
func (r *AgentReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kubeclawv1alpha1.Agent{}).
Owns(&corev1.Pod{}).
Complete(r)
}

View file

@ -0,0 +1,52 @@
/*
Copyright 2025 Scott Scoble. Licensed under the MIT License.
*/
package controller
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
kubeclawv1alpha1 "github.com/sscoble/kubeclaw/api/v1alpha1"
)
// CompliancePolicyReconciler reconciles CompliancePolicy objects.
type CompliancePolicyReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *CompliancePolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
cp := &kubeclawv1alpha1.CompliancePolicy{}
if err := r.Get(ctx, req.NamespacedName, cp); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
logger.Info("validating compliance policy", "name", cp.Name)
valid := true
if len(cp.Spec.Rules) == 0 {
valid = false
}
cp.Status.Ready = valid
if err := r.Status().Update(ctx, cp); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *CompliancePolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kubeclawv1alpha1.CompliancePolicy{}).
Complete(r)
}

View file

@ -0,0 +1,12 @@
package controller
func joinSlice(items []string, sep string) string {
result := ""
for i, item := range items {
if i > 0 {
result += sep
}
result += item
}
return result
}

View file

@ -0,0 +1,57 @@
/*
Copyright 2025 Scott Scoble. Licensed under the MIT License.
*/
package controller
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
kubeclawv1alpha1 "github.com/sscoble/kubeclaw/api/v1alpha1"
)
// WorkflowTemplateReconciler reconciles WorkflowTemplate objects.
type WorkflowTemplateReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *WorkflowTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
wt := &kubeclawv1alpha1.WorkflowTemplate{}
if err := r.Get(ctx, req.NamespacedName, wt); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
logger.Info("validating workflow template", "name", wt.Name)
// Validate basic shape
valid := true
if len(wt.Spec.Steps) == 0 {
valid = false
}
if wt.Spec.AuditLevel == "" {
wt.Spec.AuditLevel = "standard"
}
wt.Status.Ready = valid
wt.Status.Validated = true
if err := r.Status().Update(ctx, wt); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *WorkflowTemplateReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kubeclawv1alpha1.WorkflowTemplate{}).
Complete(r)
}

BIN
manager Executable file

Binary file not shown.

35
nix/build-image.nix Normal file
View file

@ -0,0 +1,35 @@
# build-image.nix — Package the pre-built kubeclaw controller binary as an OCI image
# Usage: nix-build build-image.nix && result | k3s ctr images import -
{ pkgs ? import <nixpkgs> {} }:
let
manager-bin = pkgs.stdenv.mkDerivation {
name = "kubeclaw-manager";
src = ./../manager;
phases = [ "installPhase" ];
installPhase = ''
mkdir -p $out/bin
cp $src $out/bin/manager
chmod +x $out/bin/manager
'';
};
in pkgs.dockerTools.streamLayeredImage {
name = "kubeclaw/controller";
tag = "latest";
created = "now";
contents = [
manager-bin
pkgs.cacert
pkgs.tzdata
];
config = {
User = "65532:65532";
Entrypoint = [ "/bin/manager" ];
ExposedPorts = {
"8080/tcp" = {};
"8081/tcp" = {};
};
};
}

1
nix/result Symbolic link
View file

@ -0,0 +1 @@
/nix/store/6xkkvlii8k7jj9i2zj0bkw5xj6dbmz81-stream-controller

170
quickstart.sh Executable file
View file

@ -0,0 +1,170 @@
#!/usr/bin/env bash
set -euo pipefail
# kubeclaw quickstart: install CRDs + deploy controller on Docker Desktop Kubernetes
# Prerequisites: Docker Desktop with Kubernetes enabled, kubectl pointing to docker-desktop
echo "=== kubeclaw Quickstart ==="
echo ""
# 1. Verify cluster access
echo "→ Checking cluster connection..."
kubectl cluster-info --request-timeout=5s > /dev/null 2>&1 || {
echo "ERROR: Cannot reach Kubernetes cluster."
echo "Make sure Docker Desktop has Kubernetes enabled (Settings → Kubernetes → Enable Kubernetes)"
exit 1
}
echo " ✓ Cluster reachable ($(kubectl config current-context))"
# 2. Install CRDs
echo ""
echo "→ Installing CRDs..."
for crd in config/crd/bases/*.yaml; do
kubectl apply -f "$crd"
done
echo " ✓ CRDs installed"
# Wait for CRDs to register
sleep 2
# 3. Verify CRDs are ready
echo ""
echo "→ Verifying CRDs..."
kubectl get crd | grep kubeclaw.scoble.me | awk '{print " " $1}'
# 4. Create namespace
echo ""
echo "→ Creating scobleclaw namespace..."
kubectl create namespace scobleclaw --dry-run=client -o yaml | kubectl apply -f -
echo " ✓ Namespace ready"
# 5. Deploy controller
echo ""
echo "→ Deploying kubeclaw controller..."
kubectl apply -f config/rbac/role.yaml 2>/dev/null || {
# Generate minimal RBAC if role.yaml doesn't exist
kubectl apply -f - <<'RBAC'
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubeclaw-controller
namespace: scobleclaw
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubeclaw-controller
rules:
- apiGroups: ["kubeclaw.scoble.me"]
resources: ["*"]
verbs: ["*"]
- apiGroups: [""]
resources: ["pods", "configmaps", "serviceaccounts"]
verbs: ["*"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubeclaw-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubeclaw-controller
subjects:
- kind: ServiceAccount
name: kubeclaw-controller
namespace: scobleclaw
RBAC
}
kubectl apply -f - <<'DEPLOYMENT'
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeclaw-config
namespace: scobleclaw
data:
config.yaml: |
logLevel: info
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubeclaw-controller
namespace: scobleclaw
labels:
app: kubeclaw
spec:
replicas: 1
selector:
matchLabels:
app: kubeclaw
template:
metadata:
labels:
app: kubeclaw
spec:
serviceAccountName: kubeclaw-controller
containers:
- name: manager
image: ghcr.io/sscoble/kubeclaw:latest
imagePullPolicy: IfNotPresent
command: ["/manager"]
args:
- --health-probe-bind-address=:8081
- --metrics-bind-address=:8080
ports:
- containerPort: 8080
name: metrics
- containerPort: 8081
name: health
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /healthz
port: health
readinessProbe:
httpGet:
path: /readyz
port: health
DEPLOYMENT
echo " ✓ Controller deployed"
# 6. Wait for controller
echo ""
echo "→ Waiting for controller to start..."
kubectl wait --for=condition=available deployment/kubeclaw-controller -n scobleclaw --timeout=60s 2>/dev/null || {
echo " (controller may still be starting — check with 'kubectl get pods -n scobleclaw')"
}
# 7. Apply samples
echo ""
echo "→ Installing sample resources..."
for sample in config/samples/0*.yaml; do
echo " Applying $sample..."
kubectl apply -f "$sample" -n scobleclaw 2>/dev/null || kubectl apply -f "$sample"
done
echo ""
echo "=== Quickstart Complete ==="
echo ""
echo "Check status:"
echo " kubectl get agents -n scobleclaw"
echo " kubectl get workflowtemplates"
echo " kubectl get compliancepolicies"
echo ""
echo "View controller logs:"
echo " kubectl logs -n scobleclaw deployment/kubeclaw-controller"