Lcore mcps#19
Conversation
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: Akrog The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
@Akrog: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
/retest |
With this commit we start deploying the MCP server as a sidecar container of the lightspeed-service container. The MCP server deployed is the one from our own rhos-mcps repository [1]. On installation the `openshift-cli` tool is enabled when OpenStack Lightspeed is configured, and the `openstack-cli` tool is enabled when the `OpenStackControlPlane` is ready. From a security perspective since we are deploying the MCP tools in the pod's network namespace not using TLS is not a real security risk. [1]: https://github.com/openstack-lightspeed/rhos-mcps Jira: OSPRH-27075 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Job govulncheck is failing with the following error. ``` Vulnerability openstack-k8s-operators#1: GO-2026-5970 Infinite loop on invalid input in golang.org/x/text More info: https://pkg.go.dev/vuln/GO-2026-5970 Module: golang.org/x/text Found in: golang.org/x/text@v0.37.0 Fixed in: golang.org/x/text@v0.39.0 ``` This commit updates the text dependency and transitive version bumps of other dependencies.
For now we don't want to automatically deploy the MCP tools, because: - Releasing the rhos-mcps image may be done at a later time. - This is an experimental feature right now, so we want it disabled by default. The name of the feature flag is `rhos_mcps` as shown in the config ample. Jira: OSPRH-27075 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Initial implementation of the MCP deployment uses the credentials from the `openstackclient` pod, which means that we are not the owners of that secret, just the copy we make in the `openstack-lightspeed` namespace, so those credentials could be removed/deleted and that would break our `openstack-cli` tool. In this patch we change the credentials and we leverage the `KeystoneApplicationCredential` CR to get our own credentials. Credential Rotation is handled by the code as well. Jira: OSPRH-27075 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I have added another commit to fix the vulnerability reported by the CI job |
lpiwowar
left a comment
There was a problem hiding this comment.
Overall LGTM!:) Thank you!
I wrote a comment for anything that crossed my mind not everything is blocking.
I personally would like to resolve these two things somehow (either by an agreement or by change in the code) before we merge:
- MCP image handling in downstream and
pullspecs_repleceatron.sh(setting to empty string) - The usage of
RawClient()and question whether we should not addopenstackintoWATCH_NAMESPACES
| OpenStackLightspeedMCPServerWaitingAC = "Waiting for application credential secret" | ||
|
|
||
| // OpenStackLightspeedMCPServerDisabledMessage | ||
| OpenStackLightspeedMCPServerDisabledMessage = "RHOS MCP server is disabled (rhos_mcps feature flag not set)" |
There was a problem hiding this comment.
question (non-blocking): Shouldn't we use "RHOSO MCP" server or "OpenStack Lightspeed MCP server"? I feel like the "RHOS" naming is something we do not use much.
| 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" |
There was a problem hiding this comment.
question (blocking): Do we know how we are going to deal with the image downstream? Related to the conversation we already had. Suggested solution was to use empty string downstream for the RELATED_IMAGE_MCP_* environment variable. The issue is that the the pullspecs_repleceatron.sh ends with an error when any RELATED_IMAGE_* env var is an empty string. In my opinion before we merge we should:
- Update
pullspecs_repleceatron.shto make it work with empty string forRELATED_IMAGE_*env vars. - Check with somebody an empty string for
RELATED_IMAGE_*will be accepted well downstream.
There was a problem hiding this comment.
Perhaps we can discuss this with Miguel ? He seems pretty open to suggestions
| // must be consistent within modules and service operators | ||
| replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e | ||
|
|
||
| require k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect |
There was a problem hiding this comment.
nit (non-blocking): Why this can not be in the require block below together with all the other // indirect dependencies?
|
|
||
| // getSecretResourceVersion retrieves the resource version of a Secret. | ||
| func getSecretResourceVersion(ctx context.Context, h *common_helper.Helper, name string, namespace string) (string, error) { | ||
| rawClient, err := getRawClient(h) |
There was a problem hiding this comment.
suggestion (non-blocking): I think we should really consider adding openstack namespace into namespaces that are watched by default. That way it is obvious what namespaces the operator watches and we do need to bypass the WATCH_NAMESPACES with raw client. I think it would be a step in the right direction. If for whatever reason the OpenStack controlplane lives in a different namespace then the customer can control that with OperatorGroup.
There was a problem hiding this comment.
I like this suggestion as I am a bit worry about the whole cross-namespacing aspects of things. I think that we could have it namespaced for now even if it's a tech debt for multi-namespaces (or non default namespaces) of the OpenStack depoyment until we have something else figure out. More like, "security first" approach for this first implementation.
| set -euo pipefail | ||
| READY=$(oc get openstacklightspeed openstack-lightspeed \ | ||
| -n openstack-lightspeed \ | ||
| -o jsonpath='{.status.openStackReady}' 2>/dev/null || true) | ||
| if [ "$READY" = "true" ]; then | ||
| echo "ERROR: openStackReady is still true" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
suggestion (non-blocking): This can probably be:
apiVersion: lightspeed.openstack.org/v1beta1
kind: OpenStackLightspeed
metadata:
name: openstack-lightspeed
namespace: openstack-lightspeed
status:
conditions:
- type: Ready
status: "True"| MCP_DATA=$(oc get configmap mcp-config -n openstack-lightspeed \ | ||
| -o jsonpath='{.data.config\.yaml}') | ||
| ENABLED=$(echo "$MCP_DATA" | grep -A1 "openstack:" | grep "enabled:" | tr -d ' ') | ||
| if [ "$ENABLED" != "enabled:false" ]; then | ||
| echo "ERROR: MCP config does not have openstack disabled" | ||
| echo "$MCP_DATA" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
suggestion (non-blocking): If we really need to use the script (which seems to be the valid case here) I think we should move the repeating snippets into ../common/shared_funcs.go where they can be easily reused by the individual test steps:
source ../common/shared_funcs.go
ssert_mcp_openstack_enabled true / falseI think we can find some other good candidates for this.
| case 0: | ||
| return nil, errors.New("no cloud entry found in clouds.yaml") | ||
| case 1: | ||
| for name = range clouds.Clouds { |
There was a problem hiding this comment.
issue (blocking): This does nothing.
| } | ||
|
|
||
| // Delete AC CR | ||
| rawClient, err := getRawClient(helper) |
There was a problem hiding this comment.
suggestion (non-blocking): Already mentioned. I think we should ensure that our operator properly watches the openstack namespace and be transparent about it.
|
|
||
| // cleanupMCPResources removes MCP server resources when the rhos_mcps feature | ||
| // flag is disabled. | ||
| func (r *OpenStackLightspeedReconciler) cleanupMCPResources( |
There was a problem hiding this comment.
suggestion (non-blocking): This can be simplified e.g. like this:
func (r *OpenStackLightspeedReconciler) cleanupMCPResources(
ctx context.Context,
helper *common_helper.Helper,
instance *apiv1beta1.OpenStackLightspeed,
) error {
ns := instance.Namespace
resources := []client.Object{
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: MCPConfigYAMLConfigMapName, Namespace: ns}},
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: CloudsYAMLConfigMapName, Namespace: ns}},
&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: SecureYAMLSecretName, Namespace: ns}},
&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: CombinedCABundleSecretName, Namespace: ns}},
}
for _, obj := range resources {
if err := helper.GetClient().Delete(ctx, obj); err != nil && !k8s_errors.IsNotFound(err) {
return fmt.Errorf("failed to delete %s %s: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err)
}
}
if err := r.reconcileDeleteOpenStackResources(ctx, helper, instance); err != nil {
return fmt.Errorf("failed to clean up OpenStack resources during RHOS MCP disable: %w", err)
}
helper.GetLogger().Info("RHOS MCP resources cleaned up")
return nil
}
umago
left a comment
There was a problem hiding this comment.
Thanks Gorka! Overall I think it looks solid, I'm a bit worried about the wide scope of some aspects of this change (commented inline). I wonder if we could go a bit more "namespaced-scoped" from the start rather than having to tight up things later.
| 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" |
There was a problem hiding this comment.
Perhaps we can discuss this with Miguel ? He seems pretty open to suggestions
| return err | ||
| } | ||
|
|
||
| userID, err := osClient.CreateUser(log, openstack_lib.User{ |
There was a problem hiding this comment.
Not sure if this is an issue or not, but we are calling CreateUser on every reconciliation. Does keystone handles it properly ?
| verbs: | ||
| - create | ||
| - get | ||
| - update |
There was a problem hiding this comment.
This comes from the old coderabbit review. I don't know if there's a way around it, maybe we can assume openstack is always depoyed in the "openstack" namespace and scope it only to that namespace as a compromise until we have a better way of doing it ?
I worry that this rule allows the operator to read and write on all Secrets cluster wide without any namespace restriction. it does sounds a bit too broad. I wonder if something like this can/will be flagged in the security assessments we've been doing recently.
| oldSecret, err := kclient.CoreV1().Secrets(oscpNamespace).Get(ctx, prevSecret, metav1.GetOptions{}) | ||
| if err == nil && controllerutil.RemoveFinalizer(oldSecret, LightspeedACFinalizerName) { | ||
| if _, err := kclient.CoreV1().Secrets(oscpNamespace).Update(ctx, oldSecret, metav1.UpdateOptions{}); err != nil { | ||
| helper.GetLogger().Info("Failed to remove finalizer from old AC secret", "secret", prevSecret, "error", err) |
There was a problem hiding this comment.
nit: should this be an error log instead ? (as L540 above)
| ctx, instance.Status.ApplicationCredentialSecret, metav1.GetOptions{}) | ||
| if err == nil && controllerutil.RemoveFinalizer(acSecret, LightspeedACFinalizerName) { | ||
| if _, err := helper.GetKClient().CoreV1().Secrets(oscpNS).Update(ctx, acSecret, metav1.UpdateOptions{}); err != nil { | ||
| log.Info("Failed to remove finalizer from AC secret", "error", err) |
There was a problem hiding this comment.
nit: this should be n error instad of info
| apiv1beta1.OpenStackLightspeedMCPServerDeployed, | ||
| ) | ||
| } else { | ||
| instance.Status.Conditions.MarkTrue( |
There was a problem hiding this comment.
Sounds like this should be False instead as it is still waiting for OpenStack to be ready (OpenStackLightspeedMCPServerWaitingOpenStack L337 below)
| }, | ||
| ImagePullPolicy: corev1.PullIfNotPresent, | ||
| } | ||
| containers = append(containers, mcpContainer) |
There was a problem hiding this comment.
Nice to add: Some liveness/readiness check for the MCP container ?
|
|
||
| // getSecretResourceVersion retrieves the resource version of a Secret. | ||
| func getSecretResourceVersion(ctx context.Context, h *common_helper.Helper, name string, namespace string) (string, error) { | ||
| rawClient, err := getRawClient(h) |
There was a problem hiding this comment.
I like this suggestion as I am a bit worry about the whole cross-namespacing aspects of things. I think that we could have it namespaced for now even if it's a tech debt for multi-namespaces (or non default namespaces) of the OpenStack depoyment until we have something else figure out. More like, "security first" approach for this first implementation.
This PR adds support for the rhos-mcps image that provides openstack-cli and openshift-cli tools.
The MCP server is deployed as a sidecar container with the openshift-cli tool always enabled and the openstack-cli tool only enabled once the OpenStackControlPlane is ready.
By default the rhos-mcps is not deployed and requires the rhos_mcps feature flag to be enabled.