Skip to content

Commit 9492f62

Browse files
committed
Add OpenStackAssistant CRD, controller, and webhook
Implements the OpenStackAssistant API (assistant.openstack.org/v1beta1) which deploys a managed Goose AI agent pod with read-only RBAC for cluster diagnostics via Lightspeed Stack.
1 parent 0eee9c3 commit 9492f62

12 files changed

Lines changed: 1973 additions & 40 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Licensed under the Apache License, Version 2.0 (the "License");
3+
you may not use this file except in compliance with the License.
4+
You may obtain a copy of the License at
5+
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
*/
14+
15+
package v1beta1
16+
17+
import (
18+
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
19+
)
20+
21+
// OpenStackAssistant Condition Types used by API objects.
22+
const (
23+
// OpenStackAssistantReadyCondition Status=True condition which indicates if OpenStackAssistant is configured and operational
24+
OpenStackAssistantReadyCondition condition.Type = "OpenStackAssistantReady"
25+
)
26+
27+
// Common Messages used by API objects.
28+
const (
29+
// OpenStackAssistantReadyInitMessage
30+
OpenStackAssistantReadyInitMessage = "OpenStack Assistant not started"
31+
32+
// OpenStackAssistantReadyRunningMessage
33+
OpenStackAssistantReadyRunningMessage = "OpenStack Assistant in progress"
34+
35+
// OpenStackAssistantReadyMessage
36+
OpenStackAssistantReadyMessage = "OpenStack Assistant created"
37+
38+
// OpenStackAssistantReadyErrorMessage
39+
OpenStackAssistantReadyErrorMessage = "OpenStack Assistant error occured %s"
40+
41+
// OpenStackAssistantProviderSecretWaitingMessage
42+
OpenStackAssistantProviderSecretWaitingMessage = "Waiting for lightspeed provider secret"
43+
44+
// OpenStackAssistantRecipesWaitingMessage
45+
OpenStackAssistantRecipesWaitingMessage = "Waiting for Goose recipes ConfigMap"
46+
47+
// OpenStackAssistantHintsWaitingMessage
48+
OpenStackAssistantHintsWaitingMessage = "Waiting for Goose hints ConfigMap"
49+
)

api/assistant/v1beta1/openstackassistant_types.go

Lines changed: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,110 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
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+
// GooseConfig defines Goose-specific provider configuration
56+
type GooseConfig struct {
57+
// Recipes is a ConfigMap name containing Goose recipe YAML files.
58+
// Each key in the ConfigMap becomes a recipe file registered as a
59+
// Goose slash command (e.g., /cluster-health).
60+
// +kubebuilder:validation:Optional
61+
Recipes *string `json:"recipes,omitempty"`
62+
63+
// Hints is a ConfigMap name containing Goose hints/context.
64+
// The ConfigMap must have a key "hints" with the content that
65+
// will be written to ~/.goosehints in the pod.
66+
// +kubebuilder:validation:Optional
67+
Hints *string `json:"hints,omitempty"`
68+
}
2569

26-
// OpenStackAssistantSpec defines the desired state of OpenStackAssistant.
70+
// OpenStackAssistantSpec defines the desired state of OpenStackAssistant
2771
type OpenStackAssistantSpec struct {
28-
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
29-
// Important: Run "make" to regenerate code after modifying this file
72+
// ContainerImage for the assistant container.
73+
// +kubebuilder:validation:Required
74+
ContainerImage string `json:"containerImage"`
75+
76+
// Provider is the AI agent provider type. Currently only "goose" is supported.
77+
// +kubebuilder:validation:Optional
78+
// +kubebuilder:default=goose
79+
Provider ProviderType `json:"provider,omitempty"`
80+
81+
// LightspeedStack configuration for the AI backend.
82+
// +kubebuilder:validation:Required
83+
LightspeedStack LightspeedStackSpec `json:"lightspeedStack"`
84+
85+
// Goose contains Goose-specific provider configuration.
86+
// Only applicable when provider is "goose".
87+
// +kubebuilder:validation:Optional
88+
Goose *GooseConfig `json:"goose,omitempty"`
89+
90+
// NodeSelector to target subset of worker nodes for pod scheduling.
91+
// +kubebuilder:validation:Optional
92+
NodeSelector *map[string]string `json:"nodeSelector,omitempty"`
3093

31-
// Foo is an example field of OpenStackAssistant. Edit openstackassistant_types.go to remove/update
32-
Foo string `json:"foo,omitempty"`
94+
// Env is a list of additional environment variables for the container.
95+
// +kubebuilder:validation:Optional
96+
// +listType=map
97+
// +listMapKey=name
98+
Env []corev1.EnvVar `json:"env,omitempty"`
3399
}
34100

