-
Notifications
You must be signed in to change notification settings - Fork 114
Add OpenStackAssistant CRD with MCP server support #1927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dprince
wants to merge
2
commits into
openstack-k8s-operators:main
Choose a base branch
from
dprince:assistant
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /* | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package v1beta1 | ||
|
|
||
| import ( | ||
| condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" | ||
| ) | ||
|
|
||
| // OpenStackAssistant Condition Types used by API objects. | ||
| const ( | ||
| // OpenStackAssistantReadyCondition Status=True condition which indicates if OpenStackAssistant is configured and operational | ||
| OpenStackAssistantReadyCondition condition.Type = "OpenStackAssistantReady" | ||
| ) | ||
|
|
||
| // Common Messages used by API objects. | ||
| const ( | ||
| // OpenStackAssistantReadyInitMessage | ||
| OpenStackAssistantReadyInitMessage = "OpenStack Assistant not started" | ||
|
|
||
| // OpenStackAssistantReadyRunningMessage | ||
| OpenStackAssistantReadyRunningMessage = "OpenStack Assistant in progress" | ||
|
|
||
| // OpenStackAssistantReadyMessage | ||
| OpenStackAssistantReadyMessage = "OpenStack Assistant created" | ||
|
|
||
| // OpenStackAssistantReadyErrorMessage | ||
| OpenStackAssistantReadyErrorMessage = "OpenStack Assistant error occured %s" | ||
|
|
||
| // OpenStackAssistantProviderSecretWaitingMessage | ||
| OpenStackAssistantProviderSecretWaitingMessage = "Waiting for lightspeed provider secret" | ||
|
|
||
| // OpenStackAssistantRecipesWaitingMessage | ||
| OpenStackAssistantRecipesWaitingMessage = "Waiting for Goose recipes ConfigMap" | ||
|
|
||
| // OpenStackAssistantHintsWaitingMessage | ||
| OpenStackAssistantHintsWaitingMessage = "Waiting for Goose hints ConfigMap" | ||
|
|
||
| // OpenStackAssistantSkillsWaitingMessage | ||
| OpenStackAssistantSkillsWaitingMessage = "Waiting for Goose skills ConfigMap" | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /* | ||
| Copyright 2026. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| // Package v1beta1 contains API Schema definitions for the assistant v1beta1 API group. | ||
| // +kubebuilder:object:generate=true | ||
| // +groupName=assistant.openstack.org | ||
| package v1beta1 | ||
|
|
||
| import ( | ||
| "k8s.io/apimachinery/pkg/runtime/schema" | ||
| "sigs.k8s.io/controller-runtime/pkg/scheme" | ||
| ) | ||
|
|
||
| var ( | ||
| // GroupVersion is group version used to register these objects. | ||
| GroupVersion = schema.GroupVersion{Group: "assistant.openstack.org", Version: "v1beta1"} | ||
|
|
||
| // SchemeBuilder is used to add go types to the GroupVersionKind scheme. | ||
| SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} | ||
|
|
||
| // AddToScheme adds the types in this group-version to the given scheme. | ||
| AddToScheme = SchemeBuilder.AddToScheme | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| /* | ||
| Copyright 2026. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package v1beta1 | ||
|
|
||
| import ( | ||
| condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" | ||
| "github.com/openstack-k8s-operators/lib-common/modules/common/util" | ||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| ) | ||
|
|
||
| const ( | ||
| // OpenStackAssistantContainerImage is the fall-back container image for OpenStackAssistant | ||
| OpenStackAssistantContainerImage = "quay.io/dprince/goose:oc-fedora" | ||
| ) | ||
|
|
||
| // ProviderType defines the AI agent provider | ||
| // +kubebuilder:validation:Enum=goose | ||
| type ProviderType string | ||
|
|
||
| const ( | ||
| // ProviderGoose is the Goose AI agent provider | ||
| ProviderGoose ProviderType = "goose" | ||
| ) | ||
|
|
||
| // LightspeedStackSpec defines connectivity to the Lightspeed Stack AI backend | ||
| type LightspeedStackSpec struct { | ||
| // ProviderSecret is the name of a Secret containing the lightspeed | ||
| // provider config JSON (custom_providers/lightspeed.json content). | ||
| // Must contain key "lightspeed.json". | ||
| // +kubebuilder:validation:Required | ||
| ProviderSecret string `json:"providerSecret"` | ||
|
|
||
| // CaBundleSecretName is the name of a Secret containing CA certs | ||
| // to trust for TLS connections to the lightspeed-stack endpoint. | ||
| // The Secret must contain a key "ca-bundle.crt" with PEM-encoded certs. | ||
| // +kubebuilder:validation:Optional | ||
| CaBundleSecretName string `json:"caBundleSecretName,omitempty"` | ||
| } | ||
|
|
||
| // MCPServerRef references an MCP server endpoint to configure as a Goose extension. | ||
| // Exactly one of URL or OpenStackClientRef must be specified. | ||
| // +kubebuilder:validation:XValidation:rule="has(self.url) != has(self.openstackClientRef)",message="exactly one of url or openstackClientRef must be set" | ||
| type MCPServerRef struct { | ||
| // Name is the extension name in Goose config. It is used to derive the | ||
| // MCP_SERVER_<name> environment variable, so it must be a valid | ||
| // environment-variable name: start with a letter or underscore and | ||
| // contain only letters, digits, and underscores (no dashes). | ||
| // +kubebuilder:validation:Required | ||
| // +kubebuilder:validation:MinLength=1 | ||
| // +kubebuilder:validation:MaxLength=63 | ||
| // +kubebuilder:validation:Pattern=`^[a-zA-Z_][a-zA-Z0-9_]*$` | ||
| Name string `json:"name"` | ||
|
|
||
| // URL is the MCP server's Streamable HTTP endpoint. | ||
| // Mutually exclusive with OpenStackClientRef. | ||
| // +kubebuilder:validation:Optional | ||
| // +kubebuilder:validation:MinLength=1 | ||
| URL string `json:"url,omitempty"` | ||
|
|
||
| // OpenStackClientRef is the name of an OpenStackClient CR in the same | ||
| // namespace that has MCP enabled. The controller auto-computes the | ||
| // correct service URL and TLS CA configuration. | ||
| // Mutually exclusive with URL. | ||
| // +kubebuilder:validation:Optional | ||
| // +kubebuilder:validation:MinLength=1 | ||
| OpenStackClientRef string `json:"openstackClientRef,omitempty"` | ||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // GooseConfig defines Goose-specific provider configuration | ||
| type GooseConfig struct { | ||
| // Model is the model identifier for the Goose AI agent | ||
| // (e.g., "gemini/models/gemini-2.5-flash"). Sets the GOOSE_MODEL env var. | ||
| // +kubebuilder:validation:Optional | ||
| Model string `json:"model,omitempty"` | ||
|
|
||
| // Recipes is a ConfigMap name containing Goose recipe YAML files. | ||
| // Each key in the ConfigMap becomes a recipe file registered as a | ||
| // Goose slash command (e.g., /cluster-health). | ||
| // +kubebuilder:validation:Optional | ||
| Recipes *string `json:"recipes,omitempty"` | ||
|
|
||
| // Skills is a ConfigMap name containing Goose Agent Skill files. | ||
| // Each key in the ConfigMap becomes a skill named after the key | ||
| // (extension stripped), written as ~/.config/goose/skills/<name>/SKILL.md. | ||
| // Unlike Recipes, skills are not explicitly invoked - Goose loads | ||
| // them automatically when their description matches the task at hand. | ||
| // +kubebuilder:validation:Optional | ||
| Skills *string `json:"skills,omitempty"` | ||
|
|
||
| // Hints is a ConfigMap name containing Goose hints/context. | ||
| // The ConfigMap must have a key "hints" with the content that | ||
| // will be written to ~/.goosehints in the pod. | ||
| // +kubebuilder:validation:Optional | ||
| Hints *string `json:"hints,omitempty"` | ||
|
|
||
| // MCPServers lists MCP server endpoints to configure as Goose extensions. | ||
| // +kubebuilder:validation:Optional | ||
| MCPServers []MCPServerRef `json:"mcpServers,omitempty"` | ||
| } | ||
|
|
||
| // OpenStackAssistantSpec defines the desired state of OpenStackAssistant | ||
| type OpenStackAssistantSpec struct { | ||
| // ContainerImage for the assistant container (will be set to environmental default if empty). | ||
| // +kubebuilder:validation:Optional | ||
| ContainerImage string `json:"containerImage,omitempty"` | ||
|
|
||
| // Provider is the AI agent provider type. Currently only "goose" is supported. | ||
| // +kubebuilder:validation:Optional | ||
| // +kubebuilder:default=goose | ||
| Provider ProviderType `json:"provider,omitempty"` | ||
|
|
||
| // LightspeedStack configuration for the AI backend. | ||
| // +kubebuilder:validation:Required | ||
| LightspeedStack LightspeedStackSpec `json:"lightspeedStack"` | ||
|
|
||
| // Goose contains Goose-specific provider configuration. | ||
| // Only applicable when provider is "goose". | ||
| // +kubebuilder:validation:Optional | ||
| Goose *GooseConfig `json:"goose,omitempty"` | ||
|
|
||
| // NodeSelector to target subset of worker nodes for pod scheduling. | ||
| // +kubebuilder:validation:Optional | ||
| NodeSelector *map[string]string `json:"nodeSelector,omitempty"` | ||
|
|
||
| // Env is a list of additional environment variables for the container. | ||
| // +kubebuilder:validation:Optional | ||
| // +listType=map | ||
| // +listMapKey=name | ||
| Env []corev1.EnvVar `json:"env,omitempty"` | ||
| } | ||
|
|
||
| // OpenStackAssistantStatus defines the observed state of OpenStackAssistant | ||
| type OpenStackAssistantStatus struct { | ||
| // PodName is the name of the running assistant pod | ||
| PodName string `json:"podName,omitempty"` | ||
|
|
||
| // Conditions tracks the state of each sub-resource | ||
| Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` | ||
|
|
||
| // ObservedGeneration - the most recent generation observed | ||
| ObservedGeneration int64 `json:"observedGeneration,omitempty"` | ||
|
|
||
| // Hash tracks input hashes to detect changes | ||
| Hash map[string]string `json:"hash,omitempty"` | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
| // +kubebuilder:subresource:status | ||
| // +operator-sdk:csv:customresourcedefinitions:displayName="OpenStack Assistant" | ||
| // +kubebuilder:resource:shortName=osassistant;osassistants | ||
| // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" | ||
| // +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" | ||
|
|
||
| // OpenStackAssistant is the Schema for the openstackassistants API | ||
| type OpenStackAssistant struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
|
||
| Spec OpenStackAssistantSpec `json:"spec,omitempty"` | ||
| Status OpenStackAssistantStatus `json:"status,omitempty"` | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
|
|
||
| // OpenStackAssistantList contains a list of OpenStackAssistant | ||
| type OpenStackAssistantList struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| metav1.ListMeta `json:"metadata,omitempty"` | ||
| Items []OpenStackAssistant `json:"items"` | ||
| } | ||
|
|
||
| func init() { | ||
| SchemeBuilder.Register(&OpenStackAssistant{}, &OpenStackAssistantList{}) | ||
| } | ||
|
|
||
| // IsReady - returns true if OpenStackAssistant is reconciled successfully | ||
| func (instance OpenStackAssistant) IsReady() bool { | ||
| return instance.Status.Conditions.IsTrue(OpenStackAssistantReadyCondition) | ||
| } | ||
|
|
||
| // RbacConditionsSet - set the conditions for the rbac object | ||
| func (instance OpenStackAssistant) RbacConditionsSet(c *condition.Condition) { | ||
| instance.Status.Conditions.Set(c) | ||
| } | ||
|
|
||
| // RbacNamespace - return the namespace | ||
| func (instance OpenStackAssistant) RbacNamespace() string { | ||
| return instance.Namespace | ||
| } | ||
|
|
||
| // RbacResourceName - return the name to be used for rbac objects (serviceaccount, role, rolebinding) | ||
| func (instance OpenStackAssistant) RbacResourceName() string { | ||
| return "openstackassistant-" + instance.Name | ||
| } | ||
|
|
||
| // OpenStackAssistantDefaults holds defaults for the assistant | ||
| type OpenStackAssistantDefaults struct { | ||
| ContainerImageURL string | ||
| } | ||
|
|
||
| var openStackAssistantDefaults OpenStackAssistantDefaults | ||
|
|
||
| // SetupOpenStackAssistantDefaults - initialize OpenStackAssistant spec defaults | ||
| func SetupOpenStackAssistantDefaults(defaults OpenStackAssistantDefaults) { | ||
| openStackAssistantDefaults = defaults | ||
| } | ||
|
|
||
| // SetupDefaults - initializes any CRD field defaults based on environment variables | ||
| func SetupDefaults() { | ||
| openStackAssistantDefaults := OpenStackAssistantDefaults{ | ||
| ContainerImageURL: util.GetEnvVar("RELATED_IMAGE_OPENSTACK_ASSISTANT_IMAGE_URL_DEFAULT", OpenStackAssistantContainerImage), | ||
| } | ||
|
|
||
| SetupOpenStackAssistantDefaults(openStackAssistantDefaults) | ||
| } | ||
|
|
||
| // Default implements webhook.Defaulter | ||
| func (r *OpenStackAssistant) Default() { | ||
| if r.Spec.ContainerImage == "" { | ||
| r.Spec.ContainerImage = openStackAssistantDefaults.ContainerImageURL | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /* | ||
| Copyright 2026. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package v1beta1 | ||
|
|
||
| import ( | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
| ) | ||
|
|
||
| // ValidateCreate implements webhook.Validator | ||
| func (r *OpenStackAssistant) ValidateCreate() (admission.Warnings, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| // ValidateUpdate implements webhook.Validator | ||
| func (r *OpenStackAssistant) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| // ValidateDelete implements webhook.Validator | ||
| func (r *OpenStackAssistant) ValidateDelete() (admission.Warnings, error) { | ||
| return nil, nil | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.