Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-and-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
uses: redhat-actions/openshift-tools-installer@v1
with:
source: github
operator-sdk: 1.38.0
operator-sdk: 1.41.1

- name: Log in to Quay
uses: docker/login-action@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/verify-generation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: redhat-actions/openshift-tools-installer@v1
with:
source: github
operator-sdk: 1.38.0
operator-sdk: 1.41.1

- name: Verify generated files are up to date and fail if anything changed
run: |
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ endif

# Set the Operator SDK version to use. By default, what is installed on the system is used.
# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
OPERATOR_SDK_VERSION ?= v1.38.0-ocp
OPERATOR_SDK_VERSION ?= v1.41.1
# Image URL to use all building/pushing image targets
IMG ?= $(IMAGE_TAG_BASE):latest
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
Expand Down
22 changes: 22 additions & 0 deletions api/v1beta1/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const (
// operational and it can be used by OpenStack Lightspeed operator.
OpenShiftLightspeedOperatorReadyCondition condition.Type = "OpenShiftLightspeedOperatorReady"

// OpenStackLightspeedMCPServerReadyCondition is set to True when the MCP server
// deployment succeeds. False indicates a failure during MCP server deployment.
OpenStackLightspeedMCPServerReadyCondition condition.Type = "OpenStackLightspeedMCPServerReady"

// OCPRAGCondition Status=True condition which indicates the OCP RAG version resolution status
OCPRAGCondition condition.Type = "OCPRAGReady"
)
Expand Down Expand Up @@ -64,6 +68,24 @@ const (
// OCPRAGOverrideInvalidMessage
OCPRAGOverrideInvalidMessage = "Invalid OCP RAG version override"

// OpenStackLightspeedMCPServerInitMessage
OpenStackLightspeedMCPServerInitMessage = "MCP server deployment has not resolved"

// OpenStackLightspeedMCPServerDeployed
OpenStackLightspeedMCPServerDeployed = "MCP server is ready"

// OpenStackLightspeedMCPServerWaitingOpenStack
OpenStackLightspeedMCPServerWaitingOpenStack = "MCP server deployed, waiting for OpenStackControlPlane to become ready"

// OpenStackLightspeedMCPServerCreatingUser
OpenStackLightspeedMCPServerCreatingUser = "Creating OpenStack service user"

// OpenStackLightspeedMCPServerWaitingAC
OpenStackLightspeedMCPServerWaitingAC = "Waiting for application credential secret"

// OpenStackLightspeedMCPServerDisabledMessage
OpenStackLightspeedMCPServerDisabledMessage = "RHOS MCP server is disabled (rhos_mcps feature flag not set)"

// DeploymentCheckFailedMessage
DeploymentCheckFailedMessage = "Failed to check deployment status: %s"

Expand Down
17 changes: 17 additions & 0 deletions api/v1beta1/openstacklightspeed_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const (
// OKPContainerImage is the fall-back container image for OKP (Offline Knowledge Portal)
OKPContainerImage = "registry.redhat.io/offline-knowledge-portal/rhokp-rhel9:latest"

// MCPServerContainerImage is the fall-back container image for the MCP server
MCPServerContainerImage = "quay.io/openstack-lightspeed/rhos-mcps:latest"
Comment on lines +49 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

sed -n '1,120p' api/v1beta1/openstacklightspeed_types.go && printf '\n---\n' && sed -n '300,360p' api/v1beta1/openstacklightspeed_types.go

Repository: openstack-lightspeed/operator

Length of output: 6783


🏁 Script executed:

rg -n "MCPServerContainerImage|RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT|latest|rhos-mcps" api/v1beta1/openstacklightspeed_types.go api -S

Repository: openstack-lightspeed/operator

Length of output: 2317


🏁 Script executed:

git diff --stat -- api/v1beta1/openstacklightspeed_types.go && printf '\n---\n' && git diff -- api/v1beta1/openstacklightspeed_types.go --unified=20

Repository: openstack-lightspeed/operator

Length of output: 172


🏁 Script executed:

rg -n "RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT|MCPServerImageURL|MCPServerContainerImage|rhos-mcps" -S .

Repository: openstack-lightspeed/operator

Length of output: 1449


🏁 Script executed:

rg -n "RELATED_IMAGE_.*IMAGE_URL_DEFAULT|RELATED_IMAGE_.*DEFAULT" -S config bundle manifests deploy api controllers | sed -n '1,220p'

Repository: openstack-lightspeed/operator

Length of output: 3145


🏁 Script executed:

fd -a -t f 'csv|yaml|yml' config bundle manifests deploy . | sed -n '1,200p'

Repository: openstack-lightspeed/operator

Length of output: 10746


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== related image refs =="
rg -n "RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT|RELATED_IMAGE_.*IMAGE_URL_DEFAULT" config bundle manifests deploy api controllers -S || true

echo
echo "== candidate manifest files =="
fd -a -t f 'csv|yaml|yml' config bundle manifests deploy . | sed -n '1,200p'

Repository: openstack-lightspeed/operator

Length of output: 13781


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== related image refs =="
rg -n "RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT|RELATED_IMAGE_.*IMAGE_URL_DEFAULT" config bundle manifests deploy api controllers -S || true

echo
echo "== manifest files =="
git ls-files 'config/**' 'bundle/**' 'manifests/**' 'deploy/**' | sed -n '1,200p'

Repository: openstack-lightspeed/operator

Length of output: 5126


Pin the MCP sidecar image to an immutable release.

quay.io/openstack-lightspeed/rhos-mcps:latest is still used as the default in api/v1beta1/openstacklightspeed_types.go, and the same tag is baked into config/manager/manager.yaml and the generated CSV. That makes installs and upgrades pick up whatever image is current at deploy time instead of the tested MCP binary.

Suggested change
-	MCPServerContainerImage = "quay.io/openstack-lightspeed/rhos-mcps:latest"
+	MCPServerContainerImage = "quay.io/openstack-lightspeed/rhos-mcps:<release-or-digest>"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/v1beta1/openstacklightspeed_types.go` around lines 49 - 50, The MCP
sidecar image is still defaulting to a mutable “latest” tag, which means
deployments can drift from the tested binary. Update the default image constant
in openstacklightspeed_types.go and ensure the same pinned, immutable release
image is used in manager.yaml and the generated CSV so all install and upgrade
paths reference the exact released MCP server image.


// MaxTokensForResponseDefault is the default maximum number of tokens that should be used for response
MaxTokensForResponseDefault = 2048
)
Expand Down Expand Up @@ -220,6 +223,16 @@ type OpenStackLightspeedStatus struct {
// ActiveOCPRAGVersion contains the OCP version being used for RAG configuration
// Will be one of: "4.16", "4.18", "latest", or empty if OCP RAG is disabled
ActiveOCPRAGVersion string `json:"activeOCPRAGVersion,omitempty"`

// +optional
// OpenStackReady indicates whether an OpenStackControlPlane was detected and
// is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
OpenStackReady bool `json:"openStackReady,omitempty"`

// +optional
// ApplicationCredentialSecret is the name of the current AC secret in the
// OpenStack namespace. Tracked for rotation detection.
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
}

// +kubebuilder:object:root=true
Expand All @@ -244,6 +257,7 @@ type OpenStackLightspeedStatus struct {
// +operator-sdk:csv:customresourcedefinitions:resources={{PersistentVolumeClaim,v1,openstack-lightspeed-database}}
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterRole,v1,lightspeed-app-server-sar-role}}
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterRoleBinding,v1,lightspeed-app-server-sar-role-binding}}
// +operator-sdk:csv:customresourcedefinitions:resources={{ConfigMap,v1,mcp-config}}
// +operator-sdk:csv:customresourcedefinitions:resources={{Subscription,v1alpha1}}
// +operator-sdk:csv:customresourcedefinitions:resources={{ClusterServiceVersion,v1alpha1}}
// +operator-sdk:csv:customresourcedefinitions:resources={{InstallPlan,v1alpha1}}
Expand Down Expand Up @@ -283,6 +297,7 @@ type OpenStackLightspeedDefaults struct {
ConsoleImageURL string
ConsoleImagePF5URL string
OKPImageURL string
MCPServerImageURL string
MaxTokensForResponse int
}

Expand All @@ -306,6 +321,8 @@ func SetupDefaults() {
"RELATED_IMAGE_CONSOLE_PF5_IMAGE_URL_DEFAULT", ConsoleContainerImagePF5),
OKPImageURL: util.GetEnvVar(
"RELATED_IMAGE_OKP_IMAGE_URL_DEFAULT", OKPContainerImage),
MCPServerImageURL: util.GetEnvVar(
"RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT", MCPServerContainerImage),
MaxTokensForResponse: MaxTokensForResponseDefault,
}

Expand Down
2 changes: 1 addition & 1 deletion bundle.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/
LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/
LABEL operators.operatorframework.io.bundle.package.v1=openstack-lightspeed-operator
LABEL operators.operatorframework.io.bundle.channels.v1=alpha
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.38.0
LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.41.1
LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1
LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ spec:
ActiveOCPRAGVersion contains the OCP version being used for RAG configuration
Will be one of: "4.16", "4.18", "latest", or empty if OCP RAG is disabled
type: string
applicationCredentialSecret:
description: |-
ApplicationCredentialSecret is the name of the current AC secret in the
OpenStack namespace. Tracked for rotation detection.
type: string
conditions:
description: Conditions
items:
Expand Down Expand Up @@ -251,6 +256,11 @@ spec:
for this object.
format: int64
type: integer
openStackReady:
description: |-
OpenStackReady indicates whether an OpenStackControlPlane was detected and
is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
type: boolean
type: object
type: object
served: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ metadata:
features.operators.openshift.io/token-auth-azure: "false"
features.operators.openshift.io/token-auth-gcp: "false"
operatorframework.io/suggested-namespace: openstack-lightspeed
operators.operatorframework.io/builder: operator-sdk-v1.38.0
operators.operatorframework.io/builder: operator-sdk-v1.41.1
operators.operatorframework.io/project_layout: go.kubebuilder.io/v4
repository: https://github.com/openstack-lightspeed/operator
name: openstack-lightspeed-operator.v0.0.1
Expand Down Expand Up @@ -110,6 +110,9 @@ spec:
- kind: ConfigMap
name: llama-stack-config
version: v1
- kind: ConfigMap
name: mcp-config
version: v1
- kind: Secret
name: metrics-reader-token
version: v1
Expand Down Expand Up @@ -162,6 +165,26 @@ spec:
spec:
clusterPermissions:
- rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resourceNames:
Expand All @@ -170,6 +193,14 @@ spec:
- secrets
verbs:
- get
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- get
- list
- watch
- apiGroups:
- config.openshift.io
resources:
Expand All @@ -190,6 +221,32 @@ spec:
- patch
- update
- watch
- apiGroups:
- core.openstack.org
resources:
- openstackcontrolplanes
verbs:
- get
- list
- watch
- apiGroups:
- keystone.openstack.org
resources:
- keystoneapplicationcredentials
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- keystone.openstack.org
resources:
- keystoneapplicationcredentials/status
verbs:
- get
- apiGroups:
- lightspeed.openstack.org
resources:
Expand Down Expand Up @@ -305,6 +362,8 @@ spec:
value: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-rhel9:1.0.12
- name: RELATED_IMAGE_CONSOLE_PF5_IMAGE_URL_DEFAULT
value: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9:1.0.12
- name: RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT
value: quay.io/openstack-lightspeed/rhos-mcps:latest
image: quay.io/openstack-lightspeed/operator:latest
livenessProbe:
httpGet:
Expand All @@ -322,10 +381,10 @@ spec:
resources:
limits:
cpu: 500m
memory: 128Mi
memory: 256Mi
requests:
cpu: 10m
memory: 64Mi
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
Expand Down Expand Up @@ -492,4 +551,6 @@ spec:
name: console-image-url-default
- image: registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9:1.0.12
name: console-pf5-image-url-default
- image: quay.io/openstack-lightspeed/rhos-mcps:latest
name: mcp-server-image-url-default
version: 0.0.1
2 changes: 1 addition & 1 deletion bundle/metadata/annotations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ annotations:
operators.operatorframework.io.bundle.metadata.v1: metadata/
operators.operatorframework.io.bundle.package.v1: openstack-lightspeed-operator
operators.operatorframework.io.bundle.channels.v1: alpha
operators.operatorframework.io.metrics.builder: operator-sdk-v1.38.0
operators.operatorframework.io.metrics.builder: operator-sdk-v1.41.1
operators.operatorframework.io.metrics.mediatype.v1: metrics+v1
operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4

Expand Down
48 changes: 45 additions & 3 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ import (
"fmt"
"os"
"strings"
"sync/atomic"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
Expand Down Expand Up @@ -62,6 +66,8 @@ func init() {
utilruntime.Must(consolev1.AddToScheme(scheme))

utilruntime.Must(openshiftv1.AddToScheme(scheme))

utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}

Expand Down Expand Up @@ -146,7 +152,15 @@ func main() {
setupLog.Info(fmt.Sprintf("openstack-lightspeed operator watches %s namespace", namespace))
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
cfg := ctrl.GetConfigOrDie()

kclient, err := kubernetes.NewForConfig(cfg)
if err != nil {
setupLog.Error(err, "unable to create kubernetes client")
os.Exit(1)
}

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
Expand Down Expand Up @@ -176,9 +190,14 @@ func main() {
// Defaults for OpenStackLightspeed
apiv1beta1.SetupDefaults()

dynamicWatchCRDs := getDynamicWatchCRDs()

if err = (&controller.OpenStackLightspeedReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Kclient: kclient,
Scheme: mgr.GetScheme(),
Cache: mgr.GetCache(),
DynamicWatchCRD: dynamicWatchCRDs,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "OpenStackLightspeed")
os.Exit(1)
Expand Down Expand Up @@ -216,3 +235,26 @@ func getWatchNamespaces() ([]string, error) {

return strings.Split(ns, ","), nil
}

// getDynamicWatchCRDs returns a map of GroupVersionKind to *atomic.Bool
// representing resources that should be watched dynamically. The watch starts
// once they appear in the cluster for the first time (not required at operator
// start time).
//
// The OpenStackControlPlane GVK is hard-coded here to avoid pulling in the
// openstack-operator/api dependency (which is pinned to an older k8s version).
// The CRD is watched using unstructured types, so the Go type is not needed.
func getDynamicWatchCRDs() map[schema.GroupVersionKind]*atomic.Bool {
return map[schema.GroupVersionKind]*atomic.Bool{
{
Group: "core.openstack.org",
Version: "v1beta1",
Kind: "OpenStackControlPlane",
}: new(atomic.Bool),
{
Group: "keystone.openstack.org",
Version: "v1beta1",
Kind: "KeystoneApplicationCredential",
}: new(atomic.Bool),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ spec:
ActiveOCPRAGVersion contains the OCP version being used for RAG configuration
Will be one of: "4.16", "4.18", "latest", or empty if OCP RAG is disabled
type: string
applicationCredentialSecret:
description: |-
ApplicationCredentialSecret is the name of the current AC secret in the
OpenStack namespace. Tracked for rotation detection.
type: string
conditions:
description: Conditions
items:
Expand Down Expand Up @@ -251,6 +256,11 @@ spec:
for this object.
format: int64
type: integer
openStackReady:
description: |-
OpenStackReady indicates whether an OpenStackControlPlane was detected and
is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
type: boolean
type: object
type: object
served: true
Expand Down
Loading
Loading