35-
// OpenStackAssistantStatus defines the observed state of OpenStackAssistant.
101+
// OpenStackAssistantStatus defines the observed state of OpenStackAssistant
36102
type OpenStackAssistantStatus struct {
37-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
38-
// Important: Run "make" to regenerate code after modifying this file
103+
// PodName is the name of the running assistant pod
104+
PodName string `json:"podName,omitempty"`
105+
106+
// Conditions tracks the state of each sub-resource
107+
Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"`
108+
109+
// ObservedGeneration - the most recent generation observed
110+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
111+
112+
// Hash tracks input hashes to detect changes
113+
Hash map[string]string `json:"hash,omitempty"`
39114
}
40115

41116
// +kubebuilder:object:root=true
42117
// +kubebuilder:subresource:status
118+
// +operator-sdk:csv:customresourcedefinitions:displayName="OpenStack Assistant"
119+
// +kubebuilder:resource:shortName=osassistant;osassistants
120+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status"
121+
// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message"
43122

44-
// OpenStackAssistant is the Schema for the openstackassistants API.
123+
// OpenStackAssistant is the Schema for the openstackassistants API
45124
type OpenStackAssistant struct {
46125
metav1.TypeMeta `json:",inline"`
47126
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -52,7 +131,7 @@ type OpenStackAssistant struct {
52131

53132
// +kubebuilder:object:root=true
54133

55-
// OpenStackAssistantList contains a list of OpenStackAssistant.
134+
// OpenStackAssistantList contains a list of OpenStackAssistant
56135
type OpenStackAssistantList struct {
57136
metav1.TypeMeta `json:",inline"`
58137
metav1.ListMeta `json:"metadata,omitempty"`
@@ -62,3 +141,51 @@ type OpenStackAssistantList struct {
62141
func init() {
63142
SchemeBuilder.Register(&OpenStackAssistant{}, &OpenStackAssistantList{})
64143
}
144+
145+
// IsReady - returns true if OpenStackAssistant is reconciled successfully
146+
func (instance OpenStackAssistant) IsReady() bool {
147+
return instance.Status.Conditions.IsTrue(OpenStackAssistantReadyCondition)
148+
}
149+
150+
// RbacConditionsSet - set the conditions for the rbac object
151+
func (instance OpenStackAssistant) RbacConditionsSet(c *condition.Condition) {
152+
instance.Status.Conditions.Set(c)
153+
}
154+
155+
// RbacNamespace - return the namespace
156+
func (instance OpenStackAssistant) RbacNamespace() string {
157+
return instance.Namespace
158+
}
159+
160+
// RbacResourceName - return the name to be used for rbac objects (serviceaccount, role, rolebinding)
161+
func (instance OpenStackAssistant) RbacResourceName() string {
162+
return "openstackassistant-" + instance.Name
163+
}
164+
165+
// OpenStackAssistantDefaults holds defaults for the assistant
166+
type OpenStackAssistantDefaults struct {
167+
ContainerImageURL string
168+
}
169+
170+
var openStackAssistantDefaults OpenStackAssistantDefaults
171+
172+
// SetupOpenStackAssistantDefaults - initialize OpenStackAssistant spec defaults
173+
func SetupOpenStackAssistantDefaults(defaults OpenStackAssistantDefaults) {
174+
openStackAssistantDefaults = defaults
175+
}
176+
177+
// SetupDefaults - initializes any CRD field defaults based on environment variables
178+
func SetupDefaults() {
179+
openStackAssistantDefaults := OpenStackAssistantDefaults{
180+
ContainerImageURL: util.GetEnvVar("RELATED_IMAGE_OPENSTACK_ASSISTANT_IMAGE_URL_DEFAULT", OpenStackAssistantContainerImage),
181+
}
182+
183+
SetupOpenStackAssistantDefaults(openStackAssistantDefaults)
184+
}
185+
186+
// Default implements webhook.Defaulter
187+
func (r *OpenStackAssistant) Default() {
188+
if r.Spec.ContainerImage == "" {
189+
r.Spec.ContainerImage = openStackAssistantDefaults.ContainerImageURL
190+
}
191+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2022.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1beta1
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/runtime"
21+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
22+
)
23+
24+
// ValidateCreate implements webhook.Validator
25+
func (r *OpenStackAssistant) ValidateCreate() (admission.Warnings, error) {
26+
return nil, nil
27+
}
28+
29+
// ValidateUpdate implements webhook.Validator
30+
func (r *OpenStackAssistant) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) {
31+
return nil, nil
32+
}
33+
34+
// ValidateDelete implements webhook.Validator
35+
func (r *OpenStackAssistant) ValidateDelete() (admission.Warnings, error) {
36+
return nil, nil
37+
}

0 commit comments

Comments
 (0)