11/*
2- Copyright 2022 .
2+ Copyright 2026 .
33
44Licensed under the Apache License, Version 2.0 (the "License");
55you may not use this file except in compliance with the License.
@@ -17,31 +17,139 @@ limitations under the License.
1717package v1beta1
1818
1919import (
20+ condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
21+ "github.com/openstack-k8s-operators/lib-common/modules/common/util"
22+ corev1 "k8s.io/api/core/v1"
2023 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2124)
2225
23- // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
24- // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
26+ const (
27+ // OpenStackAssistantContainerImage is the fall-back container image for OpenStackAssistant
28+ OpenStackAssistantContainerImage = "quay.io/dprince/goose:oc-fedora"
29+ )
30+
31+ // ProviderType defines the AI agent provider
32+ // +kubebuilder:validation:Enum=goose
33+ type ProviderType string
34+
35+ const (
36+ // ProviderGoose is the Goose AI agent provider
37+ ProviderGoose ProviderType = "goose"
38+ )
39+
40+ // LightspeedStackSpec defines connectivity to the Lightspeed Stack AI backend
41+ type LightspeedStackSpec struct {
42+ // ProviderSecret is the name of a Secret containing the lightspeed
43+ // provider config JSON (custom_providers/lightspeed.json content).
44+ // Must contain key "lightspeed.json".
45+ // +kubebuilder:validation:Required
46+ ProviderSecret string `json:"providerSecret"`
47+
48+ // CaBundleSecretName is the name of a Secret containing CA certs
49+ // to trust for TLS connections to the lightspeed-stack endpoint.
50+ // The Secret must contain a key "ca-bundle.crt" with PEM-encoded certs.
51+ // +kubebuilder:validation:Optional
52+ CaBundleSecretName string `json:"caBundleSecretName,omitempty"`
53+ }
54+
55+ // MCPServerRef references an MCP server endpoint to configure as a Goose extension.
56+ // Either URL or OpenStackClientRef must be specified, but not both.
57+ type MCPServerRef struct {
58+ // Name is the extension name in Goose config
59+ // +kubebuilder:validation:Required
60+ Name string `json:"name"`
61+
62+ // URL is the MCP server's Streamable HTTP endpoint.
63+ // Mutually exclusive with OpenStackClientRef.
64+ // +kubebuilder:validation:Optional
65+ URL string `json:"url,omitempty"`
66+
67+ // OpenStackClientRef is the name of an OpenStackClient CR in the same
68+ // namespace that has MCP enabled. The controller auto-computes the
69+ // correct service URL and TLS CA configuration.
70+ // Mutually exclusive with URL.
71+ // +kubebuilder:validation:Optional
72+ OpenStackClientRef string `json:"openstackClientRef,omitempty"`
73+ }
2574
26- // OpenStackAssistantSpec defines the desired state of OpenStackAssistant.
75+ // GooseConfig defines Goose-specific provider configuration
76+ type GooseConfig struct {
77+ // Model is the model identifier for the Goose AI agent
78+ // (e.g., "gemini/models/gemini-2.5-flash"). Sets the GOOSE_MODEL env var.
79+ // +kubebuilder:validation:Optional
80+ Model string `json:"model,omitempty"`
81+
82+ // Recipes is a ConfigMap name containing Goose recipe YAML files.
83+ // Each key in the ConfigMap becomes a recipe file registered as a
84+ // Goose slash command (e.g., /cluster-health).
85+ // +kubebuilder:validation:Optional
86+ Recipes * string `json:"recipes,omitempty"`
87+
88+ // Hints is a ConfigMap name containing Goose hints/context.
89+ // The ConfigMap must have a key "hints" with the content that
90+ // will be written to ~/.goosehints in the pod.
91+ // +kubebuilder:validation:Optional
92+ Hints * string `json:"hints,omitempty"`
93+
94+ // MCPServers lists MCP server endpoints to configure as Goose extensions.
95+ // +kubebuilder:validation:Optional
96+ MCPServers []MCPServerRef `json:"mcpServers,omitempty"`
97+ }
98+
99+ // OpenStackAssistantSpec defines the desired state of OpenStackAssistant
27100type OpenStackAssistantSpec struct {
28- // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
29- // Important: Run "make" to regenerate code after modifying this file
101+ // ContainerImage for the assistant container (will be set to environmental default if empty).
102+ // +kubebuilder:validation:Optional
103+ ContainerImage string `json:"containerImage,omitempty"`
30104
31- // Foo is an example field of OpenStackAssistant. Edit openstackassistant_types.go to remove/update
32- Foo string `json:"foo,omitempty"`
105+ // Provider is the AI agent provider type. Currently only "goose" is supported.
106+ // +kubebuilder:validation:Optional
107+ // +kubebuilder:default=goose
108+ Provider ProviderType `json:"provider"`
109+
110+ // LightspeedStack configuration for the AI backend.
111+ // +kubebuilder:validation:Required
112+ LightspeedStack LightspeedStackSpec `json:"lightspeedStack"`
113+
114+ // Goose contains Goose-specific provider configuration.
115+ // Only applicable when provider is "goose".
116+ // +kubebuilder:validation:Optional
117+ Goose * GooseConfig `json:"goose,omitempty"`
118+
119+ // NodeSelector to target subset of worker nodes for pod scheduling.
120+ // +kubebuilder:validation:Optional
121+ NodeSelector * map [string ]string `json:"nodeSelector,omitempty"`
122+
123+ // Env is a list of additional environment variables for the container.
124+ // +kubebuilder:validation:Optional
125+ // +listType=map
126+ // +listMapKey=name
127+ Env []corev1.EnvVar `json:"env,omitempty"`
33128}
34129
35- // OpenStackAssistantStatus defines the observed state of OpenStackAssistant.
130+ // OpenStackAssistantStatus defines the observed state of OpenStackAssistant
36131type OpenStackAssistantStatus struct {
37- // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
38- // Important: Run "make" to regenerate code after modifying this file
132+ // PodName is the name of the running assistant pod
133+ PodName string `json:"podName,omitempty"`
134+
135+ // Conditions tracks the state of each sub-resource
136+ Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"`
137+
138+ // ObservedGeneration - the most recent generation observed
139+ ObservedGeneration int64 `json:"observedGeneration,omitempty"`
140+
141+ // Hash tracks input hashes to detect changes
142+ Hash map [string ]string `json:"hash,omitempty"`
39143}
40144
41145// +kubebuilder:object:root=true
42146// +kubebuilder:subresource:status
147+ // +operator-sdk:csv:customresourcedefinitions:displayName="OpenStack Assistant"
148+ // +kubebuilder:resource:shortName=osassistant;osassistants
149+ // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status"
150+ // +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message"
43151
44- // OpenStackAssistant is the Schema for the openstackassistants API.
152+ // OpenStackAssistant is the Schema for the openstackassistants API
45153type OpenStackAssistant struct {
46154 metav1.TypeMeta `json:",inline"`
47155 metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -52,7 +160,7 @@ type OpenStackAssistant struct {
52160
53161// +kubebuilder:object:root=true
54162
55- // OpenStackAssistantList contains a list of OpenStackAssistant.
163+ // OpenStackAssistantList contains a list of OpenStackAssistant
56164type OpenStackAssistantList struct {
57165 metav1.TypeMeta `json:",inline"`
58166 metav1.ListMeta `json:"metadata,omitempty"`
@@ -62,3 +170,51 @@ type OpenStackAssistantList struct {
62170func init () {
63171 SchemeBuilder .Register (& OpenStackAssistant {}, & OpenStackAssistantList {})
64172}
173+
174+ // IsReady - returns true if OpenStackAssistant is reconciled successfully
175+ func (instance OpenStackAssistant ) IsReady () bool {
176+ return instance .Status .Conditions .IsTrue (OpenStackAssistantReadyCondition )
177+ }
178+
179+ // RbacConditionsSet - set the conditions for the rbac object
180+ func (instance OpenStackAssistant ) RbacConditionsSet (c * condition.Condition ) {
181+ instance .Status .Conditions .Set (c )
182+ }
183+
184+ // RbacNamespace - return the namespace
185+ func (instance OpenStackAssistant ) RbacNamespace () string {
186+ return instance .Namespace
187+ }
188+
189+ // RbacResourceName - return the name to be used for rbac objects (serviceaccount, role, rolebinding)
190+ func (instance OpenStackAssistant ) RbacResourceName () string {
191+ return "openstackassistant-" + instance .Name
192+ }
193+
194+ // OpenStackAssistantDefaults holds defaults for the assistant
195+ type OpenStackAssistantDefaults struct {
196+ ContainerImageURL string
197+ }
198+
199+ var openStackAssistantDefaults OpenStackAssistantDefaults
200+
201+ // SetupOpenStackAssistantDefaults - initialize OpenStackAssistant spec defaults
202+ func SetupOpenStackAssistantDefaults (defaults OpenStackAssistantDefaults ) {
203+ openStackAssistantDefaults = defaults
204+ }
205+
206+ // SetupDefaults - initializes any CRD field defaults based on environment variables
207+ func SetupDefaults () {
208+ openStackAssistantDefaults := OpenStackAssistantDefaults {
209+ ContainerImageURL : util .GetEnvVar ("RELATED_IMAGE_OPENSTACK_ASSISTANT_IMAGE_URL_DEFAULT" , OpenStackAssistantContainerImage ),
210+ }
211+
212+ SetupOpenStackAssistantDefaults (openStackAssistantDefaults )
213+ }
214+
215+ // Default implements webhook.Defaulter
216+ func (r * OpenStackAssistant ) Default () {
217+ if r .Spec .ContainerImage == "" {
218+ r .Spec .ContainerImage = openStackAssistantDefaults .ContainerImageURL
219+ }
220+ }
0 commit comments