feat(api): v1alpha2 CRD types with typed provider slices (Phase 1)#264
feat(api): v1alpha2 CRD types with typed provider slices (Phase 1)#264rhuss wants to merge 9 commits intollamastack:mainfrom
Conversation
This specification defines the v1alpha2 API for the LlamaStackDistribution CRD, enabling operator-generated server configuration. Key features: - New spec fields: providers, resources, storage, networking, workload - Config generation from high-level abstracted spec - Base config extraction from OCI image labels - Polymorphic field support (single object or list) - Backward compatibility via conversion webhook - Integration with spec 001 external providers Includes implementation plan (5 phases) and detailed task breakdown (33 tasks with dependencies). Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
Adds a condensed 2-page summary of the v1alpha2 spec for easier team review. Includes before/after example, key design decisions, and specific questions for reviewers. Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
Apply fixes from consistency analysis across spec, plan, and tasks:
- Fix expose:{} handling in plan (ShouldExposeRoute returned false)
- Align autoscaling field name to targetCPUUtilizationPercentage
- Fix hub/spoke conversion pattern (Hub() marker, not ConvertTo)
- Restructure plan section 2.2 for phased base config approach
- Add v1alpha2 printer columns (Phase, Providers, Available, Age)
- Add contracts, data-model, quickstart, and research artifacts
- Update review_summary with changelog for reviewers
Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
…findings Major changes to the v1alpha2 operator-generated config specification: - Replace polymorphic JSON types with typed []ProviderConfig slices, enabling kubebuilder validation and CEL rules (FR-004) - Add explicit secretRefs field on ProviderConfig, eliminating heuristic secret detection that caused false positives (FR-005) - Fix CEL validation feasibility: FR-071/FR-072 now work because providers are typed slices, not opaque JSON - Add missing CEL rules for TLS, Redis, and Postgres conditional field requirements (FR-079, FR-079a-c) - Add webhook validation for distribution name (FR-079d) - Change disabled+provider conflict from warning to error (OQ-002) - Clarify merge semantics with concrete before/after examples in config-generation contract - Add ConfigMap cleanup requirement (FR-025a, retain last 2) - Add ConfigResolver interface for Phase 2 extensibility (FR-027a1) - Add Kubernetes Events requirement (NFR-007) - Update all YAML examples across spec, quickstart, and contracts - Add 30-minute review recipe in review_summary.md Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
The []ProviderConfig type annotations were interpreted as YAML flow sequences by the pre-commit YAML validator. Quoting them preserves readability while keeping the file valid YAML. Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
The review summary mentioned providers polymorphism removal but didn't explicitly list the models and expose changes as separate line items. Added both to the "Key Changes from PR llamastack#253" table and expanded the "Decision 1" section to cover all three polymorphic field replacements. Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
The old name implied "a summary of a review that happened" when the file is actually "the document that guides reviewers through their review." REVIEW.md follows the convention of README.md, CHANGELOG.md, and CONTRIBUTING.md, making it immediately recognizable as the entry point for reviewers. Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
Add the complete v1alpha2 API types for LlamaStackDistribution: - ProvidersSpec with typed []ProviderConfig slices (no polymorphic JSON) - ProviderConfig with explicit secretRefs field (no heuristic detection) - ResourcesSpec with typed []ModelConfig (name required, provider optional) - StateStorageSpec with KV (sqlite/redis) and SQL (sqlite/postgres) backends - NetworkingSpec with typed ExposeConfig (enabled + hostname fields) - WorkloadSpec consolidating all deployment settings - Status types: ResolvedDistributionStatus, ConfigGenerationStatus - 4 condition types with reason constants and event types CEL validation rules (15 total): - 4 mutual exclusivity rules (providers/resources/storage/disabled vs overrideConfig) - 5 provider ID required rules (per API type, when list > 1) - 2 distribution name/image rules - TLS secretName required when enabled - Redis endpoint required, Postgres connectionString required Addresses FR-001 through FR-014, FR-070-072, FR-079, FR-079a-c from spec. Assisted-by: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
eoinfennessy
left a comment
There was a problem hiding this comment.
Generally looks good. I added lots and lots of suggestions to strengthen our validations.
Also one question about why we use a pointer to a boolean for ExposeConfig.Enabled
| // Inference providers (e.g., vllm, ollama). | ||
| // +optional | ||
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | ||
| Inference []ProviderConfig `json:"inference,omitempty"` | ||
|
|
||
| // Safety providers (e.g., llama-guard). | ||
| // +optional | ||
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | ||
| Safety []ProviderConfig `json:"safety,omitempty"` | ||
|
|
||
| // VectorIo providers (e.g., pgvector, chromadb). | ||
| // +optional | ||
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | ||
| VectorIo []ProviderConfig `json:"vectorIo,omitempty"` | ||
|
|
||
| // ToolRuntime providers (e.g., brave-search, rag-runtime). | ||
| // +optional | ||
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | ||
| ToolRuntime []ProviderConfig `json:"toolRuntime,omitempty"` | ||
|
|
||
| // Telemetry providers (e.g., opentelemetry). | ||
| // +optional | ||
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | ||
| Telemetry []ProviderConfig `json:"telemetry,omitempty"` |
There was a problem hiding this comment.
We can also add validation here to ensure that when a list is provided it must have one or more items. This ensures empty lists cannot be provided explicitly:
| // Inference providers (e.g., vllm, ollama). | |
| // +optional | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| Inference []ProviderConfig `json:"inference,omitempty"` | |
| // Safety providers (e.g., llama-guard). | |
| // +optional | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| Safety []ProviderConfig `json:"safety,omitempty"` | |
| // VectorIo providers (e.g., pgvector, chromadb). | |
| // +optional | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| VectorIo []ProviderConfig `json:"vectorIo,omitempty"` | |
| // ToolRuntime providers (e.g., brave-search, rag-runtime). | |
| // +optional | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| ToolRuntime []ProviderConfig `json:"toolRuntime,omitempty"` | |
| // Telemetry providers (e.g., opentelemetry). | |
| // +optional | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| Telemetry []ProviderConfig `json:"telemetry,omitempty"` | |
| // Inference providers (e.g., vllm, ollama). | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| Inference []ProviderConfig `json:"inference,omitempty"` | |
| // Safety providers (e.g., llama-guard). | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| Safety []ProviderConfig `json:"safety,omitempty"` | |
| // VectorIo providers (e.g., pgvector, chromadb). | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| VectorIo []ProviderConfig `json:"vectorIo,omitempty"` | |
| // ToolRuntime providers (e.g., brave-search, rag-runtime). | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| ToolRuntime []ProviderConfig `json:"toolRuntime,omitempty"` | |
| // Telemetry providers (e.g., opentelemetry). | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:XValidation:rule="self.size() <= 1 || self.all(p, has(p.id))",message="each provider must have an explicit id when multiple providers are specified" | |
| Telemetry []ProviderConfig `json:"telemetry,omitempty"` |
| // ID is a unique provider identifier. Required when multiple providers are | ||
| // specified for the same API type. Auto-generated from provider field for | ||
| // single-element lists. | ||
| // +optional | ||
| ID string `json:"id,omitempty"` |
There was a problem hiding this comment.
For optional strings, we can add CEL validation to ensure they are not empty if provided:
// +kubebuilder:validation:XValidation:rule="!has(self.id) || self.id != ''",message="id must not be empty if specified"
WDYT?
| // SecretRefs are named secret references for provider-specific connection fields. | ||
| // Each key becomes the env var field suffix: LLSD_<PROVIDER_ID>_<KEY>. | ||
| // +optional | ||
| SecretRefs map[string]SecretKeyRef `json:"secretRefs,omitempty"` |
There was a problem hiding this comment.
Similar validation here to gaurd against empty maps when provided:
| // SecretRefs are named secret references for provider-specific connection fields. | |
| // Each key becomes the env var field suffix: LLSD_<PROVIDER_ID>_<KEY>. | |
| // +optional | |
| SecretRefs map[string]SecretKeyRef `json:"secretRefs,omitempty"` | |
| // SecretRefs are named secret references for provider-specific connection fields. | |
| // Each key becomes the env var field suffix: LLSD_<PROVIDER_ID>_<KEY>. | |
| // +optional | |
| // +kubebuilder:validation:MinProperties=1 | |
| SecretRefs map[string]SecretKeyRef `json:"secretRefs,omitempty"` |
| // Models to register with inference providers. | ||
| // +optional | ||
| Models []ModelConfig `json:"models,omitempty"` | ||
|
|
||
| // Tools are tool group names to register with the toolRuntime provider. | ||
| // +optional | ||
| Tools []string `json:"tools,omitempty"` | ||
|
|
||
| // Shields are safety shield names to register with the safety provider. | ||
| // +optional | ||
| Shields []string `json:"shields,omitempty"` |
There was a problem hiding this comment.
Similarly to above, MinItems gaurds against explicitly empty lists. Using MinLength also gaurds against empty strings in the Tools and Shields slices.
| // Models to register with inference providers. | |
| // +optional | |
| Models []ModelConfig `json:"models,omitempty"` | |
| // Tools are tool group names to register with the toolRuntime provider. | |
| // +optional | |
| Tools []string `json:"tools,omitempty"` | |
| // Shields are safety shield names to register with the safety provider. | |
| // +optional | |
| Shields []string `json:"shields,omitempty"` | |
| // Models to register with inference providers. | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| Models []ModelConfig `json:"models,omitempty"` | |
| // Tools are tool group names to register with the toolRuntime provider. | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:items:MinLength=1 | |
| Tools []string `json:"tools,omitempty"` | |
| // Shields are safety shield names to register with the safety provider. | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:items:MinLength=1 | |
| Shields []string `json:"shields,omitempty"` |
| // ModelConfig defines a model to register. | ||
| type ModelConfig struct { | ||
| // Name is the model identifier (e.g., llama3.2-8b). | ||
| // +kubebuilder:validation:Required | ||
| // +kubebuilder:validation:MinLength=1 | ||
| Name string `json:"name"` | ||
|
|
||
| // Provider is the provider ID to register this model with. | ||
| // Defaults to the first inference provider when omitted. | ||
| // +optional | ||
| Provider string `json:"provider,omitempty"` | ||
|
|
||
| // ContextLength is the model context window size. | ||
| // +optional | ||
| ContextLength *int `json:"contextLength,omitempty"` | ||
|
|
||
| // ModelType is the model type classification. | ||
| // +optional | ||
| ModelType string `json:"modelType,omitempty"` | ||
|
|
||
| // Quantization is the quantization method used. | ||
| // +optional | ||
| Quantization string `json:"quantization,omitempty"` | ||
| } |
There was a problem hiding this comment.
Like above, ensure optional strings are not empty if provided.
| // ModelConfig defines a model to register. | |
| type ModelConfig struct { | |
| // Name is the model identifier (e.g., llama3.2-8b). | |
| // +kubebuilder:validation:Required | |
| // +kubebuilder:validation:MinLength=1 | |
| Name string `json:"name"` | |
| // Provider is the provider ID to register this model with. | |
| // Defaults to the first inference provider when omitted. | |
| // +optional | |
| Provider string `json:"provider,omitempty"` | |
| // ContextLength is the model context window size. | |
| // +optional | |
| ContextLength *int `json:"contextLength,omitempty"` | |
| // ModelType is the model type classification. | |
| // +optional | |
| ModelType string `json:"modelType,omitempty"` | |
| // Quantization is the quantization method used. | |
| // +optional | |
| Quantization string `json:"quantization,omitempty"` | |
| } | |
| // ModelConfig defines a model to register. | |
| // +kubebuilder:validation:XValidation:rule="!has(self.provider) || self.provider != ''",message="provider must not be empty if specified" | |
| // +kubebuilder:validation:XValidation:rule="!has(self.modelType) || self.modelType != ''",message="modelType must not be empty if specified" | |
| // +kubebuilder:validation:XValidation:rule="!has(self.quantization) || self.quantization != ''",message="quantization must not be empty if specified" | |
| type ModelConfig struct { | |
| // Name is the model identifier (e.g., llama3.2-8b). | |
| // +kubebuilder:validation:Required | |
| // +kubebuilder:validation:MinLength=1 | |
| Name string `json:"name"` | |
| // Provider is the provider ID to register this model with. | |
| // Defaults to the first inference provider when omitted. | |
| // +optional | |
| Provider string `json:"provider,omitempty"` | |
| // ContextLength is the model context window size. | |
| // +optional | |
| ContextLength *int `json:"contextLength,omitempty"` | |
| // ModelType is the model type classification. | |
| // +optional | |
| ModelType string `json:"modelType,omitempty"` | |
| // Quantization is the quantization method used. | |
| // +optional | |
| Quantization string `json:"quantization,omitempty"` | |
| } |
| // PVCStorageSpec configures persistent volume storage. | ||
| type PVCStorageSpec struct { | ||
| // Size is the PVC size (e.g., "10Gi"). | ||
| // +optional | ||
| Size *resource.Quantity `json:"size,omitempty"` | ||
|
|
||
| // MountPath is where storage is mounted in the container. | ||
| // +optional | ||
| // +kubebuilder:default:="/.llama" | ||
| MountPath string `json:"mountPath,omitempty"` | ||
| } | ||
|
|
There was a problem hiding this comment.
Ensure MountPath is not empty when provided
| // PVCStorageSpec configures persistent volume storage. | |
| type PVCStorageSpec struct { | |
| // Size is the PVC size (e.g., "10Gi"). | |
| // +optional | |
| Size *resource.Quantity `json:"size,omitempty"` | |
| // MountPath is where storage is mounted in the container. | |
| // +optional | |
| // +kubebuilder:default:="/.llama" | |
| MountPath string `json:"mountPath,omitempty"` | |
| } | |
| // PVCStorageSpec configures persistent volume storage. | |
| // +kubebuilder:validation:XValidation:rule="!has(self.mountPath) || self.mountPath != ''",message="mountPath must not be empty if specified" | |
| type PVCStorageSpec struct { | |
| // Size is the PVC size (e.g., "10Gi"). | |
| // +optional | |
| Size *resource.Quantity `json:"size,omitempty"` | |
| // MountPath is where storage is mounted in the container. | |
| // +optional | |
| // +kubebuilder:default:="/.llama" | |
| MountPath string `json:"mountPath,omitempty"` | |
| } | |
| // PodDisruptionBudgetSpec defines voluntary disruption controls. | ||
| type PodDisruptionBudgetSpec struct { | ||
| // MinAvailable is the minimum number of pods that must remain available. | ||
| // +optional | ||
| MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"` | ||
|
|
||
| // MaxUnavailable is the maximum number of pods that can be disrupted. | ||
| // +optional | ||
| MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"` | ||
| } |
There was a problem hiding this comment.
Ensure one and only one field is provided. (mutually exclusive, similar to actual k8s PDBs)
| // PodDisruptionBudgetSpec defines voluntary disruption controls. | |
| type PodDisruptionBudgetSpec struct { | |
| // MinAvailable is the minimum number of pods that must remain available. | |
| // +optional | |
| MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"` | |
| // MaxUnavailable is the maximum number of pods that can be disrupted. | |
| // +optional | |
| MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"` | |
| } | |
| // PodDisruptionBudgetSpec defines voluntary disruption controls. | |
| // +kubebuilder:validation:XValidation:rule="has(self.minAvailable) || has(self.maxUnavailable)",message="at least one of minAvailable or maxUnavailable must be specified" | |
| // +kubebuilder:validation:XValidation:rule="!(has(self.minAvailable) && has(self.maxUnavailable))",message="minAvailable and maxUnavailable are mutually exclusive" | |
| type PodDisruptionBudgetSpec struct { | |
| // MinAvailable is the minimum number of pods that must remain available. | |
| // +optional | |
| MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"` | |
| // MaxUnavailable is the maximum number of pods that can be disrupted. | |
| // +optional | |
| MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"` | |
| } |
| // WorkloadOverrides provides low-level Pod customization. | ||
| type WorkloadOverrides struct { | ||
| // ServiceAccountName overrides the ServiceAccount. | ||
| // +optional | ||
| ServiceAccountName string `json:"serviceAccountName,omitempty"` | ||
|
|
||
| // Env adds environment variables to the container. | ||
| // +optional | ||
| Env []corev1.EnvVar `json:"env,omitempty"` | ||
|
|
||
| // Command overrides the container entrypoint. | ||
| // +optional | ||
| Command []string `json:"command,omitempty"` | ||
|
|
||
| // Args overrides the container arguments. | ||
| // +optional | ||
| Args []string `json:"args,omitempty"` | ||
|
|
||
| // Volumes adds volumes to the Pod. | ||
| // +optional | ||
| Volumes []corev1.Volume `json:"volumes,omitempty"` | ||
|
|
||
| // VolumeMounts adds volume mounts to the container. | ||
| // +optional | ||
| VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` | ||
| } |
There was a problem hiding this comment.
Ensure all fields are non-empty if provided. Ensure no empty strings in lists.
| // WorkloadOverrides provides low-level Pod customization. | |
| type WorkloadOverrides struct { | |
| // ServiceAccountName overrides the ServiceAccount. | |
| // +optional | |
| ServiceAccountName string `json:"serviceAccountName,omitempty"` | |
| // Env adds environment variables to the container. | |
| // +optional | |
| Env []corev1.EnvVar `json:"env,omitempty"` | |
| // Command overrides the container entrypoint. | |
| // +optional | |
| Command []string `json:"command,omitempty"` | |
| // Args overrides the container arguments. | |
| // +optional | |
| Args []string `json:"args,omitempty"` | |
| // Volumes adds volumes to the Pod. | |
| // +optional | |
| Volumes []corev1.Volume `json:"volumes,omitempty"` | |
| // VolumeMounts adds volume mounts to the container. | |
| // +optional | |
| VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` | |
| } | |
| // WorkloadOverrides provides low-level Pod customization. | |
| // +kubebuilder:validation:XValidation:rule="!has(self.serviceAccountName) || self.serviceAccountName != ''",message="serviceAccountName must not be empty if specified" | |
| type WorkloadOverrides struct { | |
| // +optional | |
| ServiceAccountName string `json:"serviceAccountName,omitempty"` | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| Env []corev1.EnvVar `json:"env,omitempty"` | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:items:MinLength=1 | |
| Command []string `json:"command,omitempty"` | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| // +kubebuilder:validation:items:MinLength=1 | |
| Args []string `json:"args,omitempty"` | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| Volumes []corev1.Volume `json:"volumes,omitempty"` | |
| // +optional | |
| // +kubebuilder:validation:MinItems=1 | |
| VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` | |
| } |
| // ExternalProvidersSpec configures external provider injection. | ||
| type ExternalProvidersSpec struct { | ||
| // Inference external providers. | ||
| // +optional | ||
| Inference []ExternalProviderConfig `json:"inference,omitempty"` | ||
| } |
There was a problem hiding this comment.
Ensure Inference slice is not empty if provided.
| // ExternalProvidersSpec configures external provider injection. | |
| type ExternalProvidersSpec struct { | |
| // Inference external providers. | |
| // +optional | |
| Inference []ExternalProviderConfig `json:"inference,omitempty"` | |
| } | |
| // ExternalProvidersSpec configures external provider injection. | |
| type ExternalProvidersSpec struct { | |
| // Inference external providers. | |
| // +optional | |
| // +kubebuilder:validation:items:MinLength=1 | |
| Inference []ExternalProviderConfig `json:"inference,omitempty"` | |
| } |
| // ExternalProviderConfig defines an external provider sidecar. | ||
| type ExternalProviderConfig struct { | ||
| // ProviderID is the unique identifier for this provider. | ||
| // +kubebuilder:validation:Required | ||
| ProviderID string `json:"providerId"` | ||
|
|
||
| // Image is the container image for the provider sidecar. | ||
| // +kubebuilder:validation:Required | ||
| Image string `json:"image"` | ||
| } |
There was a problem hiding this comment.
Ensure all fields are non-empty strings.
| // ExternalProviderConfig defines an external provider sidecar. | |
| type ExternalProviderConfig struct { | |
| // ProviderID is the unique identifier for this provider. | |
| // +kubebuilder:validation:Required | |
| ProviderID string `json:"providerId"` | |
| // Image is the container image for the provider sidecar. | |
| // +kubebuilder:validation:Required | |
| Image string `json:"image"` | |
| } | |
| // ExternalProviderConfig defines an external provider sidecar. | |
| type ExternalProviderConfig struct { | |
| // ProviderID is the unique identifier for this provider. | |
| // +kubebuilder:validation:Required | |
| // +kubebuilder:validation:MinLength=1 | |
| ProviderID string `json:"providerId"` | |
| // Image is the container image for the provider sidecar. | |
| // +kubebuilder:validation:Required | |
| // +kubebuilder:validation:MinLength=1 | |
| Image string `json:"image"` | |
| } |
…alidation Incorporates all review feedback from @eoinfennessy on PR llamastack#264: - Add MinItems=1 on all optional slices to reject explicitly empty lists (provider slices, models, tools, shields, env, command, args, volumes, volumeMounts, topologySpreadConstraints, configMapKeys, namespaces, labels) - Add MinProperties=1 on secretRefs map - Add MinLength=1 on required string fields (provider, name, configMapName, providerId, image, secretKeyRef name/key) - Add CEL non-empty guards on optional strings (id, provider, modelType, quantization, hostname, serviceAccountName, mountPath, configMapNamespace) - Add reverse validation on storage types: endpoint/password only valid for redis, connectionString only valid for postgres - Add TLS reverse validation: secretName and caBundle only valid when enabled - Add port range validation (1-65535) - Add replicas Minimum=0 - Add autoscaling: minReplicas/maxReplicas Minimum=1, percentage range 1-100, CEL rule maxReplicas >= minReplicas - Add PDB mutual exclusivity: minAvailable and maxUnavailable cannot both be set, at least one required - Document *bool semantics on ExposeConfig.Enabled (nil vs false vs true) Assisted-By: 🤖 Claude Code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Closing in favor of #266, which includes the v1alpha2 types with all review feedback from this PR incorporated (see #266 (comment) for the full list of changes). |
…alidation Incorporates all review feedback from @eoinfennessy on PR llamastack#264: - Add MinItems=1 on all optional slices to reject explicitly empty lists (provider slices, models, tools, shields, env, command, args, volumes, volumeMounts, topologySpreadConstraints, configMapKeys, namespaces, labels) - Add MinProperties=1 on secretRefs map - Add MinLength=1 on required string fields (provider, name, configMapName, providerId, image, secretKeyRef name/key) - Add CEL non-empty guards on optional strings (id, provider, modelType, quantization, hostname, serviceAccountName, mountPath, configMapNamespace) - Add reverse validation on storage types: endpoint/password only valid for redis, connectionString only valid for postgres - Add TLS reverse validation: secretName and caBundle only valid when enabled - Add port range validation (1-65535) - Add replicas Minimum=0 - Add autoscaling: minReplicas/maxReplicas Minimum=1, percentage range 1-100, CEL rule maxReplicas >= minReplicas - Add PDB mutual exclusivity: minAvailable and maxUnavailable cannot both be set, at least one required - Document *bool semantics on ExposeConfig.Enabled (nil vs false vs true) Assisted-By: 🤖 Claude Code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Roland Huß <rhuss@redhat.com>
…alidation Incorporates all review feedback from @eoinfennessy on PR llamastack#264: - Add MinItems=1 on all optional slices to reject explicitly empty lists (provider slices, models, tools, shields, env, command, args, volumes, volumeMounts, topologySpreadConstraints, configMapKeys, namespaces, labels) - Add MinProperties=1 on secretRefs map - Add MinLength=1 on required string fields (provider, name, configMapName, providerId, image, secretKeyRef name/key) - Add CEL non-empty guards on optional strings (id, provider, modelType, quantization, hostname, serviceAccountName, mountPath, configMapNamespace) - Add reverse validation on storage types: endpoint/password only valid for redis, connectionString only valid for postgres - Add TLS reverse validation: secretName and caBundle only valid when enabled - Add port range validation (1-65535) - Add replicas Minimum=0 - Add autoscaling: minReplicas/maxReplicas Minimum=1, percentage range 1-100, CEL rule maxReplicas >= minReplicas - Add PDB mutual exclusivity: minAvailable and maxUnavailable cannot both be set, at least one required - Document *bool semantics on ExposeConfig.Enabled (nil vs false vs true) Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
Summary
Phase 1 implementation of the v1alpha2 CRD schema for operator-generated config. This PR adds the complete type definitions with CEL validation rules, ready for team review before proceeding to Phase 2 (config generation engine).
What's included
The
api/v1alpha2/package with:ProvidersSpecwith typed[]ProviderConfigslices (no polymorphic JSON)ProviderConfigwith explicitsecretRefsfield (no heuristic secret detection)ResourcesSpecwith typed[]ModelConfig(onlynamerequired)StateStorageSpecwith KV (sqlite/redis) and SQL (sqlite/postgres) backendsNetworkingSpecwith typedExposeConfig(enabled+hostnamefields)WorkloadSpecconsolidating all deployment settings from v1alpha1+kubebuilder:storageversionmarker (v1alpha2 is the hub)15 CEL validation rules
All compiled and verified with
controller-gen:secretNamerequired whenenabled: trueendpointrequired whentype: redisconnectionStringrequired whentype: postgresHow to test
Apply the CRD and try creating invalid CRs to verify CEL validation:
Review focus
api/v1alpha2/llamastackdistribution_types.gofeel right for Kubernetes users?inference: [{provider: vllm}](always-list syntax) acceptable?secretRefs map[string]SecretKeyRefdesign intuitive?Task progress
This project uses beads for task tracking:
Current: Phase 1 complete (13/13). Waiting for review before Phase 2 (config generation engine).
Spec reference
Full spec:
specs/002-operator-generated-config/spec.md(merged in #263)Review guide:
specs/002-operator-generated-config/REVIEW.mdAssisted-by: 🤖 Claude Code