From 8dc9f15dd389e2ab161eaffb2fd0ef4eb096d813 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Sat, 28 Feb 2026 06:29:16 -0500 Subject: [PATCH 1/6] Sync dependencies with openstack-operator Align dependencies with openstack-k8s-operators namespace to enable consumption of the OpenStackControlPlane CRD structure. This requires downgrading controller-runtime to 0.19.7, which cascades to other dependency updates. Future work should consider adopting the renovate repository [1] from openstack-k8s-operators for managing downgrade exceptions and replace directives exceptions [2]. [1] https://github.com/openstack-k8s-operators/renovate-config [2] https://github.com/openstack-k8s-operators/openstack-operator/blob/ace3aed5d215767cadadfd0c594df996e6efe889/go.mod#L155 --- cmd/main.go | 1 + go.mod | 104 +++++++++++++++++++++++--------- go.sum | 171 +++++++++++++++++++++++++++++++++++----------------- 3 files changed, 195 insertions(+), 81 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 1f71bf5e..510c36e5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -27,6 +27,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + _ "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" diff --git a/go.mod b/go.mod index 2877c719..26c923ed 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,23 @@ go 1.24.6 require ( github.com/go-logr/logr v1.4.3 - github.com/onsi/ginkgo/v2 v2.27.5 - github.com/onsi/gomega v1.39.0 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0 - github.com/operator-framework/api v0.37.0 - k8s.io/apimachinery v0.34.3 - k8s.io/client-go v0.34.2 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.22.4 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad + github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6 + github.com/operator-framework/api v0.27.0 + k8s.io/apiextensions-apiserver v0.33.2 + k8s.io/apimachinery v0.33.2 + k8s.io/client-go v0.33.2 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.19.7 ) require ( cel.dev/expr v0.24.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect @@ -35,13 +38,17 @@ require ( github.com/go-openapi/swag v0.23.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/btree v1.1.3 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -49,12 +56,37 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v3.9.0+incompatible // indirect + github.com/openstack-k8s-operators/barbican-operator/api v0.6.1-0.20260224084010-bcf584741715 // indirect + github.com/openstack-k8s-operators/cinder-operator/api v0.6.1-0.20260214081335-e97f5c12bd0f // indirect + github.com/openstack-k8s-operators/designate-operator/api v0.6.1-0.20260226141406-72b1c8326c4d // indirect + github.com/openstack-k8s-operators/glance-operator/api v0.6.1-0.20260224155146-43189d7b5f44 // indirect + github.com/openstack-k8s-operators/heat-operator/api v0.6.1-0.20260224124345-771b1609979a // indirect + github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260223151854-36176307c2c1 // indirect + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260226150302-364bc9caac2c // indirect + github.com/openstack-k8s-operators/ironic-operator/api v0.6.1-0.20260223140038-38453280e869 // indirect + github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260226214115-ba279900835f // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260205083029-d03e9df035ef // indirect + github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260224071535-c6fd98c589ad // indirect + github.com/openstack-k8s-operators/manila-operator/api v0.6.1-0.20260214081334-8a8c2b21bb84 // indirect + github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260227092325-cf2df81d2ede // indirect + github.com/openstack-k8s-operators/neutron-operator/api v0.6.1-0.20260223140037-80e7527aa0da // indirect + github.com/openstack-k8s-operators/nova-operator/api v0.6.1-0.20260223172047-681b14478188 // indirect + github.com/openstack-k8s-operators/octavia-operator/api v0.6.1-0.20260223172047-af5395c33450 // indirect + github.com/openstack-k8s-operators/ovn-operator/api v0.6.1-0.20260223141159-1108d9dfaa08 // indirect + github.com/openstack-k8s-operators/placement-operator/api v0.6.1-0.20260223141159-9919225e53fe // indirect + github.com/openstack-k8s-operators/swift-operator/api v0.6.1-0.20260223142633-94dda0b5aea4 // indirect + github.com/openstack-k8s-operators/telemetry-operator/api v0.6.1-0.20260225124101-39a4be8a175d // indirect + github.com/openstack-k8s-operators/watcher-operator/api v0.6.1-0.20260219111939-eaf82eeed7c6 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect + github.com/rabbitmq/cluster-operator/v2 v2.16.0 // indirect + github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.77.1-rhobs1 // indirect + github.com/rhobs/observability-operator v1.0.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.9 // indirect @@ -70,36 +102,54 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.36.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + golang.org/x/tools v0.41.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/apiserver v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/api v0.33.2 // indirect + k8s.io/apiserver v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +// Pin k8s.io versions to match controller-runtime v0.19.7 and lib-common requirements +replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging + +replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging + +replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging + +replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging + +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging + +replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging + +// pin to support rabbitmq 2.16.0 rebase +replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging + +// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.16.0_patches) +replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec diff --git a/go.sum b/go.sum index 264df5b3..0ade5bce 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -19,8 +21,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -54,25 +56,30 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo= +github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -105,14 +112,62 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE= -github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= -github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0 h1:2TD4hi+MLt67jKxJUs2tuBKFMxibrLJQqKqhsTMsHeQ= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0/go.mod h1:rgpcv2tLD+/vudXx/gpIQSTuRpk4GOxHx84xwfvQalM= -github.com/operator-framework/api v0.37.0 h1:2XCMWitBnumtJTqzip6LQKUwpM2pXVlt3gkpdlkbaCE= -github.com/operator-framework/api v0.37.0/go.mod h1:NZs4vB+Jiamyv3pdPDjZtuC4U7KX0eq4z2r5hKY5fUA= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= +github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/openstack-k8s-operators/barbican-operator/api v0.6.1-0.20260224084010-bcf584741715 h1:hNROsRDZp3AEiuybQce8pfeTow1fXUE+2U/s5OaY86g= +github.com/openstack-k8s-operators/barbican-operator/api v0.6.1-0.20260224084010-bcf584741715/go.mod h1:b02h7tVlbdTcXyMrk8HrJuC0TDN+l4halOffKKSSovc= +github.com/openstack-k8s-operators/cinder-operator/api v0.6.1-0.20260214081335-e97f5c12bd0f h1:HC8zeL4goEVhZ40qEpostFgARLIQWd1/sTaMwME4kIo= +github.com/openstack-k8s-operators/cinder-operator/api v0.6.1-0.20260214081335-e97f5c12bd0f/go.mod h1:FATSK+sHg210WxxPnZKHHckqd52z30fpOeldzvpaR0M= +github.com/openstack-k8s-operators/designate-operator/api v0.6.1-0.20260226141406-72b1c8326c4d h1:CpW6pBVLG+rWfG7SuWELI5UG1LO29TcD0H6N3kEIbsE= +github.com/openstack-k8s-operators/designate-operator/api v0.6.1-0.20260226141406-72b1c8326c4d/go.mod h1:mkTeZcZh2iCVt94RiSLr79dscYlt1mDveArM+Ko4LqU= +github.com/openstack-k8s-operators/glance-operator/api v0.6.1-0.20260224155146-43189d7b5f44 h1:p+26/PQupwaGs8NHF5TrVDmdcjCik2Nar+panWK5i2U= +github.com/openstack-k8s-operators/glance-operator/api v0.6.1-0.20260224155146-43189d7b5f44/go.mod h1:mdM4lgeEeNIzKv5zDSpz1ZQYwVyTc5zLahzHonoy4x8= +github.com/openstack-k8s-operators/heat-operator/api v0.6.1-0.20260224124345-771b1609979a h1:FZzn4xAr7Gyg08j9DnRL4fzDxMqPPphHQAsc5GafU7U= +github.com/openstack-k8s-operators/heat-operator/api v0.6.1-0.20260224124345-771b1609979a/go.mod h1:oCU2YPu91BPMRN8a+gKcct3GRXGaIuBj9JAN73nO4dY= +github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260223151854-36176307c2c1 h1:P+yf2BZz5dR5BW8eUHcNK1aQmnHviFZwa0A3D9WPM3g= +github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20260223151854-36176307c2c1/go.mod h1:rbJiGqYQIGdbfCLienv6qrzC2ve8pl8zhim7Atwe6wE= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260226150302-364bc9caac2c h1:JokgOl3nOi+GxYDHsw/Y1zKYFHFOfzLlhMtm39Jig4E= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260226150302-364bc9caac2c/go.mod h1:5hS/cVzc/HURwsbp4MMNwgqAl5bUqKvJdK+4irXeymE= +github.com/openstack-k8s-operators/ironic-operator/api v0.6.1-0.20260223140038-38453280e869 h1:YEBKRN4/vaCzg+jm/7MQwF1R7vsssbgGZIlU4GseF10= +github.com/openstack-k8s-operators/ironic-operator/api v0.6.1-0.20260223140038-38453280e869/go.mod h1:rwSopRg6m3xK0KVu8UZ7w3UXhInil/5W4qJMOoAH3VE= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260226214115-ba279900835f h1:kww9uX/ai4jkYhH4zzVNqSp4ea5rt1YHJfNmZVUeeOo= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260226214115-ba279900835f/go.mod h1:3HPuplIwUZOqIKQ1Cbv8Ciufunof87NO1/3RZOOxINo= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad h1:CCUNIUzCukBYhMJ0aZbQ2/6eF7Lskzzf/CuXLLtEz5k= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad/go.mod h1:+vcGsjqibpMUz3y/g0B5YIXNotlTvQdMB6f92siiwKM= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260205083029-d03e9df035ef h1:IXN2GwCJLirHciDp/sq8oql4F/ScBCKwklTtUYdptS4= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260205083029-d03e9df035ef/go.mod h1:zOX7Y05keiSppIvLabuyh42QHBMhCcoskAtxFRbwXKo= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260224071535-c6fd98c589ad h1:PAQBnG86cQlbgJ8oTaP0glg61BboxcSEHHoTbfLKXyw= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260224071535-c6fd98c589ad/go.mod h1:3loLaPUDQyvbPekylZd9OCLF+EXH2klRI9IeeQhuMcs= +github.com/openstack-k8s-operators/manila-operator/api v0.6.1-0.20260214081334-8a8c2b21bb84 h1:ZMr9smVn/0dbum/HUpNWiASBsecFpZO+WCHGz9l7NNY= +github.com/openstack-k8s-operators/manila-operator/api v0.6.1-0.20260214081334-8a8c2b21bb84/go.mod h1:jxZ5NdCT6rZdrfUVDDZDLmQxYcNBEwjuT+wivZSqerY= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260227092325-cf2df81d2ede h1:EZogjKFvg5SC0ju1c42jccjzLTzdBEUwQBk5loVdsOc= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260227092325-cf2df81d2ede/go.mod h1:bBzAf1Wn8fMkNHRp/9lOQCaw717Wgdvl3aUhLnrPuig= +github.com/openstack-k8s-operators/neutron-operator/api v0.6.1-0.20260223140037-80e7527aa0da h1:24F/dMYdk5sqBSzFWI9m7x8lEVS2I5tWLWWqLhfuJDQ= +github.com/openstack-k8s-operators/neutron-operator/api v0.6.1-0.20260223140037-80e7527aa0da/go.mod h1:wppdogZcqFpqePqElJji7k4gFEMYZvfFvS0qRttHN58= +github.com/openstack-k8s-operators/nova-operator/api v0.6.1-0.20260223172047-681b14478188 h1:ms9ecAqsGVyNTN/6ShW6lI9aUHy5aSSPyh4IHMCO53o= +github.com/openstack-k8s-operators/nova-operator/api v0.6.1-0.20260223172047-681b14478188/go.mod h1:jc2c9B9vPB8eNC64y+wbp1NWfHGf3kaUpqDuolejVDM= +github.com/openstack-k8s-operators/octavia-operator/api v0.6.1-0.20260223172047-af5395c33450 h1:dy+wWCVAUmrhYqekvlOVA8Nwk6fnTcioLbwZOMGMEXk= +github.com/openstack-k8s-operators/octavia-operator/api v0.6.1-0.20260223172047-af5395c33450/go.mod h1:yLFm9wOqXvtXSXRh2S0OQfC1sBKR1duQhxmZVKMwhxA= +github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6 h1:uxVBo9bCcCmSH7Ky/jQ3XZAQhMSazs80gB0Dh40YSL4= +github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6/go.mod h1:cByl4/iuZ/93GiXPldeen38HtrsbwQ4rDm8uWShjZAs= +github.com/openstack-k8s-operators/ovn-operator/api v0.6.1-0.20260223141159-1108d9dfaa08 h1:c0qe8sRmZJZuX/q3Xx1R2I3tKa4+Sjx7MtGa8EDsdvo= +github.com/openstack-k8s-operators/ovn-operator/api v0.6.1-0.20260223141159-1108d9dfaa08/go.mod h1:WHY9jJVYxJJljm0JwO5EwX8u33mg+mZdfkyRxNz0/XI= +github.com/openstack-k8s-operators/placement-operator/api v0.6.1-0.20260223141159-9919225e53fe h1:TOJX0PTCqrNrH0jizyu765kJZOCLgPc90E23kA5auzg= +github.com/openstack-k8s-operators/placement-operator/api v0.6.1-0.20260223141159-9919225e53fe/go.mod h1:FzIV8wfuxxvPHwsxVSiL5t7wV9hu7uuQ2VJQwCvcG5I= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec h1:saovr368HPAKHN0aRPh8h8n9s9dn3d8Frmfua0UYRlc= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec/go.mod h1:Nh2NEePLjovUQof2krTAg4JaAoLacqtPTZQXK6izNfg= +github.com/openstack-k8s-operators/swift-operator/api v0.6.1-0.20260223142633-94dda0b5aea4 h1:3LXEzcK6Am1kTTGeCATWbhgVAKHEyKx9shKIEjn1n90= +github.com/openstack-k8s-operators/swift-operator/api v0.6.1-0.20260223142633-94dda0b5aea4/go.mod h1:Kpak2VcVhlzhyYaExQlsDG5SKRfulZTTWXNXxWlyND0= +github.com/openstack-k8s-operators/telemetry-operator/api v0.6.1-0.20260225124101-39a4be8a175d h1:kReh7Ix3lKnV+/qqG1dZ2oEhjecZO4cX+RBrAz4t2wQ= +github.com/openstack-k8s-operators/telemetry-operator/api v0.6.1-0.20260225124101-39a4be8a175d/go.mod h1:Zeoa1UFw7qDDKZ8az7XTeuUSjK+2uDsbn+L2KT9SRsk= +github.com/openstack-k8s-operators/watcher-operator/api v0.6.1-0.20260219111939-eaf82eeed7c6 h1:I/Qnyl0xZI3I/VZtDfZEfCsqzgNTih80WJ9x4g7d4Gg= +github.com/openstack-k8s-operators/watcher-operator/api v0.6.1-0.20260219111939-eaf82eeed7c6/go.mod h1:XEJp64OcVDbT9G1gHowBBruWcZngWN4C5Z8UgpOoqvk= +github.com/operator-framework/api v0.27.0 h1:OrVaGKZJvbZo58HTv2guz7aURkhVKYhFqZ/6VpifiXI= +github.com/operator-framework/api v0.27.0/go.mod h1:lg2Xx+S8NQWGYlEOvFwQvH46E5EK5IrAIL7HWfAhciM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -126,6 +181,12 @@ github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.77.1-rhobs1 h1:uahWdfwYHqAb28Y1WsltZsiFlIgjxI8HHQxgUdBgPYQ= +github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring v0.77.1-rhobs1/go.mod h1:I9jGubP/TOORi53RNCK7yPH5cP0TAyxzrNirBhHJtoM= +github.com/rhobs/observability-operator v1.0.0 h1:BqV9ZaIt7/52yJTw9xeSiQ1uo7O1A272AzgQms/uo7M= +github.com/rhobs/observability-operator v1.0.0/go.mod h1:OheVnMXdnGj+GPoVA3UDgsJfC2pXIKluLFe+5LJzxC0= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -140,8 +201,6 @@ github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -185,8 +244,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -198,47 +257,47 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= @@ -254,36 +313,40 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.31.14 h1:xYn/S/WFJsksI7dk/5uBRd3Umm/D8W5g7sRnd4csotA= +k8s.io/api v0.31.14/go.mod h1:K8fvRey4z73RAuxBZCma7WtY8WFvkViYhfFLCMT4xgA= +k8s.io/apiextensions-apiserver v0.31.14 h1:1KupD0PyU7CgiT/PiZPSgZhTCL2KGwvXd1ejGcxjEfg= +k8s.io/apiextensions-apiserver v0.31.14/go.mod h1:Odk14fSl/zaciI8DRUSPMSH74UXtz4gfinw7zY7YHvE= +k8s.io/apimachinery v0.31.14 h1:/eMIwjv+GFm6A/sSGlB1NupBU6wTDPhEWsju0Fj69kY= +k8s.io/apimachinery v0.31.14/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.14 h1:DORopWIu2qg7gmVyA9UUGGGmO1Rmnq5Oe+GmsKen3yo= +k8s.io/apiserver v0.31.14/go.mod h1:q81QJuh85u/HN74pdw5Ci4EnrRmCOonZj9FvLwf8DWc= +k8s.io/client-go v0.31.14 h1:d4/G0xfksNIbMWH7ghjzOwC5bTAwQ20gABTjZw7fLlQ= +k8s.io/client-go v0.31.14/go.mod h1:0uRpRB7r5QwtsbxEngZPkbcIVoNdAQAPIcopgiXjhQc= +k8s.io/component-base v0.31.14 h1:VNjBuEMmvlwL4twRlMmlaVmsodIRaNivXcZoAx1/x7Q= +k8s.io/component-base v0.31.14/go.mod h1:9ogYcJBUdB4VQ/OMgInYVRScC9bguXxSEEZPsInY+uM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e h1:UGI9rv1A2cV87NhXr4s+AUBxIuoo/SME/IyJ3b6KztE= +k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= -sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.19.7 h1:DLABZfMr20A+AwCZOHhcbcu+TqBXnJZaVBri9K3EO48= +sigs.k8s.io/controller-runtime v0.19.7/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 2422cdcb64845747b036986150c151f470b12781 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Sat, 28 Feb 2026 04:35:37 -0500 Subject: [PATCH 2/6] Add dynamic CRD watching capability Introduce logic for watching CRDs that may be created after the operator starts but are not expected to exist before installation (e.g., OpenStackControlPlane). This provides a foundation for dynamically watching such CRDs and can be extended to support additional resources. If any dynamically watched CRD is not present in the cluster, reconciliation is requeued every 30 seconds. Once all dynamically watched CRDs are detected, the forced requeue stops. Known limitation: Once a CRD is registered with the controller, it cannot be unregistered. If the CRD is later removed from the cluster, the following error appears in logs: failed to list core.openstack.org/v1beta1, Kind=OpenStackControlPlane: the server could not find the requested resource A potential solution is to restart the controller via os.Exit(0) when CRD removal is detected, but this causes downtime. For now, we accept this log message as a trade-off. --- ...tspeed-operator.clusterserviceversion.yaml | 8 ++ cmd/main.go | 10 +- config/rbac/role.yaml | 8 ++ internal/controller/funcs.go | 83 +++++++++++++- .../openstacklightspeed_controller.go | 102 ++++++++++++++++-- 5 files changed, 203 insertions(+), 8 deletions(-) diff --git a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml index 9b2b7299..62c37723 100644 --- a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml +++ b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml @@ -98,6 +98,14 @@ spec: spec: clusterPermissions: - rules: + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: diff --git a/cmd/main.go b/cmd/main.go index 510c36e5..fd0742ce 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,13 +22,15 @@ 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" - _ "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" + openstackv1beta1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -40,6 +42,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" "github.com/openstack-lightspeed/operator/internal/controller" @@ -57,6 +60,10 @@ func init() { utilruntime.Must(operatorsv1alpha1.AddToScheme(scheme)) utilruntime.Must(apiv1beta1.AddToScheme(scheme)) + + utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) + + utilruntime.Must(openstackv1beta1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -174,6 +181,7 @@ func main() { if err = (&controller.OpenStackLightspeedReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), + Cache: mgr.GetCache(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OpenStackLightspeed") os.Exit(1) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0c785742..e505d752 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,14 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch - apiGroups: - config.openshift.io resources: diff --git a/internal/controller/funcs.go b/internal/controller/funcs.go index 2be67ed9..3ad5c283 100644 --- a/internal/controller/funcs.go +++ b/internal/controller/funcs.go @@ -22,6 +22,9 @@ import ( "math/rand" "strconv" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -29,10 +32,13 @@ import ( _ "embed" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + common_secret "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -172,6 +178,7 @@ func PatchOLSConfig( helper *common_helper.Helper, instance *apiv1beta1.OpenStackLightspeed, olsConfig *uns.Unstructured, + dynamicWatchCRD *DynamicWatchCRD, ) error { // Patch the Providers section providersPatch := []interface{}{ @@ -362,3 +369,77 @@ func OLSConfigPing(ctx context.Context, helper *common_helper.Helper) error { } return nil } + +// GetCRDName returns the name of the CustomResourceDefinition (CRD) for a given +// GroupVersionKind (GVK). The CRD name is constructed as "s." string. +// +// NOTE(lpiwowar): This approach is NOT perfect but it is sufficient for +// OpenStackControlPlane use cases and potentially other CRDs. For broader use, +// we should consider implementing a more robust transformation from GroupVersionKind +// to CRD name. +func GetCRDName(gvk schema.GroupVersionKind) string { + return fmt.Sprintf("%ss.%s", gvk.Kind, gvk.Group) +} + +// IsCRDEstablished checks if a CRD exists and is in "Established" state (ready for use) +// It returns the following values: +// - (true, nil) if the CRD exists and is established +// - (false, nil) if the CRD doesn't exist +// - (false, error) for other errors +func IsCRDEstablished(ctx context.Context, helper *common_helper.Helper, gvk schema.GroupVersionKind) (bool, error) { + crdName := GetCRDName(gvk) + crd := &apiextensionsv1.CustomResourceDefinition{} + err := helper.GetClient().Get(ctx, client.ObjectKey{Name: crdName}, crd) + if err != nil && k8s_errors.IsNotFound(err) { + return false, nil + } else if err != nil { + return false, err + } + + for _, condition := range crd.Status.Conditions { + if condition.Type == apiextensionsv1.Established && condition.Status == apiextensionsv1.ConditionTrue { + return true, nil + } + } + + return false, nil +} + + +// GetObjectGVKs retrieves the GroupVersionKinds for an clientObject using the +// provided runtime.Scheme. It returns the list of GVKs and any error encountered. +func GetObjectGVKs(helper *common_helper.Helper, object client.Object) ([]schema.GroupVersionKind, error) { + gvks, _, err := helper.GetScheme().ObjectKinds(object) + if err != nil { + return nil, err + } + + return gvks, nil +} + +// IsDynamicCRDReady checks whether all GroupVersionKinds (GVKs) associated with +// the given object are being watched and have been observed as ready by the +// dynamic watch. It returns true only if all relevant GVKs are present and marked as seen. +func IsDynamicCRDReady( + helper *common_helper.Helper, + dynamicWatchCRD DynamicWatchCRD, + object client.Object, +) (bool, error) { + gvks, err := GetObjectGVKs(helper, object) + if err != nil { + return false, err + } + + for _, gvk := range gvks { + seen, exists := dynamicWatchCRD[gvk] + if !exists { + return false, fmt.Errorf("GVK %v not found in DynamicWatchCRD map", gvk) + } + + if !seen.Load() { + return false, nil + } + } + + return true, nil +} diff --git a/internal/controller/openstacklightspeed_controller.go b/internal/controller/openstacklightspeed_controller.go index 67c32dd2..bd50266b 100644 --- a/internal/controller/openstacklightspeed_controller.go +++ b/internal/controller/openstacklightspeed_controller.go @@ -19,12 +19,14 @@ package controller import ( "context" "fmt" + "sync/atomic" "time" "github.com/go-logr/logr" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -32,20 +34,32 @@ import ( "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" ) -// OpenStackLightspeedReconciler reconciles a OpenStackLightspeed object +type DynamicWatchCRD map[schema.GroupVersionKind]*atomic.Bool + +// OpenStackLightspeedReconciler is responsible for reconciling OpenStackLightspeed objects. type OpenStackLightspeedReconciler struct { client.Client - Scheme *runtime.Scheme - Kclient kubernetes.Interface + Scheme *runtime.Scheme + Kclient kubernetes.Interface + controller controller.Controller + Cache cache.Cache + + // DynamicWatchCRD contains the list of CRDs that the operator should monitor. + // These CRDs do not need to exist when the operator starts. Once the operator + // detects that a CRD exists, it automatically registers a watch for it using Watch(). + DynamicWatchCRD DynamicWatchCRD } // GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields @@ -64,6 +78,7 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg // +kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions,namespace=openshift-lightspeed,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=operators.coreos.com,resources=installplans,namespace=openshift-lightspeed,verbs=get;list;watch;update;delete // +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch +// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -74,7 +89,7 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/reconcile -func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, e error) { Log := r.GetLogger(ctx) Log.Info("OpenStackLightspeed Reconciling") @@ -99,6 +114,11 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, err } + err = r.WatchDynamicCRD(ctx, helper) + if err != nil { + return ctrl.Result{}, err + } + // Save a copy of the conditions so that we can restore the LastTransitionTime // when a condition's state doesn't change. savedConditions := instance.Status.Conditions.DeepCopy() @@ -130,6 +150,11 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. return } + for _, GVKSeen := range r.DynamicWatchCRD { + if !GVKSeen.Load() { + result.RequeueAfter = 30 * time.Second + } + } }() cl := condition.CreateList( @@ -162,6 +187,19 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. instance.Spec.MaxTokensForResponse = apiv1beta1.OpenStackLightspeedDefaultValues.MaxTokensForResponse } + res, err := r.ReconcileMCPServer(ctx, helper, instance) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + apiv1beta1.OpenStackLightspeedMCPServerReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error(), + )) + + return res, err + } + // Ensure a compatible version of the OpenShift Lightspeed Operator is running in the cluster. // This checks if the correct OLS Operator version is present and installs it if necessary. isOLSOperatorInstalled, err := EnsureOLSOperatorInstalled(ctx, helper, instance) @@ -388,7 +426,7 @@ func (r *OpenStackLightspeedReconciler) SetupWithManager(mgr ctrl.Manager) error Kind: "ClusterVersion", }) - return ctrl.NewControllerManagedBy(mgr). + c, err := ctrl.NewControllerManagedBy(mgr). For(&apiv1beta1.OpenStackLightspeed{}). Owns(&operatorsv1alpha1.ClusterServiceVersion{}). Owns(&operatorsv1alpha1.Subscription{}). @@ -402,7 +440,18 @@ func (r *OpenStackLightspeedReconciler) SetupWithManager(mgr ctrl.Manager) error handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). - Complete(r) + Watches( + &apiextensionsv1.CustomResourceDefinition{}, + handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Build(r) + if err != nil { + return err + } + + r.controller = c + return nil } // NotifyAllOpenStackLightspeeds returns a list of reconcile requests for all OpenStackLightspeed objects. @@ -436,3 +485,44 @@ func (r *OpenStackLightspeedReconciler) NotifyAllOpenStackLightspeeds(ctx contex return requests } + +// WatchDynamicCRD dynamically registers watches for resources whose CRDs are listed +// in r.DynamicWatchCRD. When a target CRD is detected as existing and available in the +// cluster, this method ensures that the controller starts watching resources of that type. +// This enables reconciliation to be triggered whenever those resources are created or modified. +func (r *OpenStackLightspeedReconciler) WatchDynamicCRD( + ctx context.Context, + helper *common_helper.Helper, +) error { + for gvk, seen := range r.DynamicWatchCRD { + crdAvailable, err := IsCRDEstablished(ctx, helper, gvk) + if err != nil { + return err + } + + if !crdAvailable { + seen.Store(false) + continue + } + + GVKUnstructObj := &uns.Unstructured{} + GVKUnstructObj.SetGroupVersionKind(gvk) + err = r.controller.Watch( + source.Kind( + r.Cache, + GVKUnstructObj, + handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, o *uns.Unstructured) []ctrl.Request { + return r.NotifyAllOpenStackLightspeeds(ctx, o) + }), + predicate.TypedResourceVersionChangedPredicate[*uns.Unstructured]{}, + ), + ) + if err != nil { + return fmt.Errorf("failed to set up watch for %s: %w", GetCRDName(gvk), err) + } + + seen.Store(true) + } + + return nil +} From 1794cd4eed2d8276572fa48e7c4bcebe9fda3170 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Sat, 28 Feb 2026 08:19:01 -0500 Subject: [PATCH 3/6] Watch OpenStackControlPlane CRD dynamically Add infrastructure to watch OpenStackControlPlane CRD after it becomes available in the cluster during OpenStack installation. This enables the operator to start monitoring OpenStackControlPlane resources once the CRD is installed, rather than requiring it to exist at operator startup. --- ...tspeed-operator.clusterserviceversion.yaml | 8 +++++ cmd/main.go | 32 +++++++++++++++++-- config/rbac/role.yaml | 8 +++++ .../openstacklightspeed_controller.go | 2 ++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml index 62c37723..d434fdc4 100644 --- a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml +++ b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml @@ -114,6 +114,14 @@ spec: - get - list - watch + - apiGroups: + - core.openstack.org + resources: + - openstackcontrolplanes + verbs: + - get + - list + - watch - apiGroups: - lightspeed.openstack.org resources: diff --git a/cmd/main.go b/cmd/main.go index fd0742ce..905d6e27 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -178,10 +178,17 @@ func main() { // Defaults for OpenStackLightspeed apiv1beta1.SetupDefaults() + dynamicWatchCRDs, err := getDynamicWatchCRDs() + if err != nil { + setupLog.Error(err, "unable to retrieve DynamicWatchCRDs") + os.Exit(1) + } + if err = (&controller.OpenStackLightspeedReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Cache: mgr.GetCache(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Cache: mgr.GetCache(), + DynamicWatchCRD: dynamicWatchCRDs, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OpenStackLightspeed") os.Exit(1) @@ -219,3 +226,22 @@ 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). +func getDynamicWatchCRDs() (map[schema.GroupVersionKind]*atomic.Bool, error) { + var openStackControlPlane openstackv1beta1.OpenStackControlPlane + openStackControlPlaneGVKs, _, err := scheme.ObjectKinds(&openStackControlPlane) + if err != nil { + return nil, err + } + + dynamicWatchCRDs := make(map[schema.GroupVersionKind]*atomic.Bool) + for _, gvk := range openStackControlPlaneGVKs { + dynamicWatchCRDs[gvk] = new(atomic.Bool) + } + + return dynamicWatchCRDs, nil +} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e505d752..ae4553dd 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -20,6 +20,14 @@ rules: - get - list - watch +- apiGroups: + - core.openstack.org + resources: + - openstackcontrolplanes + verbs: + - get + - list + - watch - apiGroups: - lightspeed.openstack.org resources: diff --git a/internal/controller/openstacklightspeed_controller.go b/internal/controller/openstacklightspeed_controller.go index bd50266b..8df9b47e 100644 --- a/internal/controller/openstacklightspeed_controller.go +++ b/internal/controller/openstacklightspeed_controller.go @@ -25,6 +25,7 @@ import ( "github.com/go-logr/logr" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + openstackv1beta1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" @@ -79,6 +80,7 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg // +kubebuilder:rbac:groups=operators.coreos.com,resources=installplans,namespace=openshift-lightspeed,verbs=get;list;watch;update;delete // +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch // +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch +// +kubebuilder:rbac:groups=core.openstack.org,resources=openstackcontrolplanes,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. From c293e34ac4e0817c80b98b3ddd8da8dc48511585 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Tue, 3 Mar 2026 14:37:38 -0500 Subject: [PATCH 4/6] Deploy OpenStack Lightspeed MCP Server Implement ReconcileMCPServer() to deploy the OpenStack Lightspeed MCP Server when an OpenStackControlPlane instance is present. Before deployment, copy required resources to the namespace where the OpenStackLightspeed instance is created: - ConfigMap containing clouds.yaml - Secret containing secrets.yaml - Secret containing the CA bundle Each copied resource includes an openstack.org/checksum annotation with a checksum of its data. This checksum is passed to the deployment to ensure it restarts when secrets are updated. --- api/v1beta1/conditions.go | 15 + api/v1beta1/openstacklightspeed_types.go | 5 + ...tspeed-operator.clusterserviceversion.yaml | 17 + config/manager/manager.yaml | 2 + config/rbac/role.yaml | 13 + go.mod | 2 +- internal/controller/funcs.go | 213 +++++- internal/controller/mcp_server.go | 607 ++++++++++++++++++ internal/controller/mcp_server_config.yaml | 22 + .../openstacklightspeed_controller.go | 18 +- 10 files changed, 908 insertions(+), 6 deletions(-) create mode 100644 internal/controller/mcp_server.go create mode 100644 internal/controller/mcp_server_config.yaml diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index cd96fe95..0a800ede 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -28,6 +28,12 @@ const ( // operational and it can be used by OpenStack Lightspeed operator. OpenShiftLightspeedOperatorReadyCondition condition.Type = "OpenShiftLightspeedOperatorReady" + // OpenStackLightspeedMCPServerReadyCondition is set to True when the reconciliation of the MCP server succeeds. + // This can indicate either that no OpenStack deployment was detected (thus, no MCP server was needed), or that an + // MCP server was successfully deployed because an OpenStack deployment was present. If set to False, it means that + // there was a failure during the MCP server deployment process. + OpenStackLightspeedMCPServerReadyCondition condition.Type = "OpenStackLightspeedMCPServerReady" + // OCPRAGCondition Status=True condition which indicates the OCP RAG version resolution status OCPRAGCondition condition.Type = "OCPRAGReady" ) @@ -63,4 +69,13 @@ const ( // OCPRAGOverrideInvalidMessage OCPRAGOverrideInvalidMessage = "Invalid OCP RAG version override" + + // OpenStackLightspeedMCPServerInitMessage + OpenStackLightspeedMCPServerInitMessage = "MCP server deployment has not resolved" + + // OpenStackLightspeedMCPServerInitNoDeployment + OpenStackLightspeedMCPServerNoDeployment = "MCP server not deployed (no OpenStack instance detected)" + + // OpenStackLightspeedMCPServerDeployed + OpenStackLightspeedMCPServerDeployed = "MCP server is ready" ) diff --git a/api/v1beta1/openstacklightspeed_types.go b/api/v1beta1/openstacklightspeed_types.go index 17237357..e331802c 100644 --- a/api/v1beta1/openstacklightspeed_types.go +++ b/api/v1beta1/openstacklightspeed_types.go @@ -27,6 +27,7 @@ const ( // OpenStackLightspeedContainerImage is the fall-back container image for OpenStackLightspeed OpenStackLightspeedContainerImage = "quay.io/openstack-lightspeed/rag-content:os-docs-2025.2" + MCPServerContainerImage = "quay.io/openstack-lightspeed/rhos-mcps:latest" MaxTokensForResponseDefault = 2048 ) @@ -165,6 +166,7 @@ func (instance OpenStackLightspeed) IsReady() bool { type OpenStackLightspeedDefaults struct { RAGImageURL string + MCPServerImageURL string MaxTokensForResponse int } @@ -176,6 +178,9 @@ func SetupDefaults() { openStackLightspeedDefaults := OpenStackLightspeedDefaults{ RAGImageURL: util.GetEnvVar( "RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT", OpenStackLightspeedContainerImage), + MCPServerImageURL: util.GetEnvVar( + "RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT", MCPServerContainerImage, + ), MaxTokensForResponse: MaxTokensForResponseDefault, } diff --git a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml index d434fdc4..70070cfc 100644 --- a/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml +++ b/bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml @@ -98,6 +98,19 @@ spec: spec: clusterPermissions: - rules: + - apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - apiextensions.k8s.io resources: @@ -230,6 +243,8 @@ spec: fieldPath: metadata.annotations['olm.targetNamespaces'] - name: RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT value: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + - 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: @@ -357,4 +372,6 @@ spec: relatedImages: - image: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 name: openstack-lightspeed-image-url-default + - image: quay.io/openstack-lightspeed/rhos-mcps:latest + name: mcp-server-image-url-default version: 0.0.1 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 788d464a..f093f835 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -74,6 +74,8 @@ spec: fieldPath: metadata.namespace - name: RELATED_IMAGE_OPENSTACK_LIGHTSPEED_IMAGE_URL_DEFAULT value: quay.io/openstack-lightspeed/rag-content:os-docs-2025.2 + - name: RELATED_IMAGE_MCP_SERVER_IMAGE_URL_DEFAULT + value: quay.io/openstack-lightspeed/rhos-mcps:latest securityContext: allowPrivilegeEscalation: false capabilities: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ae4553dd..5e0d007e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,19 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - apiextensions.k8s.io resources: diff --git a/go.mod b/go.mod index 26c923ed..f0ba1e40 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260224071535-c6fd98c589ad github.com/openstack-k8s-operators/openstack-operator/api v0.0.0-20260228013141-e872d7cb0dd6 github.com/operator-framework/api v0.27.0 + k8s.io/api v0.33.2 k8s.io/apiextensions-apiserver v0.33.2 k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.33.2 @@ -123,7 +124,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.2 // indirect k8s.io/apiserver v0.33.2 // indirect k8s.io/component-base v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/internal/controller/funcs.go b/internal/controller/funcs.go index 3ad5c283..5bc00851 100644 --- a/internal/controller/funcs.go +++ b/internal/controller/funcs.go @@ -18,12 +18,19 @@ package controller import ( "context" + "errors" "fmt" "math/rand" "strconv" + "strings" - "k8s.io/apimachinery/pkg/api/errors" + corev1 "k8s.io/api/core/v1" + + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -31,11 +38,13 @@ import ( _ "embed" + appsv1 "k8s.io/api/apps/v1" + + common_cm "github.com/openstack-k8s-operators/lib-common/modules/common/configmap" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" common_secret "github.com/openstack-k8s-operators/lib-common/modules/common/secret" openstackv1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - k8s_errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -47,6 +56,9 @@ const ( // by openstack-operator. OpenStackLightspeedDefaultProvider = "openstack-lightspeed-provider" + // OpenStackLightspeedChecksumAnnotation - is the annotation key used to store the checksum of resources + OpenStackLightspeedChecksumAnnotation = "openstack.org/checksum" + // OpenStackLightspeedOwnerIDLabel - name of a label that contains ID of OpenStackLightspeed instance // that manages the OLSConfig. OpenStackLightspeedOwnerIDLabel = "openstack.org/lightspeed-owner-id" @@ -175,7 +187,9 @@ func BuildRAGConfigs(instance *apiv1beta1.OpenStackLightspeed, ocpVersion string // PatchOLSConfig patches OLSConfig with information from OpenStackLightspeed instance. func PatchOLSConfig( + ctx context.Context, helper *common_helper.Helper, + scheme *runtime.Scheme, instance *apiv1beta1.OpenStackLightspeed, olsConfig *uns.Unstructured, dynamicWatchCRD *DynamicWatchCRD, @@ -289,6 +303,55 @@ func PatchOLSConfig( return err } + available, err := IsDynamicCRDReady( + helper, + *dynamicWatchCRD, + &openstackv1.OpenStackControlPlane{}, + ) + if err != nil { + return err + } + + if available { + OpenShiftMCPServerConfig := map[string]interface{}{ + "name": "openshift-lightspeed-mcp", + "streamableHTTP": map[string]interface{}{ + "url": fmt.Sprintf("%s/openshift", GetMCPServerURL()), + "headers": map[string]interface{}{ + "OCP_TOKEN": "kubernetes", + }, + }, + } + + OpenStackMCPServerConfig := map[string]interface{}{ + "name": "openstack-lightspeed-mcp", + "streamableHTTP": map[string]interface{}{ + "url": fmt.Sprintf("%s/openstack", GetMCPServerURL()), + }, + } + + MCPServersConfig := []interface{}{OpenShiftMCPServerConfig, OpenStackMCPServerConfig} + err = uns.SetNestedSlice(olsConfig.Object, MCPServersConfig, "spec", "mcpServers") + if err != nil { + return err + } + + // Add featureGates to enable "MCPServe" + err = uns.SetNestedSlice(olsConfig.Object, []interface{}{"MCPServer"}, "spec", "featureGates") + if err != nil { + return err + } + } else { + err = uns.SetNestedSlice(olsConfig.Object, []interface{}{}, "spec", "mcpServers") + if err != nil { + return err + } + err = uns.SetNestedSlice(olsConfig.Object, []interface{}{}, "spec", "featureGates") + if err != nil { + return err + } + } + // Add OpenStack finalizers if !controllerutil.AddFinalizer(olsConfig, helper.GetFinalizer()) && instance.Status.Conditions == nil { return fmt.Errorf("cannot add finalizer") @@ -378,7 +441,7 @@ func OLSConfigPing(ctx context.Context, helper *common_helper.Helper) error { // we should consider implementing a more robust transformation from GroupVersionKind // to CRD name. func GetCRDName(gvk schema.GroupVersionKind) string { - return fmt.Sprintf("%ss.%s", gvk.Kind, gvk.Group) + return fmt.Sprintf("%ss.%s", strings.ToLower(gvk.Kind), gvk.Group) } // IsCRDEstablished checks if a CRD exists and is in "Established" state (ready for use) @@ -405,6 +468,150 @@ func IsCRDEstablished(ctx context.Context, helper *common_helper.Helper, gvk sch return false, nil } +// CreateOwnerReference creates an owner reference for the given OpenStackLightspeed instance. +func CreateOwnerReference(instance *apiv1beta1.OpenStackLightspeed) metav1.OwnerReference { + return metav1.OwnerReference{ + APIVersion: instance.APIVersion, + Kind: instance.Kind, + Name: instance.Name, + UID: instance.UID, + Controller: ptr.To(true), + BlockOwnerDeletion: ptr.To(true), + } +} + +// CopyResource copies a resources (supported: Secret, ConfigMap) from one namespace +// to another, applying the specified owner references and computing checksums. +// It performs the following steps: +// +// 1. Fetches the source object identified by its namespace and name. +// 2. Creates or patches the target object in the target namespace with the same data and relevant metadata. +// 3. Sets the given owner references on the copied object. +// 4. Recomputes and sets checksum annotation using the lib-common Hash methods. +// +// Returns the copied object if successful, or an error if the operation fails +// or the type is unsupported. +func CopyResource( + ctx context.Context, + helper *common_helper.Helper, + sourceObject client.Object, + targetObject client.Object, + ownerReference []metav1.OwnerReference, +) (client.Object, error) { + objectKey := types.NamespacedName{ + Namespace: sourceObject.GetNamespace(), + Name: sourceObject.GetName(), + } + + err := helper.GetClient().Get(ctx, objectKey, sourceObject) + if err != nil { + return nil, err + } + + var copyObject client.Object + + switch object := sourceObject.(type) { + case *corev1.Secret: + copySecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetObject.GetName(), + Namespace: targetObject.GetNamespace(), + }, + } + + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), copySecret, func() error { + copySecret.Data = object.Data + copySecret.StringData = object.StringData + copySecret.Type = object.Type + copySecret.SetOwnerReferences(ownerReference) + + checksum, err := common_secret.Hash(copySecret) + if err != nil { + return err + } + + SetChecksumAnnotation(copySecret, checksum) + + return nil + }) + + copyObject = copySecret + case *corev1.ConfigMap: + copyConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetObject.GetName(), + Namespace: targetObject.GetNamespace(), + }, + } + + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), copyConfigMap, func() error { + copyConfigMap.Data = object.Data + copyConfigMap.BinaryData = object.BinaryData + copyConfigMap.SetOwnerReferences(ownerReference) + + checksum, err := common_cm.Hash(copyConfigMap) + if err != nil { + return err + } + + SetChecksumAnnotation(copyConfigMap, checksum) + + return nil + }) + + copyObject = copyConfigMap + default: + return nil, errors.New("cannot copy resource (invalid type)") + } + + if err != nil { + return nil, err + } + + return copyObject, nil +} + +// SetChecksumAnnotation sets or updates the checksum annotation on the provided +// object.This function adds or overwrites only the OpenStackLightspeedChecksumAnnotation +// key, preserving any existing annotations. +func SetChecksumAnnotation(object client.Object, checksum string) { + annotations := object.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + + annotations[OpenStackLightspeedChecksumAnnotation] = checksum + object.SetAnnotations(annotations) +} + +// GetChecksumAnnotation retrieves the checksum annotation from the given object. +// If the annotation is not found, it returns an empty string. +func GetChecksumAnnotation(object client.Object) string { + annotations := object.GetAnnotations() + if annotations == nil { + return "" + } + + checksum, ok := annotations[OpenStackLightspeedChecksumAnnotation] + if !ok { + return "" + } + + return checksum +} + +// GetDeploymentVolumeSection returns a pointer to the Volume in the Deployment's PodSpec +// whose name matches the given volumeSectionName. If no such volume is found, it returns nil. +// This is useful for patching or inspecting a named volume within a Deployment's specification. +func GetDeploymentVolumeSection(deployment appsv1.Deployment, volumeSectionName string) *corev1.Volume { + for i, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Name == volumeSectionName { + return &deployment.Spec.Template.Spec.Volumes[i] + } + } + + return nil +} // GetObjectGVKs retrieves the GroupVersionKinds for an clientObject using the // provided runtime.Scheme. It returns the list of GVKs and any error encountered. diff --git a/internal/controller/mcp_server.go b/internal/controller/mcp_server.go new file mode 100644 index 00000000..88615b5d --- /dev/null +++ b/internal/controller/mcp_server.go @@ -0,0 +1,607 @@ +/* +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 controller + +import ( + "context" + _ "embed" + "errors" + "fmt" + + common_deployment "github.com/openstack-k8s-operators/lib-common/modules/common/deployment" + common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" + apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" +) + +// --------------------------------------------------------------------------- +// Builders: MCP Server Deployment +// --------------------------------------------------------------------------- + +const ( + // CloudsYAMLConfigMapName is the name of the ConfigMap, located in the operator's namespace, + // that contains the clouds.yaml configuration file. + CloudsYAMLConfigMapName string = "openstack-config" + + // SecureYAMLSecretName is the name of the Secret, located in the operator's namespace, + // that contains the secrets.yaml configuration file for the MCP server. + SecureYAMLSecretName string = "openstack-config-secret" + + // CombinedCABundleSecretName is the name of the Secret, located in the operator's namespace, + // that contains the combined TLS CA bundle required by the MCP server. + CombinedCABundleSecretName string = "combined-ca-bundle" + + // MCPConfigYAMLConfigMapName is the name of the ConfigMap, located in the operator's namespace, + // that contains the config.yaml file for the MCP server. + MCPConfigYAMLConfigMapName string = "mcp-config" + + // MCPServerPort - Port on which the OpenStack Lightspeed MCP server listens + MCPServerPort = 8080 + + // MCPServiceName specifies the Service name for the OpenStack Lightspeed MCP server + MCPServiceName = "mcp-server-service" +) + +// MCPServerConfig - stores the config file for the MCP server +// +//go:embed mcp_server_config.yaml +var MCPServerConfig string + +// MCPDeploymentLabels are the labels applied to the MCP server deployment. +var MCPDeploymentLabels = map[string]string{ + "app": "openstack-lightspeed-mcp-server", +} + +// BuildMCPServerDeployment creates a Kubernetes Deployment resource for the MCP server. +// The Deployment expects that the following resources exists: +// - ConfigMap with `CloudsYAMLConfigMapName` name containing clouds.yaml +// - ConfigMap with `MCPConfigYAMLConfigMapName` name containing config.yaml +// - Secret with `SecretsYAMLSecretName` name containing secure.yaml +// - Secret with `CombinedCABundleSecretName` name containing tls-ca-bundle.pem +func BuildMCPServerDeployment( + instance *apiv1beta1.OpenStackLightspeed, +) appsv1.Deployment { + deployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mcp-server", + Namespace: instance.Namespace, + Labels: MCPDeploymentLabels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To(int32(1)), + Selector: &metav1.LabelSelector{ + MatchLabels: MCPDeploymentLabels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: MCPDeploymentLabels, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: SecureYAMLSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: SecureYAMLSecretName, + Items: []corev1.KeyToPath{ + { + Key: "secure.yaml", + Path: "secure.yaml", + }, + }, + }, + }, + }, + { + Name: CloudsYAMLConfigMapName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: CloudsYAMLConfigMapName, + }, + Items: []corev1.KeyToPath{ + { + Key: "clouds.yaml", + Path: "clouds.yaml", + }, + }, + }, + }, + }, + { + Name: CombinedCABundleSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: CombinedCABundleSecretName, + Items: []corev1.KeyToPath{ + { + Key: "tls-ca-bundle.pem", + Path: "tls-ca-bundle.pem", + }, + }, + }, + }, + }, + { + Name: MCPConfigYAMLConfigMapName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: MCPConfigYAMLConfigMapName, + }, + Items: []corev1.KeyToPath{ + { + Key: "config.yaml", + Path: "config.yaml", + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{{ + Name: "mcp-server-container", + Image: apiv1beta1.OpenStackLightspeedDefaultValues.MCPServerImageURL, + VolumeMounts: []corev1.VolumeMount{ + { + Name: SecureYAMLSecretName, + MountPath: "/app/secure.yaml", + SubPath: "secure.yaml", + }, + { + Name: CloudsYAMLConfigMapName, + MountPath: "/app/clouds.yaml", + SubPath: "clouds.yaml", + }, + { + Name: CombinedCABundleSecretName, + MountPath: "/app/tls-ca-bundle.pem", + SubPath: "tls-ca-bundle.pem", + ReadOnly: true, + }, + { + Name: MCPConfigYAMLConfigMapName, + MountPath: "/app/config.yaml", + SubPath: "config.yaml", + }, + }, + }}, + }, + }, + }, + } + + deployment.SetOwnerReferences([]metav1.OwnerReference{ + CreateOwnerReference(instance), + }) + + return deployment +} + +func BuildMCPServerService( + instance *apiv1beta1.OpenStackLightspeed, +) corev1.Service { + service := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: MCPServiceName, + Namespace: instance.Namespace, + Labels: MCPDeploymentLabels, + }, + Spec: corev1.ServiceSpec{ + Selector: MCPDeploymentLabels, + Ports: []corev1.ServicePort{ + { + Name: "http", + Protocol: corev1.ProtocolTCP, + Port: MCPServerPort, + TargetPort: intstr.FromInt(MCPServerPort), + }, + }, + Type: corev1.ServiceTypeClusterIP, + }, + } + + return service +} + +func BuildMCPServerConfigMap( + instance *apiv1beta1.OpenStackLightspeed, +) corev1.ConfigMap { + configMap := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: MCPConfigYAMLConfigMapName, + Namespace: instance.Namespace, + }, + Data: map[string]string{ + "config.yaml": MCPServerConfig, + }, + } + + configMap.SetOwnerReferences([]metav1.OwnerReference{ + CreateOwnerReference(instance), + }) + + return configMap +} + +func GetMCPServerURL() string { + return fmt.Sprintf("http://%s:%d", MCPServiceName, MCPServerPort) +} + +// --------------------------------------------------------------------------- +// End Builders +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// Reconcile: MCP deployment +// --------------------------------------------------------------------------- + +// ReconcileMCPServer performs the reconciliation of the OpenStack Lightspeed +// MCP server. +func (r *OpenStackLightspeedReconciler) ReconcileMCPServer( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (ctrl.Result, error) { + var openStackControlPlaneInstance openstackv1.OpenStackControlPlane + ready, err := IsDynamicCRDReady(helper, r.DynamicWatchCRD, &openStackControlPlaneInstance) + if err != nil { + return ctrl.Result{}, err + } else if !ready { + helper.GetLogger().Info("OpenStackControlPlane CRD not available, deleting MCP server") + return r.ReconcileMCPServerDelete(ctx, helper, instance) + } + + openStackControlPlaneList := &openstackv1.OpenStackControlPlaneList{} + err = r.List(ctx, openStackControlPlaneList) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + switch openStackControlPlaneListLen := len(openStackControlPlaneList.Items); openStackControlPlaneListLen { + + case 0: + helper.GetLogger().Info("No OpenStackControlPlane found, deleting MCP server") + return r.ReconcileMCPServerDelete(ctx, helper, instance) + + case 1: + openStackControlPlaneInstance = openStackControlPlaneList.Items[0] + return r.ReconcileMCPServerCreate(ctx, helper, instance, &openStackControlPlaneInstance) + + default: + err = errors.New("more than one OpenStackControlPlane found") + return ctrl.Result{}, err + } +} + +// ReconcileMCPServerCreate ensures the MCP Server is deployed and configured. +// It copies required resources from the openstack namespace, creates +// the MCP Server ConfigMap, and makes sure the deployment uses the latest resources. +// The OpenStackLightspeed instance status is updated on success; on error, +// the caller must update status. +func (r *OpenStackLightspeedReconciler) ReconcileMCPServerCreate( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, +) (ctrl.Result, error) { + ok, err := CheckRequiredOpenStackControlPlaneFields(helper, openStackControlPlaneInstance) + if err != nil { + return ctrl.Result{}, err + } else if !ok { + return ctrl.Result{}, nil + } + + copiedObjects, err := CopyObjectsToOpenStackLightspeedNamespace( + ctx, + helper, + instance, + openStackControlPlaneInstance, + ) + if err != nil && k8s_errors.IsNotFound(err) { + return ctrl.Result{}, nil + } else if err != nil { + return ctrl.Result{}, err + } + + ConfigYAMLConfigMap := BuildMCPServerConfigMap(instance) + err = helper.GetClient().Create(ctx, &ConfigYAMLConfigMap) + if err != nil && !k8s_errors.IsAlreadyExists(err) { + return ctrl.Result{}, err + } + + err = DeployMCPServer(ctx, helper, instance, openStackControlPlaneInstance, copiedObjects) + if err != nil { + return ctrl.Result{}, err + } + + // A set of resources we want to be owned by the MCP Server deployment. + resourcesToOwn := []client.Object{ + copiedObjects[CloudsYAMLConfigMapName], + copiedObjects[SecureYAMLSecretName], + copiedObjects[CombinedCABundleSecretName], + &ConfigYAMLConfigMap, + } + err = MCPServerDeploymentOwnResources(ctx, helper, instance, resourcesToOwn...) + if err != nil { + return ctrl.Result{}, err + } + + mcpServerDeploymentReady, err := IsMCPServerDeploymentReady(ctx, helper, instance) + if err != nil { + return ctrl.Result{}, err + } + + if mcpServerDeploymentReady { + instance.Status.Conditions.MarkTrue( + apiv1beta1.OpenStackLightspeedMCPServerReadyCondition, + apiv1beta1.OpenStackLightspeedMCPServerDeployed, + ) + } + + return ctrl.Result{}, nil +} + +func (r *OpenStackLightspeedReconciler) ReconcileMCPServerDelete( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (ctrl.Result, error) { + deployment := BuildMCPServerDeployment(instance) + err := helper.GetClient().Delete(ctx, &deployment) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + instance.Status.Conditions.MarkTrue( + apiv1beta1.OpenStackLightspeedMCPServerReadyCondition, + apiv1beta1.OpenStackLightspeedMCPServerNoDeployment, + ) + + return ctrl.Result{}, nil +} + +// CheckRequiredOpenStackControlPlaneFields checks whether the required fields are present +// in the OpenStackControlPlane instance for the MCP server. The following fields are validated: +// - .Spec.OpenStackClient.Template.OpenStackConfigSecret: +// The name of the Secret containing secrets.yaml. This field is required. +// - .Spec.OpenStackClient.Template.OpenStackConfigMap: +// The name of the ConfigMap containing clouds.yaml. This field is required. +// - .Status.TLS.CaBundleSecretName: +// The name of the Secret containing CA certificates. This gets populated during +// the deployment. +func CheckRequiredOpenStackControlPlaneFields( + helper *common_helper.Helper, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, +) (bool, error) { + if openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigSecret == nil { + err := errors.New("OpenStackClient.Template.OpenStackConfigSecret is missing value") + return false, err + } else if openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigMap == nil { + err := errors.New("OpenStackControlPlane.OpenStackClient.Template.OpenStackConfigMap is missing value") + return false, err + } else if openStackControlPlaneInstance.Status.TLS.CaBundleSecretName == "" { + helper.GetLogger().Info("Waiting for OpenStackControlPlaneInstance.Status.TLS.CaBundleSecretName value") + return false, nil + } + + return true, nil +} + +// CopyObjectsToOpenStackLightspeedNamespace copies the required ConfigMaps and +// Secrets from the OpenStackControlPlane's namespace to the OpenStack Lightspeed +// deployment namespace, making these resources available to the MCP server. +// All necessary fields in the OpenStackControlPlane instance must be non-nil +// and valid. It is recommended to call CheckRequiredOpenStackControlPlaneFields +// to ensure this. +func CopyObjectsToOpenStackLightspeedNamespace( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, +) (map[string]client.Object, error) { + objectsToCopy := map[string]client.Object{ + SecureYAMLSecretName: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: *openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigSecret, + Namespace: openStackControlPlaneInstance.Namespace, + }, + }, + CloudsYAMLConfigMapName: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: *openStackControlPlaneInstance.Spec.OpenStackClient.Template.OpenStackConfigMap, + Namespace: openStackControlPlaneInstance.Namespace, + }, + }, + CombinedCABundleSecretName: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: openStackControlPlaneInstance.Status.TLS.CaBundleSecretName, + Namespace: openStackControlPlaneInstance.Namespace, + }, + }, + } + + ownerReference := []metav1.OwnerReference{CreateOwnerReference(instance)} + copiedObjects := make(map[string]client.Object) + var err error + + for resourceName, sourceObject := range objectsToCopy { + targetObject := sourceObject.DeepCopyObject().(client.Object) + targetObject.SetNamespace(instance.Namespace) + targetObject.SetName(resourceName) + + copiedObjects[resourceName], err = CopyResource(ctx, helper, sourceObject, targetObject, ownerReference) + if err != nil && k8s_errors.IsNotFound(err) { + helper.GetLogger().Info( + "Resource %s not found in namespace %s, waiting for it to be created", + sourceObject.GetName(), + sourceObject.GetNamespace(), + ) + return nil, err + } else if err != nil { + return nil, err + } else if copiedObjects[resourceName] == nil { + return nil, errors.New("the internal representatnion of the copied object is nil") + } + } + + return copiedObjects, nil +} + +// DeployMCPServer ensures that the MCP Server Deployment is up-to-date and +// properly configured. It creates or patches the Deployment by injecting the +// names of the necessary copied resources (ConfigMaps and Secrets) from the +// OpenStack related namespace. Additionally, it sets annotations on +// the Deployment with the checksums of these resources. Any change in resource +// content will trigger a restart of the pods, ensuring consistent configuration. +func DeployMCPServer( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + openStackControlPlaneInstance *openstackv1.OpenStackControlPlane, + requiredResources map[string]client.Object, +) error { + deployment := BuildMCPServerDeployment(instance) + _, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), &deployment, func() error { + if deployment.Spec.Template.Annotations == nil { + deployment.Spec.Template.Annotations = make(map[string]string) + } + + var volumeSections []*corev1.Volume + volumeSections = append(volumeSections, GetDeploymentVolumeSection(deployment, CloudsYAMLConfigMapName)) + volumeSections = append(volumeSections, GetDeploymentVolumeSection(deployment, SecureYAMLSecretName)) + volumeSections = append(volumeSections, GetDeploymentVolumeSection(deployment, CombinedCABundleSecretName)) + + var deploymentChecksum string + const checksumPrefixLen = 10 + for _, volumeSection := range volumeSections { + if volumeSection == nil { + return errors.New("missing volume section in MCP Server deployment") + } + + checksum := GetChecksumAnnotation(requiredResources[volumeSection.Name]) + if len(checksum) == 0 { + return fmt.Errorf("missing checksum annotation for: %s", volumeSection.Name) + } + + deploymentChecksum += checksum[:checksumPrefixLen] + } + + // Setting the checksum annotation ensures the MCP server pod is automatically redeployed + // whenever any associated ConfigMap or Secret changes. + deployment.Spec.Template.Annotations[OpenStackLightspeedChecksumAnnotation] = deploymentChecksum + + return nil + }) + if err != nil { + return err + } + + service := BuildMCPServerService(instance) + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), &service, func() error { + return nil + }) + + return err +} + +// GetMCPServerDeployment returns (true, nil) if the MCPServer deployment is ready. +// Otherwise, it returns (false, err) or (false, nil) when the deployment is not found. +func GetMCPServerDeployment( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (*appsv1.Deployment, error) { + deployment := BuildMCPServerDeployment(instance) + + latestDeployment := &appsv1.Deployment{} + err := helper.GetClient().Get(ctx, client.ObjectKey{ + Name: deployment.Name, + Namespace: deployment.Namespace, + }, latestDeployment) + if err != nil && k8s_errors.IsNotFound(err) { + return nil, nil + } else if err != nil { + return nil, err + } + + return latestDeployment, nil +} + +// MCPServerDeploymentOwnResources sets the specified resources in mcpServerResources to be owned +// by the MCP Server Deployment by assigning an OwnerReference to each resource. This ensures that +// these resources are automatically cleaned up when the deployment is deleted. +func MCPServerDeploymentOwnResources( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, + mcpServerResources ...client.Object, +) error { + MCPServerDeployemnt, err := GetMCPServerDeployment(ctx, helper, instance) + if err != nil { + return err + } + + if MCPServerDeployemnt != nil { + deploymentOwnerRefs := []metav1.OwnerReference{ + { + APIVersion: MCPServerDeployemnt.APIVersion, + Kind: MCPServerDeployemnt.Kind, + Name: MCPServerDeployemnt.Name, + UID: MCPServerDeployemnt.UID, + }, + } + for _, obj := range mcpServerResources { + obj.SetOwnerReferences(deploymentOwnerRefs) + err = helper.GetClient().Update(ctx, obj) + if err != nil { + return err + } + } + } + + return nil +} + +// IsMCPServerDeploymentReady returns true if the MCP server deployment is fully +// ready and available for use. +func IsMCPServerDeploymentReady( + ctx context.Context, + helper *common_helper.Helper, + instance *apiv1beta1.OpenStackLightspeed, +) (bool, error) { + MCPServerDeployemnt, err := GetMCPServerDeployment(ctx, helper, instance) + if err != nil { + return false, err + } + + return common_deployment.IsReady(*MCPServerDeployemnt), nil +} + +// --------------------------------------------------------------------------- +// End Reconcile +// --------------------------------------------------------------------------- diff --git a/internal/controller/mcp_server_config.yaml b/internal/controller/mcp_server_config.yaml new file mode 100644 index 00000000..5c59add2 --- /dev/null +++ b/internal/controller/mcp_server_config.yaml @@ -0,0 +1,22 @@ +--- +ip: 0.0.0.0 +port: 8080 +debug: false +workers: 1 +processes_pool_size: 10 + +openstack: + allow_write: false + ca_cert: ./tls-ca-bundle.pem + insecure: false + +openshift: + allow_write: false + insecure: false + +mcp_transport_security: + enable_dns_rebinding_protection: false + allowed_hosts: + - "*:*" + allowed_origins: + - "http://*:*" diff --git a/internal/controller/openstacklightspeed_controller.go b/internal/controller/openstacklightspeed_controller.go index 8df9b47e..1ac3a5ee 100644 --- a/internal/controller/openstacklightspeed_controller.go +++ b/internal/controller/openstacklightspeed_controller.go @@ -25,8 +25,9 @@ import ( "github.com/go-logr/logr" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" - openstackv1beta1 "github.com/openstack-k8s-operators/openstack-operator/api/core/v1beta1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -81,6 +82,8 @@ func (r *OpenStackLightspeedReconciler) GetLogger(ctx context.Context) logr.Logg // +kubebuilder:rbac:groups=config.openshift.io,resources=clusterversions,verbs=get;list;watch // +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch // +kubebuilder:rbac:groups=core.openstack.org,resources=openstackcontrolplanes,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -261,7 +264,7 @@ func (r *OpenStackLightspeedReconciler) Reconcile(ctx context.Context, req ctrl. return fmt.Errorf("OLSConfig is managed by different OpenStackLightspeed instance") } - err = PatchOLSConfig(helper, instance, &olsConfig) + err = PatchOLSConfig(ctx, helper, r.Scheme, instance, &olsConfig, &r.DynamicWatchCRD) if err != nil { return err } @@ -432,6 +435,7 @@ func (r *OpenStackLightspeedReconciler) SetupWithManager(mgr ctrl.Manager) error For(&apiv1beta1.OpenStackLightspeed{}). Owns(&operatorsv1alpha1.ClusterServiceVersion{}). Owns(&operatorsv1alpha1.Subscription{}). + Owns(&appsv1.Deployment{}). Watches( &operatorsv1alpha1.InstallPlan{}, handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), @@ -447,6 +451,16 @@ func (r *OpenStackLightspeedReconciler) SetupWithManager(mgr ctrl.Manager) error handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). + Watches( + &corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.NotifyAllOpenStackLightspeeds), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Build(r) if err != nil { return err From a901e3021f496db39b676bdedbb0091047c9fe9b Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Wed, 4 Mar 2026 12:03:23 -0500 Subject: [PATCH 5/6] Add MCP server ready check to KUTTL tests Include OpenStackLightspeedMCPServerReady status condition in test assertions to validate MCP server deployment status. --- .../assert-openstack-lightspeed-instance.yaml | 3 +++ .../06-assert-openstacklightspeed-update.yaml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml b/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml index 608fcf6c..44753e71 100644 --- a/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml +++ b/test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml @@ -128,6 +128,9 @@ status: status: "True" reason: Ready message: OpenShift Lightspeed operator is ready. + - type: OpenStackLightspeedMCPServerReady + status: "True" + reason: Ready - type: OpenStackLightspeedReady status: "True" reason: Ready diff --git a/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml b/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml index e5946314..20a0a3ca 100644 --- a/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml +++ b/test/kuttl/tests/update-openstacklightspeed/06-assert-openstacklightspeed-update.yaml @@ -13,5 +13,7 @@ status: status: "True" - type: OpenShiftLightspeedOperatorReady status: "True" + - type: OpenStackLightspeedMCPServerReady + status: "True" - type: OpenStackLightspeedReady status: "True" From 26de235bfd46f103be57a10f1980d34a97c7cd6e Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Thu, 5 Mar 2026 09:45:00 -0500 Subject: [PATCH 6/6] Add MCP server KUTTL tests Add two KUTTL tests for MCP server deployment: - mcp-server-present-openstack-deployment: Installs OpenStack Lightspeed and verifies MCP server deployment when an OpenStackControlPlane instance is present, then confirms the MCP server is removed when the OpenStackControlPlane is deleted. - update-mcp-server-present-openstack-deployment: Validates that the MCP server restarts and accesses updated resources (clouds.yaml, secure.yaml, combined-ca-bundle) when any required resource is modified. Both tests mock OpenStack deployment by installing the OpenStackControlPlane CRD and manually creating the OpenStackControlPlane instance. Co-Authored-By: Claude Sonnet 4.5 --- .../assert-mcp-server-deployed.yaml | 51 +++++++++++++++++ .../assert-openstack-namespace-ready.yaml | 7 +++ ...openstackcontrolplane-resources-ready.yaml | 24 ++++++++ .../cleanup-openstack-namespace.yaml | 7 +++ .../cleanup-openstackcontrolplane-crd.yaml | 7 +++ ...eanup-openstackcontrolplane-resources.yaml | 20 +++++++ .../create-openstack-namespace.yaml | 5 ++ ...reate-openstackcontrolplane-resources.yaml | 57 +++++++++++++++++++ .../install-openstackcontrolplane-crd.yaml | 6 ++ .../patch-openstack-status.yaml | 6 ++ .../scripts/assert-checksums-match.yaml | 5 ++ .../check-openstackcontrolplane-crd.yaml | 5 ++ .../common/scripts/validate-checksums.sh | 44 ++++++++++++++ .../validate-openstackcontrolplane-crd.sh | 13 +++++ .../00-install-openstackcontrolplane-crd.yaml | 1 + ...ck-openstackcontrolplane-is-installed.yaml | 1 + .../02-create-openstack-namespace.yaml | 1 + .../03-assert-openstack-namespace-ready.yaml | 1 + ...reate-openstackcontrolplane-resources.yaml | 1 + ...05-patch-openstackcontrolplane-status.yaml | 1 + ...openstackcontrolplane-resources-ready.yaml | 1 + .../07-mock-resources.yaml | 1 + .../08-assert-mock-objects-created.yaml | 1 + ...-create-openstack-lightspeed-instance.yaml | 1 + .../10-assert-mcp-server-deployed.yaml | 1 + .../11-cleanup-openstack-resources.yaml | 1 + .../12-cleanup-openstackcontrolplane-crd.yaml | 1 + .../13-errors-mcp-server-undeployed.yaml | 30 ++++++++++ .../14-assert-olsconfig-mcp-removed.yaml | 8 +++ ...cleanup-openstack-lightspeed-instance.yaml | 1 + .../16-cleanup-mock-objects.yaml | 1 + .../17-cleanup-openstack-namespace.yaml | 1 + .../00-install-openstackcontrolplane-crd.yaml | 1 + ...ck-openstackcontrolplane-is-installed.yaml | 1 + .../02-create-openstack-namespace.yaml | 1 + .../03-assert-openstack-namespace-ready.yaml | 1 + ...reate-openstackcontrolplane-resources.yaml | 1 + ...05-patch-openstackcontrolplane-status.yaml | 1 + .../06-assert-openstack-resources-ready.yaml | 1 + .../07-mock-resources.yaml | 1 + .../08-assert-mock-objects-created.yaml | 1 + ...-create-openstack-lightspeed-instance.yaml | 1 + .../10-assert-mcp-server-deployed.yaml | 1 + .../11-checksum-match.yaml | 1 + .../12-update-openstack-resources.yaml | 44 ++++++++++++++ .../13-assert-mcp-deployment-updated.yaml | 14 +++++ .../14-checksum-match.yaml | 1 + ...cleanup-openstack-lightspeed-instance.yaml | 1 + .../16-cleanup-openstack-resources.yaml | 1 + .../17-cleanup-mock-objects.yaml | 1 + .../18-cleanup-openstack-namespace.yaml | 1 + .../19-cleanup-openstackcontrolplane-crd.yaml | 1 + 52 files changed, 387 insertions(+) create mode 100644 test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml create mode 100644 test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml create mode 100644 test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml create mode 100644 test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml create mode 100644 test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml create mode 100644 test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml create mode 100644 test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml create mode 100644 test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml create mode 100644 test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml create mode 100644 test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml create mode 100644 test/kuttl/common/scripts/assert-checksums-match.yaml create mode 100644 test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml create mode 100755 test/kuttl/common/scripts/validate-checksums.sh create mode 100755 test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml create mode 100644 test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml create mode 100644 test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml create mode 120000 test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml create mode 100644 test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml create mode 100644 test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml create mode 120000 test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml diff --git a/test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml b/test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml new file mode 100644 index 00000000..73ba4525 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/assert-mcp-server-deployed.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server + namespace: openshift-lightspeed +spec: + replicas: 1 +status: + readyReplicas: 1 + availableReplicas: 1 +--- +apiVersion: lightspeed.openstack.org/v1beta1 +kind: OpenStackLightspeed +metadata: + name: openstack-lightspeed + namespace: openshift-lightspeed +status: + conditions: + - type: Ready + status: "True" + reason: Ready + - type: OCPRAGReady + status: "True" + reason: Ready + - type: OpenShiftLightspeedOperatorReady + status: "True" + reason: Ready + - type: OpenStackLightspeedMCPServerReady + status: "True" + reason: Ready + - type: OpenStackLightspeedReady + status: "True" + reason: Ready +--- +apiVersion: ols.openshift.io/v1alpha1 +kind: OLSConfig +metadata: + name: cluster +spec: + mcpServers: + - name: openshift-lightspeed-mcp + streamableHTTP: + url: http://mcp-server-service:8080/openshift + headers: + OCP_TOKEN: kubernetes + - name: openstack-lightspeed-mcp + streamableHTTP: + url: http://mcp-server-service:8080/openstack + featureGates: + - MCPServer diff --git a/test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml b/test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml new file mode 100644 index 00000000..7f44ca8e --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/assert-openstack-namespace-ready.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: openstack +status: + phase: Active diff --git a/test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml b/test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml new file mode 100644 index 00000000..595f3b60 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openstack +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openstack +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openstack +--- +apiVersion: core.openstack.org/v1beta1 +kind: OpenStackControlPlane +metadata: + name: openstack-control-plane + namespace: openstack diff --git a/test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml b/test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml new file mode 100644 index 00000000..4f06a7b7 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/cleanup-openstack-namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: v1 + kind: Namespace + name: openstack diff --git a/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml new file mode 100644 index 00000000..8eb884aa --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + name: openstackcontrolplanes.core.openstack.org diff --git a/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml new file mode 100644 index 00000000..f56b52e3 --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: core.openstack.org/v1beta1 + kind: OpenStackControlPlane + name: openstack-control-plane + namespace: openstack + - apiVersion: v1 + kind: ConfigMap + name: openstack-config + namespace: openstack + - apiVersion: v1 + kind: Secret + name: openstack-config-secret + namespace: openstack + - apiVersion: v1 + kind: Secret + name: combined-ca-bundle + namespace: openstack diff --git a/test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml b/test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml new file mode 100644 index 00000000..fbf1395e --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/create-openstack-namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: openstack diff --git a/test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml b/test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml new file mode 100644 index 00000000..1eaa75cd --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/create-openstackcontrolplane-resources.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openstack +data: + clouds.yaml: | + clouds: + default: + auth: + auth_url: http://keystone.openstack.svc:5000 + project_name: admin + username: admin + user_domain_name: Default + project_domain_name: Default + region_name: regionOne + interface: public + identity_api_version: 3 +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openstack +type: Opaque +stringData: + secure.yaml: | + clouds: + default: + auth: + password: "test-password-12345" +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openstack +type: Opaque +stringData: + tls-ca-bundle.pem: | + -----BEGIN CERTIFICATE----- + cert value + -----END CERTIFICATE----- +--- +apiVersion: core.openstack.org/v1beta1 +kind: OpenStackControlPlane +metadata: + name: openstack-control-plane + namespace: openstack +spec: + secret: openstack-admin-secret + storageClass: local-storage + openstackclient: + template: + openStackConfigMap: openstack-config + openStackConfigSecret: openstack-config-secret diff --git a/test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml b/test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml new file mode 100644 index 00000000..b1a42b6a --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/install-openstackcontrolplane-crd.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl apply --server-side -f https://raw.githubusercontent.com/openstack-k8s-operators/openstack-operator/main/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml + ignoreFailure: false diff --git a/test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml b/test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml new file mode 100644 index 00000000..53e5571f --- /dev/null +++ b/test/kuttl/common/mcp-server-tests/patch-openstack-status.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl patch openstackcontrolplane openstack-control-plane -n openstack --type=merge --subresource=status --patch '{"status":{"tls":{"caBundleSecretName":"combined-ca-bundle"}}}' + ignoreFailure: false diff --git a/test/kuttl/common/scripts/assert-checksums-match.yaml b/test/kuttl/common/scripts/assert-checksums-match.yaml new file mode 100644 index 00000000..45c8bb52 --- /dev/null +++ b/test/kuttl/common/scripts/assert-checksums-match.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: bash ../../common/scripts/validate-checksums.sh + ignoreFailure: false diff --git a/test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml b/test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml new file mode 100644 index 00000000..fc661d7c --- /dev/null +++ b/test/kuttl/common/scripts/check-openstackcontrolplane-crd.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: bash ../../common/scripts/validate-openstackcontrolplane-crd.sh + ignoreFailure: false diff --git a/test/kuttl/common/scripts/validate-checksums.sh b/test/kuttl/common/scripts/validate-checksums.sh new file mode 100755 index 00000000..f7628f54 --- /dev/null +++ b/test/kuttl/common/scripts/validate-checksums.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -e +echo "Validating checksum annotations match between resources and deployment..." + +# Get checksum from ConfigMap +CM_CHECKSUM=$(kubectl get configmap openstack-config \ + -n openshift-lightspeed \ + -o jsonpath='{.metadata.annotations.openstack\.org/checksum}') + +# Get checksum from Secret (openstack-config-secret) +SECRET_CHECKSUM=$(kubectl get secret openstack-config-secret \ + -n openshift-lightspeed \ + -o jsonpath='{.metadata.annotations.openstack\.org/checksum}') + +# Get checksum from CA Bundle Secret +CA_CHECKSUM=$(kubectl get secret combined-ca-bundle \ + -n openshift-lightspeed \ + -o jsonpath='{.metadata.annotations.openstack\.org/checksum}') + +# Get checksum from Deployment pod template for secrets.yaml +DEP_CHECKSUM=$(kubectl get deployment mcp-server \ + -n openshift-lightspeed \ + -o jsonpath='{.spec.template.metadata.annotations.openstack\.org/checksum}') + + +# Validate all checksums match +FAILED=0 +COMBINED_CHECKSUM="${CM_CHECKSUM:0:10}${SECRET_CHECKSUM:0:10}${CA_CHECKSUM:0:10}" +if [ "$COMBINED_CHECKSUM" = "$DEP_CHECKSUM" ]; then + echo "✓ Combined checksum matches: $COMBINED_CHECKSUM" +else + echo "✗ Combined checksum mismatch!" + echo " Combined checksum: $COMBINED_CHECKSUM" + echo " Deployment checksum: $DEP_CHECKSUM" + FAILED=1 +fi + +if [ $FAILED -eq 1 ]; then + echo "Checksum validation failed!" + exit 1 +fi + +echo "All checksums match successfully!" +exit 0 diff --git a/test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh b/test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh new file mode 100755 index 00000000..ff2961fd --- /dev/null +++ b/test/kuttl/common/scripts/validate-openstackcontrolplane-crd.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +for i in {1..5}; do + echo "Attempt $i: Waiting for CRD to be established..." + if oc wait --for=condition=Established crd/openstackcontrolplanes.core.openstack.org --timeout=20s; then + echo "CRD is Established." + exit 0 + fi + echo "Status field not ready yet, retrying in 3s..." + sleep 3 +done + +exit 1 diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..bcf5580f --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/install-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml new file mode 120000 index 00000000..1bbaea32 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml @@ -0,0 +1 @@ +../../common/scripts/check-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml new file mode 120000 index 00000000..a785ea07 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml new file mode 120000 index 00000000..ce5d01a3 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstack-namespace-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml new file mode 120000 index 00000000..fe28f159 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml new file mode 120000 index 00000000..05a13e67 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/patch-openstack-status.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml new file mode 120000 index 00000000..8f270429 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/06-assert-openstackcontrolplane-resources-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml new file mode 120000 index 00000000..8235a1fd --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/07-mock-resources.yaml @@ -0,0 +1 @@ +../../common/mock-objects/mock-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml new file mode 120000 index 00000000..07f977a1 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml @@ -0,0 +1 @@ +../../common/mock-objects/assert-mock-objects-created.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..d08d5827 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/create-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml new file mode 120000 index 00000000..8a870474 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-mcp-server-deployed.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml new file mode 120000 index 00000000..5a2e65c9 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/11-cleanup-openstack-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..50cb4312 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/12-cleanup-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml new file mode 100644 index 00000000..12ebfd14 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/13-errors-mcp-server-undeployed.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openshift-lightspeed +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mcp-server-config + namespace: openshift-lightspeed diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml new file mode 100644 index 00000000..691a0b88 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/14-assert-olsconfig-mcp-removed.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: ols.openshift.io/v1alpha1 +kind: OLSConfig +metadata: + name: cluster +spec: + mcpServers: [] + featureGates: [] diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..6b2075b0 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/cleanup-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml new file mode 120000 index 00000000..410c9278 --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/16-cleanup-mock-objects.yaml @@ -0,0 +1 @@ +../../common/mock-objects/cleanup-mock-objects.yaml \ No newline at end of file diff --git a/test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml b/test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml new file mode 120000 index 00000000..f2fe34db --- /dev/null +++ b/test/kuttl/tests/mcp-server-present-openstack-deployment/17-cleanup-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..bcf5580f --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/00-install-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/install-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml new file mode 120000 index 00000000..1bbaea32 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/01-check-openstackcontrolplane-is-installed.yaml @@ -0,0 +1 @@ +../../common/scripts/check-openstackcontrolplane-crd.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml new file mode 120000 index 00000000..a785ea07 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/02-create-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml new file mode 120000 index 00000000..ce5d01a3 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/03-assert-openstack-namespace-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstack-namespace-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml new file mode 120000 index 00000000..fe28f159 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/04-create-openstackcontrolplane-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/create-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml new file mode 120000 index 00000000..05a13e67 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/05-patch-openstackcontrolplane-status.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/patch-openstack-status.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml new file mode 120000 index 00000000..8f270429 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/06-assert-openstack-resources-ready.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-openstackcontrolplane-resources-ready.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml new file mode 120000 index 00000000..8235a1fd --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/07-mock-resources.yaml @@ -0,0 +1 @@ +../../common/mock-objects/mock-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml new file mode 120000 index 00000000..07f977a1 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/08-assert-mock-objects-created.yaml @@ -0,0 +1 @@ +../../common/mock-objects/assert-mock-objects-created.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..d08d5827 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/09-create-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/create-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml new file mode 120000 index 00000000..8a870474 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/10-assert-mcp-server-deployed.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/assert-mcp-server-deployed.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml new file mode 120000 index 00000000..6991f16f --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/11-checksum-match.yaml @@ -0,0 +1 @@ +../../common/scripts/assert-checksums-match.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml new file mode 100644 index 00000000..f0dfa24e --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/12-update-openstack-resources.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: openstack-config + namespace: openstack +data: + clouds.yaml: | + clouds: + default: + auth: + auth_url: http://keystone-updated.openstack.svc:5000 + project_name: admin-updated + username: admin + user_domain_name: Default + project_domain_name: Default + region_name: regionTwo + interface: public + identity_api_version: 3 +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-config-secret + namespace: openstack +type: Opaque +stringData: + secure.yaml: | + clouds: + default: + auth: + password: "test-password-67890-UPDATED" +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + namespace: openstack +type: Opaque +stringData: + tls-ca-bundle.pem: | + -----BEGIN CERTIFICATE----- + cert update + -----END CERTIFICATE----- diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml new file mode 100644 index 00000000..b466569d --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/13-assert-mcp-deployment-updated.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server + namespace: openshift-lightspeed + generation: 2 +spec: + replicas: 1 +status: + observedGeneration: 2 + updatedReplicas: 1 + readyReplicas: 1 + availableReplicas: 1 diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml new file mode 120000 index 00000000..6991f16f --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/14-checksum-match.yaml @@ -0,0 +1 @@ +../../common/scripts/assert-checksums-match.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml new file mode 120000 index 00000000..6b2075b0 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/15-cleanup-openstack-lightspeed-instance.yaml @@ -0,0 +1 @@ +../../common/openstack-lightspeed-instance/cleanup-openstack-lightspeed-instance.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml new file mode 120000 index 00000000..5a2e65c9 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/16-cleanup-openstack-resources.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-resources.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml new file mode 120000 index 00000000..410c9278 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/17-cleanup-mock-objects.yaml @@ -0,0 +1 @@ +../../common/mock-objects/cleanup-mock-objects.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml new file mode 120000 index 00000000..f2fe34db --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/18-cleanup-openstack-namespace.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstack-namespace.yaml \ No newline at end of file diff --git a/test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml new file mode 120000 index 00000000..50cb4312 --- /dev/null +++ b/test/kuttl/tests/update-mcp-server-present-openstack-deployment/19-cleanup-openstackcontrolplane-crd.yaml @@ -0,0 +1 @@ +../../common/mcp-server-tests/cleanup-openstackcontrolplane-crd.yaml \ No newline at end of file