From 87043277c2bd002c36be71e180582282e398c4ca Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Wed, 13 Sep 2023 17:15:59 +0200 Subject: [PATCH 1/3] [TLS] TLS for public endpoints terminated at a route Changes openstacklient * CRD to allows to pass in CA secret * mounts the ca bundle under /etc/pki Adds CRD parameters to configure TLS for public and internal TLS. * per default self signed root CA + issuer get created for public and internal certs * via the apiOverride.TLS of a service, a secret with cert, key and CA cert can be provided to use instead of the default self signed * user can provide a CA secret for certs to be added to the combined CA secret the openstack-operator creates to pass into services / openstackclient * refactors the current route create for followup on TLS-E to create certs for each service endpoint. * when TLS for public endpoint is enabled (default) a Cert for the route gets automatically created and added to the route CR. * the openstack-operator creates a full tls-ca-bundle.pem using the operator image ca-bundle as base and adds the public, internal and user provided CAs to it. This allows to mount a full tls-ca-bundle.pem into the deployment pod and don't have to rely on kolla to run update-ca-trust which requires container to run as root. Jira: OSP-26299 Depends-On: https://github.com/openstack-k8s-operators/lib-common/pull/351 Depends-On: https://github.com/openstack-k8s-operators/keystone-operator/pull/318 Depends-On: https://github.com/openstack-k8s-operators/tcib/pull/82 --- ...client.openstack.org_openstackclients.yaml | 4 + ....openstack.org_openstackcontrolplanes.yaml | 117 +++++ apis/client/v1beta1/openstackclient_types.go | 14 +- apis/client/v1beta1/zz_generated.deepcopy.go | 11 + apis/core/v1beta1/conditions.go | 15 + .../v1beta1/openstackcontrolplane_types.go | 62 ++- apis/core/v1beta1/zz_generated.deepcopy.go | 77 +++ apis/go.mod | 8 +- apis/go.sum | 15 +- ...client.openstack.org_openstackclients.yaml | 4 + ....openstack.org_openstackcontrolplanes.yaml | 117 +++++ ...nstack-operator.clusterserviceversion.yaml | 63 +++ config/rbac/role.yaml | 28 ++ .../core_v1beta1_openstackcontrolplane.yaml | 6 + ...controlplane_galera_network_isolation.yaml | 6 + ...ne_galera_network_isolation_3replicas.yaml | 6 + ...enstackcontrolplane_network_isolation.yaml | 6 + .../client/openstackclient_controller.go | 51 +- .../core/openstackcontrolplane_controller.go | 16 +- go.mod | 13 +- go.sum | 21 +- main.go | 2 + pkg/openstack/ca.go | 437 ++++++++++++++++++ pkg/openstack/cinder.go | 4 +- pkg/openstack/common.go | 417 ++++++++++++----- pkg/openstack/glance.go | 4 +- pkg/openstack/heat.go | 8 +- pkg/openstack/horizon.go | 4 +- pkg/openstack/ironic.go | 8 +- pkg/openstack/keystone.go | 4 +- pkg/openstack/manila.go | 4 +- pkg/openstack/neutron.go | 4 +- pkg/openstack/nova.go | 8 +- pkg/openstack/octavia.go | 4 +- pkg/openstack/openstackclient.go | 12 +- pkg/openstack/placement.go | 4 +- pkg/openstack/swift.go | 4 +- pkg/openstackclient/funcs.go | 114 +++-- 38 files changed, 1457 insertions(+), 245 deletions(-) create mode 100644 pkg/openstack/ca.go diff --git a/apis/bases/client.openstack.org_openstackclients.yaml b/apis/bases/client.openstack.org_openstackclients.yaml index 95f3e38c2..fb0d6a218 100644 --- a/apis/bases/client.openstack.org_openstackclients.yaml +++ b/apis/bases/client.openstack.org_openstackclients.yaml @@ -36,6 +36,8 @@ spec: type: object spec: properties: + caSecretName: + type: string containerImage: type: string nodeSelector: @@ -43,8 +45,10 @@ spec: type: string type: object openStackConfigMap: + default: openstack-config type: string openStackConfigSecret: + default: openstack-config-secret type: string required: - containerImage diff --git a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml index a8fcca8f6..253ac8935 100644 --- a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -205,6 +205,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -2348,6 +2353,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -3537,6 +3547,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cnfAPIOverride: properties: @@ -3638,6 +3653,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4089,6 +4109,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4316,6 +4341,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4420,6 +4450,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -5034,6 +5069,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -5308,6 +5348,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -6534,6 +6579,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -7557,6 +7607,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cellOverride: additionalProperties: @@ -7661,6 +7716,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object type: object type: object @@ -8470,6 +8530,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -8969,6 +9034,30 @@ spec: - secret type: object type: object + openstackclient: + properties: + template: + properties: + caSecretName: + type: string + containerImage: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + openStackConfigMap: + default: openstack-config + type: string + openStackConfigSecret: + default: openstack-config-secret + type: string + required: + - containerImage + - openStackConfigMap + - openStackConfigSecret + type: object + type: object ovn: properties: enabled: @@ -9297,6 +9386,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -13620,6 +13714,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -13778,6 +13877,24 @@ spec: - swiftStorage type: object type: object + tls: + default: + endpoint: + internal: + enabled: false + public: + enabled: true + properties: + caSecretName: + type: string + endpoint: + additionalProperties: + properties: + enabled: + type: boolean + type: object + type: object + type: object required: - secret - storageClass diff --git a/apis/client/v1beta1/openstackclient_types.go b/apis/client/v1beta1/openstackclient_types.go index d12ff693d..dc3e4724e 100644 --- a/apis/client/v1beta1/openstackclient_types.go +++ b/apis/client/v1beta1/openstackclient_types.go @@ -15,6 +15,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -31,16 +32,25 @@ type OpenStackClientSpec struct { // +kubebuilder:validation:Required // ContainerImage for the the OpenstackClient container (will be set to environmental default if empty) ContainerImage string `json:"containerImage"` + // +kubebuilder:validation:Required + // +kubebuilder:default=openstack-config // OpenStackConfigMap is the name of the ConfigMap containing the clouds.yaml - OpenStackConfigMap string `json:"openStackConfigMap"` + OpenStackConfigMap *string `json:"openStackConfigMap"` + // +kubebuilder:validation:Required + // +kubebuilder:default=openstack-config-secret // OpenStackConfigSecret is the name of the Secret containing the secure.yaml - OpenStackConfigSecret string `json:"openStackConfigSecret"` + OpenStackConfigSecret *string `json:"openStackConfigSecret"` // +kubebuilder:validation:Optional // NodeSelector to target subset of worker nodes running control plane services (currently only applies to KeystoneAPI and PlacementAPI) NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // +kubebuilder:validation:Optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Secret containing any CA certificates which should be added to deployment pods + tls.Ca `json:",inline"` } // OpenStackClientStatus defines the observed state of OpenStackClient diff --git a/apis/client/v1beta1/zz_generated.deepcopy.go b/apis/client/v1beta1/zz_generated.deepcopy.go index 6d43290bf..4dd597559 100644 --- a/apis/client/v1beta1/zz_generated.deepcopy.go +++ b/apis/client/v1beta1/zz_generated.deepcopy.go @@ -103,6 +103,16 @@ func (in *OpenStackClientList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenStackClientSpec) DeepCopyInto(out *OpenStackClientSpec) { *out = *in + if in.OpenStackConfigMap != nil { + in, out := &in.OpenStackConfigMap, &out.OpenStackConfigMap + *out = new(string) + **out = **in + } + if in.OpenStackConfigSecret != nil { + in, out := &in.OpenStackConfigSecret, &out.OpenStackConfigSecret + *out = new(string) + **out = **in + } if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = make(map[string]string, len(*in)) @@ -110,6 +120,7 @@ func (in *OpenStackClientSpec) DeepCopyInto(out *OpenStackClientSpec) { (*out)[key] = val } } + out.Ca = in.Ca } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackClientSpec. diff --git a/apis/core/v1beta1/conditions.go b/apis/core/v1beta1/conditions.go index 900b30888..6a06ba06f 100644 --- a/apis/core/v1beta1/conditions.go +++ b/apis/core/v1beta1/conditions.go @@ -102,6 +102,9 @@ const ( // OpenStackControlPlaneDNSReadyCondition Status=True condition which indicates if DNSMasq is configured and operational OpenStackControlPlaneDNSReadyCondition condition.Type = "OpenStackControlPlaneDNSReadyCondition" + // OpenStackControlPlaneCAReadyCondition Status=True condition which indicates if the CAs are configured and operational + OpenStackControlPlaneCAReadyCondition condition.Type = "OpenStackControlPlaneCAReadyCondition" + // OpenStackControlPlaneCeilometerReadyCondition Status=True condition which indicates if OpenStack Ceilometer service is configured and operational OpenStackControlPlaneCeilometerReadyCondition condition.Type = "OpenStackControlPlaneCeilometerReady" @@ -384,4 +387,16 @@ const ( // OpenStackControlPlaneExposeServiceReadyMessage OpenStackControlPlaneExposeServiceReadyMessage = "OpenStackControlPlane %s service exposed" + + // OpenStackControlPlaneCAReadyInitMessage + OpenStackControlPlaneCAReadyInitMessage = "OpenStackControlPlane CAs not started" + + // OpenStackControlPlaneCAReadyMessage + OpenStackControlPlaneCAReadyMessage = "OpenStackControlPlane CAs completed" + + // OpenStackControlPlaneCAReadyRunningMessage + OpenStackControlPlaneCAReadyRunningMessage = "OpenStackControlPlane CAs in progress" + + // OpenStackControlPlaneCAReadyErrorMessage + OpenStackControlPlaneCAReadyErrorMessage = "OpenStackControlPlane CAs %s %s error occured %s" ) diff --git a/apis/core/v1beta1/openstackcontrolplane_types.go b/apis/core/v1beta1/openstackcontrolplane_types.go index dde7f7e87..50d6d4b80 100644 --- a/apis/core/v1beta1/openstackcontrolplane_types.go +++ b/apis/core/v1beta1/openstackcontrolplane_types.go @@ -23,10 +23,13 @@ import ( horizonv1 "github.com/openstack-k8s-operators/horizon-operator/api/v1beta1" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" networkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" "github.com/openstack-k8s-operators/lib-common/modules/storage" manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" @@ -34,12 +37,12 @@ import ( neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" ovnv1 "github.com/openstack-k8s-operators/ovn-operator/api/v1beta1" placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" swiftv1 "github.com/openstack-k8s-operators/swift-operator/api/v1beta1" telemetryv1 "github.com/openstack-k8s-operators/telemetry-operator/api/v1beta1" rabbitmqv2 "github.com/rabbitmq/cluster-operator/v2/api/v1beta1" - redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -69,6 +72,12 @@ type OpenStackControlPlaneSpec struct { // NodeSelector to target subset of worker nodes running control plane services (currently only applies to KeystoneAPI and PlacementAPI) NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // +kubebuilder:validation:Optional + // +kubebuilder:default={endpoint: {public: {enabled: true}, internal: {enabled: false}}} + //+operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS TLSSection `json:"tls"` + // +kubebuilder:validation:Optional //+operator-sdk:csv:customresourcedefinitions:type=spec // DNS - Parameters related to the DNSMasq service @@ -158,6 +167,11 @@ type OpenStackControlPlaneSpec struct { // Redis - Parameters related to the Redis service Redis RedisSection `json:"redis,omitempty"` + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // OpenStackClient - Parameters related to the OpenStackClient + OpenStackClient OpenStackClientSection `json:"openstackclient,omitempty"` + // ExtraMounts containing conf files and credentials that should be provided // to the underlying operators. // This struct can be defined in the top level CR and propagated to the @@ -168,6 +182,29 @@ type OpenStackControlPlaneSpec struct { ExtraMounts []OpenStackExtraVolMounts `json:"extraMounts,omitempty"` } +// TLSSection defines the desired state of TLS configuration +type TLSSection struct { + // +kubebuilder:validation:optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // The key must be the endpoint type (public, internal) + Endpoint map[service.Endpoint]TLSEndpointConfig `json:"endpoint,omitempty"` + + // +kubebuilder:validation:optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Secret containing any additional CA certificates, which should be added to deployment pods. + // If services get configured to use a custom cert/key, add the CA cert to validate those in this + // CA secret. + tls.Ca `json:",inline"` +} + +// TLSEndpointConfig defines the desired state of TLSEndpoint configuration +type TLSEndpointConfig struct { + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // Enabled - Whether TLS should be enabled for endpoint type + Enabled bool `json:"enabled"` +} + // DNSMasqSection defines the desired state of DNSMasq service type DNSMasqSection struct { // +kubebuilder:validation:Optional @@ -206,6 +243,20 @@ type Override struct { // +kubebuilder:validation:Optional // Route overrides to use when creating the public service endpoint Route *route.OverrideSpec `json:"route,omitempty"` + + // +kubebuilder:validation:Optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - overrides tls parameters for public endpoint + TLS *TLSServiceOverride `json:"tls,omitempty"` +} + +// TLSServiceOverride overrides tls parameters for publioc endpoint +type TLSServiceOverride struct { + // +kubebuilder:validation:Optional + // Name of a Secret in the same Namespace as the service, containing the server's private key, public certificate + // and CA certificate for TLS. + // The Secret must store these as tls.key, tls.crt and ca.crt respectively. + SecretName string `json:"secretName,omitempty"` } // PlacementSection defines the desired state of Placement service @@ -561,6 +612,14 @@ type RedisSection struct { Templates map[string]redisv1.RedisSpec `json:"templates,omitempty"` } +// OpenStackClientSection defines the desired state of the OpenStackClient +type OpenStackClientSection struct { + // +kubebuilder:validation:Optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Template - Overrides to use when creating the OpenStackClient Resource + Template v1beta1.OpenStackClientSpec `json:"template,omitempty"` +} + // OpenStackControlPlaneStatus defines the observed state of OpenStackControlPlane type OpenStackControlPlaneStatus struct { //+operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"} @@ -642,6 +701,7 @@ func (instance *OpenStackControlPlane) InitConditions() { condition.UnknownCondition(OpenStackControlPlaneSwiftReadyCondition, condition.InitReason, OpenStackControlPlaneSwiftReadyInitMessage), condition.UnknownCondition(OpenStackControlPlaneOctaviaReadyCondition, condition.InitReason, OpenStackControlPlaneOctaviaReadyInitMessage), condition.UnknownCondition(OpenStackControlPlaneRedisReadyCondition, condition.InitReason, OpenStackControlPlaneRedisReadyInitMessage), + condition.UnknownCondition(OpenStackControlPlaneCAReadyCondition, condition.InitReason, OpenStackControlPlaneCAReadyInitMessage), // Also add the overall status condition as Unknown condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), diff --git a/apis/core/v1beta1/zz_generated.deepcopy.go b/apis/core/v1beta1/zz_generated.deepcopy.go index bbe5b7cdc..05cb7bfb2 100644 --- a/apis/core/v1beta1/zz_generated.deepcopy.go +++ b/apis/core/v1beta1/zz_generated.deepcopy.go @@ -26,6 +26,7 @@ import ( redisv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/storage" apiv1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" ovn_operatorapiv1beta1 "github.com/openstack-k8s-operators/ovn-operator/api/v1beta1" @@ -325,6 +326,22 @@ func (in *OctaviaSection) DeepCopy() *OctaviaSection { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackClientSection) DeepCopyInto(out *OpenStackClientSection) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackClientSection. +func (in *OpenStackClientSection) DeepCopy() *OpenStackClientSection { + if in == nil { + return nil + } + out := new(OpenStackClientSection) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenStackControlPlane) DeepCopyInto(out *OpenStackControlPlane) { *out = *in @@ -409,6 +426,7 @@ func (in *OpenStackControlPlaneSpec) DeepCopyInto(out *OpenStackControlPlaneSpec (*out)[key] = val } } + in.TLS.DeepCopyInto(&out.TLS) in.DNS.DeepCopyInto(&out.DNS) in.Keystone.DeepCopyInto(&out.Keystone) in.Placement.DeepCopyInto(&out.Placement) @@ -429,6 +447,7 @@ func (in *OpenStackControlPlaneSpec) DeepCopyInto(out *OpenStackControlPlaneSpec in.Swift.DeepCopyInto(&out.Swift) in.Octavia.DeepCopyInto(&out.Octavia) in.Redis.DeepCopyInto(&out.Redis) + in.OpenStackClient.DeepCopyInto(&out.OpenStackClient) if in.ExtraMounts != nil { in, out := &in.ExtraMounts, &out.ExtraMounts *out = make([]OpenStackExtraVolMounts, len(*in)) @@ -500,6 +519,11 @@ func (in *Override) DeepCopyInto(out *Override) { *out = new(route.OverrideSpec) (*in).DeepCopyInto(*out) } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSServiceOverride) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Override. @@ -645,3 +669,56 @@ func (in *SwiftSection) DeepCopy() *SwiftSection { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSEndpointConfig) DeepCopyInto(out *TLSEndpointConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSEndpointConfig. +func (in *TLSEndpointConfig) DeepCopy() *TLSEndpointConfig { + if in == nil { + return nil + } + out := new(TLSEndpointConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSSection) DeepCopyInto(out *TLSSection) { + *out = *in + if in.Endpoint != nil { + in, out := &in.Endpoint, &out.Endpoint + *out = make(map[service.Endpoint]TLSEndpointConfig, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + out.Ca = in.Ca +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSection. +func (in *TLSSection) DeepCopy() *TLSSection { + if in == nil { + return nil + } + out := new(TLSSection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSServiceOverride) DeepCopyInto(out *TLSServiceOverride) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSServiceOverride. +func (in *TLSServiceOverride) DeepCopy() *TLSServiceOverride { + if in == nil { + return nil + } + out := new(TLSServiceOverride) + in.DeepCopyInto(out) + return out +} diff --git a/apis/go.mod b/apis/go.mod index b00e43267..675f481a1 100644 --- a/apis/go.mod +++ b/apis/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/onsi/ginkgo/v2 v2.13.0 - github.com/onsi/gomega v1.28.0 + github.com/onsi/gomega v1.28.1 github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe github.com/openstack-k8s-operators/glance-operator/api v0.3.1-0.20231013112543-a07d8273b82d github.com/openstack-k8s-operators/heat-operator/api v0.3.1-0.20231016213913-eacfd44e505f @@ -12,7 +12,7 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231018060345-8518a89de1be github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 - github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f + github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 @@ -52,7 +52,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gophercloud/gophercloud v1.7.0 // indirect @@ -73,7 +73,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.16.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect diff --git a/apis/go.sum b/apis/go.sum index 65249cb1e..5ac24d08e 100644 --- a/apis/go.sum +++ b/apis/go.sum @@ -81,8 +81,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= @@ -124,8 +125,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= -github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 h1:rncLxJBpFGqBztyxCMwNRnMjhhIDOWHJowi6q8G6koI= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4= github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe h1:5lAcgOSjDrh0HrJa2w3l9rOJBaH8iSP6Bth169XI7Nw= @@ -142,8 +143,8 @@ github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc/go.mod h1:QJA4yh2B6lgZzrOOb0iW8JyDmB7uRT/Mokiwalca3Cw= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 h1:EgglSFb+05sWY5Z2JivMnXT7XSdGEOqpPfnlsc0oqvE= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46/go.mod h1:sDYtAWryP7mF2v4XfmKdAoFquVAMts2J5PuYFV9VBQU= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f h1:GhwRUVLRKKCEkK06uUXTMmT/waLJQPP5kMGpN6ozbc0= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:xXAuy7HtWN4p7LF5Q+NHLkwAsKVh0KrzpnuPYIG3XaA= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c h1:Noq2+ZY8161j7+pclJY+3CXsiOIuuP0GFft7sZW7fBw= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c/go.mod h1:YRRcnOxf/V+A/LkoTbh3KQvHXjnegp1tcbGtGrQ7Me0= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f h1:UItXdH97OtXMhW+gLa/xeCTDlm/9wMy3hiGoOLlY01I= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:+J/GuQH7/JJF8SJtWYWxgDsYlqDv+fucxwFNP+QTcKk= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f h1:WBoCIlbxWeg+hIaawVvQX/XiuEw80WHEHouw4H+0Ksk= @@ -244,8 +245,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= diff --git a/config/crd/bases/client.openstack.org_openstackclients.yaml b/config/crd/bases/client.openstack.org_openstackclients.yaml index 95f3e38c2..fb0d6a218 100644 --- a/config/crd/bases/client.openstack.org_openstackclients.yaml +++ b/config/crd/bases/client.openstack.org_openstackclients.yaml @@ -36,6 +36,8 @@ spec: type: object spec: properties: + caSecretName: + type: string containerImage: type: string nodeSelector: @@ -43,8 +45,10 @@ spec: type: string type: object openStackConfigMap: + default: openstack-config type: string openStackConfigSecret: + default: openstack-config-secret type: string required: - containerImage diff --git a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml index a8fcca8f6..253ac8935 100644 --- a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -205,6 +205,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -2348,6 +2353,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -3537,6 +3547,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cnfAPIOverride: properties: @@ -3638,6 +3653,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4089,6 +4109,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4316,6 +4341,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4420,6 +4450,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -5034,6 +5069,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -5308,6 +5348,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -6534,6 +6579,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -7557,6 +7607,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cellOverride: additionalProperties: @@ -7661,6 +7716,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object type: object type: object @@ -8470,6 +8530,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -8969,6 +9034,30 @@ spec: - secret type: object type: object + openstackclient: + properties: + template: + properties: + caSecretName: + type: string + containerImage: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + openStackConfigMap: + default: openstack-config + type: string + openStackConfigSecret: + default: openstack-config-secret + type: string + required: + - containerImage + - openStackConfigMap + - openStackConfigSecret + type: object + type: object ovn: properties: enabled: @@ -9297,6 +9386,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -13620,6 +13714,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -13778,6 +13877,24 @@ spec: - swiftStorage type: object type: object + tls: + default: + endpoint: + internal: + enabled: false + public: + enabled: true + properties: + caSecretName: + type: string + endpoint: + additionalProperties: + properties: + enabled: + type: boolean + type: object + type: object + type: object required: - secret - storageClass diff --git a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml index f9e92873d..4dabc0876 100644 --- a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml @@ -42,6 +42,9 @@ spec: of several child resources. displayName: APIOverride path: cinder.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: cinder.apiOverride.tls - description: Enabled - Whether Cinder service should be deployed and managed displayName: Enabled path: cinder.enabled @@ -85,6 +88,9 @@ spec: of several child resources. displayName: APIOverride path: glance.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: glance.apiOverride.tls - description: Enabled - Whether Glance service should be deployed and managed displayName: Enabled path: glance.enabled @@ -100,10 +106,16 @@ spec: of several child resources. displayName: APIOverride path: heat.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: heat.apiOverride.tls - description: CnfAPIOverride, provides the ability to override the generated manifest of several child resources. displayName: Cnf APIOverride path: heat.cnfAPIOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: heat.cnfAPIOverride.tls - description: Enabled - Whether Heat services should be deployed and managed displayName: Enabled path: heat.enabled @@ -119,6 +131,9 @@ spec: of several child resources. displayName: APIOverride path: horizon.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: horizon.apiOverride.tls - description: Ironic - Parameters related to the Ironic services displayName: Ironic path: ironic @@ -126,6 +141,9 @@ spec: of several child resources. displayName: APIOverride path: ironic.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: ironic.apiOverride.tls - description: Enabled - Whether Ironic services should be deployed and managed displayName: Enabled path: ironic.enabled @@ -135,6 +153,9 @@ spec: manifest of several child resources. displayName: Inspector Override path: ironic.inspectorOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: ironic.inspectorOverride.tls - description: Template - Overrides to use when creating the Ironic services displayName: Template path: ironic.template @@ -145,6 +166,9 @@ spec: of several child resources. displayName: APIOverride path: keystone.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: keystone.apiOverride.tls - description: Enabled - Whether Keystone service should be deployed and managed displayName: Enabled path: keystone.enabled @@ -157,6 +181,9 @@ spec: of several child resources. displayName: APIOverride path: manila.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: manila.apiOverride.tls - description: Enabled - Whether Manila service should be deployed and managed displayName: Enabled path: manila.enabled @@ -191,6 +218,9 @@ spec: of several child resources. displayName: APIOverride path: neutron.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: neutron.apiOverride.tls - description: Enabled - Whether Neutron service should be deployed and managed displayName: Enabled path: neutron.enabled @@ -210,12 +240,18 @@ spec: of several child resources. displayName: APIOverride path: nova.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: nova.apiOverride.tls - description: CellOverride, provides the ability to override the generated manifest of several child resources for a nova cell. cell0 never have compute nodes and therefore it won't have a noVNCProxy deployed. Providing an override for cell0 noVNCProxy does not have an effect. displayName: Cell Override path: nova.cellOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: nova.cellOverride.noVNCProxy.tls - description: Enabled - Whether Nova services should be deployed and managed displayName: Enabled path: nova.enabled @@ -228,6 +264,9 @@ spec: of several child resources. displayName: APIOverride path: octavia.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: octavia.apiOverride.tls - description: Enabled - Whether the Octavia service should be deployed and managed displayName: Enabled @@ -237,6 +276,13 @@ spec: - description: Template - Overrides to use when creating Octavia Resources displayName: Template path: octavia.template + - description: OpenStackClient - Parameters related to the OpenStackClient + displayName: Open Stack Client + path: openstackclient + - description: Template - Overrides to use when creating the OpenStackClient + Resource + displayName: Template + path: openstackclient.template - description: Ovn - Overrides to use when creating the OVN Services displayName: Ovn path: ovn @@ -266,6 +312,9 @@ spec: of several child resources. displayName: APIOverride path: placement.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: placement.apiOverride.tls - description: Enabled - Whether Placement service should be deployed and managed displayName: Enabled path: placement.enabled @@ -312,9 +361,23 @@ spec: manifest of several child resources. displayName: Proxy Override path: swift.proxyOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: swift.proxyOverride.tls - description: Template - Overrides to use when creating Swift Resources displayName: Template path: swift.template + - description: TLS - Parameters related to the TLS + displayName: TLS + path: tls + - description: The key must be the endpoint type (public, internal) + displayName: Endpoint + path: tls.endpoint + - description: Enabled - Whether TLS should be enabled for endpoint type + displayName: Enabled + path: tls.endpoint.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch statusDescriptors: - description: Conditions displayName: Conditions diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 6c13715fa..1d3dd158b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -27,6 +27,30 @@ rules: - list - update - watch +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cert-manager.io + resources: + - issuers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - cinder.openstack.org resources: @@ -78,8 +102,12 @@ rules: resources: - secrets verbs: + - create + - delete - get - list + - patch + - update - watch - apiGroups: - "" diff --git a/config/samples/core_v1beta1_openstackcontrolplane.yaml b/config/samples/core_v1beta1_openstackcontrolplane.yaml index 8ad4d51cd..c27c8b6ab 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true keystone: template: databaseInstance: openstack diff --git a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml index 4e1901f91..cde87aefd 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true dns: template: override: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml index d71d83006..1e48fc5bd 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true dns: template: override: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml index 0619f35d2..32e3a53f0 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true dns: template: override: diff --git a/controllers/client/openstackclient_controller.go b/controllers/client/openstackclient_controller.go index 5ecaf3fd4..9e0d179e6 100644 --- a/controllers/client/openstackclient_controller.go +++ b/controllers/client/openstackclient_controller.go @@ -30,8 +30,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/configmap" + "github.com/openstack-k8s-operators/lib-common/modules/common/env" helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" @@ -172,7 +174,13 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{RequeueAfter: time.Duration(5) * time.Second}, nil } - _, configMapHash, err := configmap.GetConfigMapAndHashWithName(ctx, helper, instance.Spec.OpenStackConfigMap, instance.Namespace) + clientLabels := map[string]string{ + common.AppSelector: "openstackclient", + } + + configVars := make(map[string]env.Setter) + + _, configMapHash, err := configmap.GetConfigMapAndHashWithName(ctx, helper, *instance.Spec.OpenStackConfigMap, instance.Namespace) if err != nil { if k8s_errors.IsNotFound(err) { instance.Status.Conditions.Set(condition.FalseCondition( @@ -190,8 +198,9 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ err.Error())) return ctrl.Result{}, err } + configVars[*instance.Spec.OpenStackConfigMap] = env.SetValue(configMapHash) - _, secretHash, err := secret.GetSecret(ctx, helper, instance.Spec.OpenStackConfigSecret, instance.Namespace) + _, secretHash, err := secret.GetSecret(ctx, helper, *instance.Spec.OpenStackConfigSecret, instance.Namespace) if err != nil { if k8s_errors.IsNotFound(err) { instance.Status.Conditions.Set(condition.FalseCondition( @@ -209,17 +218,39 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ err.Error())) return ctrl.Result{}, err } + configVars[*instance.Spec.OpenStackConfigSecret] = env.SetValue(secretHash) - instance.Status.Conditions.Set(condition.FalseCondition( - clientv1.OpenStackClientReadyCondition, - condition.RequestedReason, - condition.SeverityInfo, - clientv1.OpenStackClientInputReady)) + if instance.Spec.CaSecretName != "" { + _, secretHash, err = secret.GetSecret(ctx, helper, instance.Spec.CaSecretName, instance.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + instance.Status.Conditions.Set(condition.FalseCondition( + clientv1.OpenStackClientReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + clientv1.OpenStackClientSecretWaitingMessage)) + return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil + } + instance.Status.Conditions.Set(condition.FalseCondition( + clientv1.OpenStackClientReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + clientv1.OpenStackClientReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + configVars[instance.Spec.CaSecretName] = env.SetValue(secretHash) + } - clientLabels := map[string]string{ - "app": "openstackclient", + configVarsHash, err := util.HashOfInputHashes(configVars) + if err != nil { + return ctrl.Result{}, err + } + + pod, err := openstackclient.ClientPod(ctx, instance, helper, clientLabels, configVarsHash) + if err != nil { + return ctrl.Result{}, err } - pod := openstackclient.ClientPod(instance, clientLabels, configMapHash, secretHash) op, err := controllerutil.CreateOrPatch(ctx, r.Client, pod, func() error { pod.Spec.Containers[0].Image = instance.Spec.ContainerImage diff --git a/controllers/core/openstackcontrolplane_controller.go b/controllers/core/openstackcontrolplane_controller.go index 2ce0515f1..8c6e0aa4d 100644 --- a/controllers/core/openstackcontrolplane_controller.go +++ b/controllers/core/openstackcontrolplane_controller.go @@ -31,12 +31,14 @@ import ( keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + corev1 "k8s.io/api/core/v1" manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + clientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1beta1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" "github.com/openstack-k8s-operators/openstack-operator/pkg/openstack" @@ -94,6 +96,9 @@ type OpenStackControlPlaneReconciler struct { //+kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch;create;update;patch;delete; //+kubebuilder:rbac:groups=route.openshift.io,resources=routes/custom-host,verbs=create;update;patch //+kubebuilder:rbac:groups=core,resources=services,verbs=get;list; +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=cert-manager.io,resources=issuers,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=cert-manager.io,resources=certificates,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. @@ -164,7 +169,14 @@ func (r *OpenStackControlPlaneReconciler) Reconcile(ctx context.Context, req ctr func (r *OpenStackControlPlaneReconciler) reconcileNormal(ctx context.Context, instance *corev1beta1.OpenStackControlPlane, helper *helper.Helper) (ctrl.Result, error) { - ctrlResult, err := openstack.ReconcileDNSMasqs(ctx, instance, helper) + ctrlResult, err := openstack.ReconcileCAs(ctx, instance, helper) + if err != nil { + return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + ctrlResult, err = openstack.ReconcileDNSMasqs(ctx, instance, helper) if err != nil { return ctrl.Result{}, err } else if (ctrlResult != ctrl.Result{}) { @@ -318,6 +330,8 @@ func (r *OpenStackControlPlaneReconciler) reconcileNormal(ctx context.Context, i func (r *OpenStackControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&corev1beta1.OpenStackControlPlane{}). + Owns(&clientv1.OpenStackClient{}). + Owns(&corev1.Secret{}). Owns(&mariadbv1.MariaDB{}). Owns(&mariadbv1.Galera{}). Owns(&memcachedv1.Memcached{}). diff --git a/go.mod b/go.mod index 34c82768c..5df2333f1 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/openstack-k8s-operators/openstack-operator go 1.19 require ( + github.com/cert-manager/cert-manager v1.11.5 github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.16 github.com/onsi/ginkgo/v2 v2.13.0 - github.com/onsi/gomega v1.28.0 + github.com/onsi/gomega v1.28.1 github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe github.com/openstack-k8s-operators/dataplane-operator/api v0.3.1-0.20231019162005-1acbd3e775f1 github.com/openstack-k8s-operators/glance-operator/api v0.3.1-0.20231013112543-a07d8273b82d @@ -16,7 +17,8 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231018060345-8518a89de1be github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 - github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f + github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453 + github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 @@ -32,6 +34,7 @@ require ( github.com/operator-framework/api v0.17.6 github.com/rabbitmq/cluster-operator/v2 v2.5.0 go.uber.org/zap v1.26.0 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d k8s.io/api v0.27.2 k8s.io/apimachinery v0.27.4 k8s.io/client-go v0.27.2 @@ -44,8 +47,8 @@ require ( github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect github.com/metal3-io/baremetal-operator/apis v0.3.1 // indirect github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.2.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/tools v0.14.0 // indirect + sigs.k8s.io/gateway-api v0.6.0 // indirect ) require ( @@ -63,7 +66,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gophercloud/gophercloud v1.7.0 // indirect @@ -85,7 +88,7 @@ require ( github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.16.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect diff --git a/go.sum b/go.sum index 489277c7e..b1a6d36bf 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cert-manager/cert-manager v1.11.5 h1:K2LurvwIE4hIhODQZnkOW6ljYe3lVMAliS/to+gI05o= +github.com/cert-manager/cert-manager v1.11.5/go.mod h1:zNOyoTEwdn9Rtj5Or2pjBY1Bqwtw4vBElP2fKSP8/g8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -84,8 +86,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= @@ -131,8 +134,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= -github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 h1:rncLxJBpFGqBztyxCMwNRnMjhhIDOWHJowi6q8G6koI= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4= github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe h1:5lAcgOSjDrh0HrJa2w3l9rOJBaH8iSP6Bth169XI7Nw= @@ -151,8 +154,10 @@ github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc/go.mod h1:QJA4yh2B6lgZzrOOb0iW8JyDmB7uRT/Mokiwalca3Cw= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 h1:EgglSFb+05sWY5Z2JivMnXT7XSdGEOqpPfnlsc0oqvE= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46/go.mod h1:sDYtAWryP7mF2v4XfmKdAoFquVAMts2J5PuYFV9VBQU= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f h1:GhwRUVLRKKCEkK06uUXTMmT/waLJQPP5kMGpN6ozbc0= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:xXAuy7HtWN4p7LF5Q+NHLkwAsKVh0KrzpnuPYIG3XaA= +github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453 h1:mwmH2t1u5WisOxSWl9DDVFzTvPbomFe0GVFoGdH//gE= +github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453/go.mod h1:wQc5eZGAaUkAi8aOw/hS4A3y5ODcXAIwLt4t0cBT62g= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c h1:Noq2+ZY8161j7+pclJY+3CXsiOIuuP0GFft7sZW7fBw= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c/go.mod h1:YRRcnOxf/V+A/LkoTbh3KQvHXjnegp1tcbGtGrQ7Me0= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f h1:UItXdH97OtXMhW+gLa/xeCTDlm/9wMy3hiGoOLlY01I= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:+J/GuQH7/JJF8SJtWYWxgDsYlqDv+fucxwFNP+QTcKk= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f h1:WBoCIlbxWeg+hIaawVvQX/XiuEw80WHEHouw4H+0Ksk= @@ -261,8 +266,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= @@ -383,6 +388,8 @@ k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSn k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/gateway-api v0.6.0 h1:v2FqrN2ROWZLrSnI2o91taHR8Sj3s+Eh3QU7gLNWIqA= +sigs.k8s.io/gateway-api v0.6.0/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= diff --git a/main.go b/main.go index 8d2816934..346521765 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cinderv1 "github.com/openstack-k8s-operators/cinder-operator/api/v1beta1" dataplanev1beta1 "github.com/openstack-k8s-operators/dataplane-operator/api/v1beta1" glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1" @@ -102,6 +103,7 @@ func init() { utilruntime.Must(clientv1.AddToScheme(scheme)) utilruntime.Must(redisv1.AddToScheme(scheme)) utilruntime.Must(routev1.AddToScheme(scheme)) + utilruntime.Must(certmgrv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/pkg/openstack/ca.go b/pkg/openstack/ca.go new file mode 100644 index 000000000..28729ff94 --- /dev/null +++ b/pkg/openstack/ca.go @@ -0,0 +1,437 @@ +package openstack + +import ( + "context" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "math" + "os" + "time" + + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmgrmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/openstack-k8s-operators/lib-common/modules/certmanager" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "golang.org/x/exp/slices" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + + corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + + ctrl "sigs.k8s.io/controller-runtime" +) + +const ( + // CombinedCASecret - + CombinedCASecret = "combined-ca-bundle" + // TLSCABundleFile - + TLSCABundleFile = "tls-ca-bundle.pem" + // DefaultCAPrefix - + DefaultCAPrefix = "rootca-" + // DownstreamTLSCABundlePath - + DownstreamTLSCABundlePath = "/etc/pki/ca-trust/extracted/pem/" + TLSCABundleFile + // UpstreamTLSCABundlePath - + UpstreamTLSCABundlePath = "/etc/ssl/certs/ca-certificates.crt" +) + +// ReconcileCAs - +func ReconcileCAs(ctx context.Context, instance *corev1.OpenStackControlPlane, helper *helper.Helper) (ctrl.Result, error) { + // create selfsigned-issuer + issuerReq := certmanager.SelfSignedIssuer( + "selfsigned-issuer", + instance.GetNamespace(), + map[string]string{}, + ) + /* + // Cleanuo? + if !instance.Spec.TLS.Enabled { + if err := cert.Delete(ctx, helper); err != nil { + return ctrl.Result{}, err + } + instance.Status.Conditions.Remove(corev1beta1.OpenStackControlPlaneCAsReadyCondition) + + return ctrl.Result{}, nil + } + */ + + helper.GetLogger().Info("Reconciling CAs", "Namespace", instance.Namespace, "Name", issuerReq.Name) + + issuer := certmanager.NewIssuer(issuerReq, 5) + ctrlResult, err := issuer.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + issuerReq.Kind, + issuerReq.GetName(), + err.Error())) + + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return ctrlResult, nil + } + + bundle := newBundle() + + // load current CA bundle from secret if exist + currentCASecret, _, err := secret.GetSecret(ctx, helper, CombinedCASecret, instance.Namespace) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + if currentCASecret != nil { + if _, ok := currentCASecret.Data[TLSCABundleFile]; ok { + err = bundle.getCertsFromPEM(currentCASecret.Data[TLSCABundleFile]) + if err != nil { + return ctrl.Result{}, err + } + } + } + + // create RootCA cert and Issuer that uses the generated CA certificate to issue certs + for endpoint, config := range instance.Spec.TLS.Endpoint { + if config.Enabled { + + labels := map[string]string{} + if endpoint == service.EndpointInternal { + labels[certmanager.RootCAIssuerInternalLabel] = "" + } + // always create a root CA and issuer for the endpoint as we can + // not expect that all services are yet configured to be provided with + // a custom secret holding the cert/private key + caCert, ctrlResult, err := createRootCACertAndIssuer( + ctx, + instance, + helper, + issuerReq, + DefaultCAPrefix+string(endpoint), + labels, + ) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + err = bundle.getCertsFromPEM(caCert) + if err != nil { + return ctrl.Result{}, err + } + } + } + instance.Status.Conditions.MarkTrue(corev1.OpenStackControlPlaneCAReadyCondition, corev1.OpenStackControlPlaneCAReadyMessage) + + // create/update combined CA secret + if instance.Spec.TLS.CaSecretName != "" { + caSecret, _, err := secret.GetSecret(ctx, helper, instance.Spec.TLS.CaSecretName, instance.Namespace) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + instance.Spec.TLS.CaSecretName, + err.Error())) + + return ctrlResult, err + } + + for _, caCert := range caSecret.Data { + err = bundle.getCertsFromPEM(caCert) + if err != nil { + return ctrl.Result{}, err + } + } + } + + // get CA bundle from operator image. Downstream and upstream build use a different + // base image, so the ca bundle cert file can be in different locations + caBundle, err := getOperatorCABundle(DownstreamTLSCABundlePath) + if err != nil { + // if the DownstreamTLSCABundlePath does not exist in the operator image, + // check for UpstreamTLSCABundlePath + if errors.Is(err, os.ErrNotExist) { + helper.GetLogger().Info(fmt.Sprintf("Downstream CA bundle not found using: %s", UpstreamTLSCABundlePath)) + caBundle, err = getOperatorCABundle(UpstreamTLSCABundlePath) + if err != nil { + return ctrl.Result{}, err + } + } else { + return ctrl.Result{}, err + } + } + err = bundle.getCertsFromPEM(caBundle) + if err != nil { + return ctrl.Result{}, err + } + + saSecretTemplate := []util.Template{ + { + Name: CombinedCASecret, + Namespace: instance.Namespace, + Type: util.TemplateTypeNone, + InstanceType: instance.Kind, + AdditionalTemplate: nil, + Annotations: map[string]string{}, + Labels: map[string]string{ + tls.CABundleLabel: "", + }, + ConfigOptions: nil, + CustomData: map[string]string{TLSCABundleFile: bundle.getBundlePEM()}, + }, + } + + if err := secret.EnsureSecrets(ctx, helper, instance, saSecretTemplate, nil); err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + CombinedCASecret, + err.Error())) + + return ctrlResult, err + } + + return ctrl.Result{}, nil +} + +func createRootCACertAndIssuer( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + selfsignedIssuerReq *certmgrv1.Issuer, + caName string, + labels map[string]string, +) ([]byte, ctrl.Result, error) { + // create RootCA Certificate used to sign certificates + caCertReq := certmanager.Cert( + caName, + instance.Namespace, + map[string]string{}, + certmgrv1.CertificateSpec{ + IsCA: true, + CommonName: caName, + SecretName: caName, + PrivateKey: &certmgrv1.CertificatePrivateKey{ + Algorithm: "ECDSA", + Size: 256, + }, + IssuerRef: certmgrmetav1.ObjectReference{ + Name: selfsignedIssuerReq.Name, + Kind: selfsignedIssuerReq.Kind, + Group: selfsignedIssuerReq.GroupVersionKind().Group, + }, + }) + cert := certmanager.NewCertificate(caCertReq, 5) + + ctrlResult, err := cert.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + caCertReq.Kind, + caCertReq.Name, + err.Error())) + + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return nil, ctrlResult, nil + } + + // create Issuer that uses the generated CA certificate to issue certs + issuerReq := certmanager.CAIssuer( + caCertReq.Name, + instance.GetNamespace(), + labels, + caCertReq.Name, + ) + + issuer := certmanager.NewIssuer(issuerReq, 5) + ctrlResult, err = issuer.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + issuerReq.Kind, + issuerReq.GetName(), + err.Error())) + + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return nil, ctrlResult, nil + } + + caCert, ctrlResult, err := getCAFromSecret(ctx, instance, helper, caName) + if err != nil { + return nil, ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return nil, ctrlResult, nil + } + + return caCert, ctrl.Result{}, nil +} + +func getCAFromSecret( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + secretName string, +) ([]byte, ctrl.Result, error) { + caSecret, ctrlResult, err := secret.GetDataFromSecret(ctx, helper, secretName, time.Duration(5), "ca.crt") + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + secretName, + err.Error())) + + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return nil, ctrlResult, nil + } + + return []byte(caSecret), ctrl.Result{}, nil +} + +func getOperatorCABundle(caFile string) ([]byte, error) { + contents, err := os.ReadFile(caFile) + if err != nil { + return nil, fmt.Errorf("File reading error %w", err) + } + + return contents, nil +} + +func days(t time.Time) int { + return int(math.Round(time.Since(t).Hours() / 24)) +} + +type caBundle struct { + certs []caCert +} + +type caCert struct { + hash string + cert *x509.Certificate +} + +// newBundle returns a new, empty Bundle +func newBundle() *caBundle { + return &caBundle{ + certs: make([]caCert, 0), + } +} + +func (cab *caBundle) getCertsFromPEM(PEMdata []byte) error { + if PEMdata == nil { + return fmt.Errorf("certificate data can't be nil") + } + + for { + var block *pem.Block + block, PEMdata = pem.Decode(PEMdata) + + if block == nil { + break + } + + if block.Type != "CERTIFICATE" { + // only certificates are allowed in a bundle + return fmt.Errorf("invalid PEM block in bundle: only CERTIFICATE blocks are permitted but found '%s'", block.Type) + } + + if len(block.Headers) != 0 { + return fmt.Errorf("invalid PEM block in bundle; blocks are not permitted to have PEM headers") + } + + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + // the presence of an invalid cert (including things which aren't certs) + // should cause the bundle to be rejected + return fmt.Errorf("invalid PEM block in bundle; invalid PEM certificate: %w", err) + } + + if certificate == nil { + return fmt.Errorf("failed appending a certificate: certificate is nil") + } + + // validate if the CA expired + if -days(certificate.NotAfter) <= 0 { + continue + } + + blockHash, err := util.ObjectHash(block.Bytes) + if err != nil { + return fmt.Errorf("failed calc hash of PEM block : %w", err) + } + + // if cert is not already in bundle list add it + // validate of nextip is already in a reservation and its not us + f := func(c caCert) bool { + return c.hash == blockHash + } + idx := slices.IndexFunc(cab.certs, f) + if idx == -1 { + cab.certs = append(cab.certs, + caCert{ + hash: blockHash, + cert: certificate, + }) + } + } + + return nil +} + +// Create PEM bundle from certificates +func (cab *caBundle) getBundlePEM() string { + var bundleData string + + for _, cert := range cab.certs { + bundleData += "# " + cert.cert.Issuer.CommonName + "\n" + + string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.cert.Raw})) + } + + return bundleData +} diff --git a/pkg/openstack/cinder.go b/pkg/openstack/cinder.go index a0be02e74..dba54f71c 100644 --- a/pkg/openstack/cinder.go +++ b/pkg/openstack/cinder.go @@ -68,14 +68,14 @@ func ReconcileCinder(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Cinder.Template.CinderAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Cinder.Template.CinderAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, cinder, svcs, instance.Spec.Cinder.Template.CinderAPI.Override.Service, - instance.Spec.Cinder.APIOverride.Route, + instance.Spec.Cinder.APIOverride, corev1beta1.OpenStackControlPlaneExposeCinderReadyCondition, ) if err != nil { diff --git a/pkg/openstack/common.go b/pkg/openstack/common.go index 91de48804..c403c7a08 100644 --- a/pkg/openstack/common.go +++ b/pkg/openstack/common.go @@ -6,19 +6,24 @@ import ( "time" routev1 "github.com/openshift/api/route/v1" + "github.com/openstack-k8s-operators/lib-common/modules/certmanager" "github.com/openstack-k8s-operators/lib-common/modules/common" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/util" corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + k8s_corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // EnsureDeleted - Delete the object which in turn will clean the sub resources @@ -52,17 +57,41 @@ func AddServiceComponentLabel(svcOverride service.RoutedOverrideSpec, value stri return svcOverride } +// EndpointDetails - endpoint details +type EndpointDetails struct { + Name string + Namespace string + Type service.Endpoint + Annotations map[string]string + Labels map[string]string + Service ServiceDetails + Route RouteDetails + Hostname *string + EndpointURL string + TLS TLSDetails +} + +// TLSDetails - tls settings for the endpoint +type TLSDetails struct { + Enabled bool + Issuer string + CertSecret *string + InternalCA string + + //PublicEndpoint bool + //InternalEndpoint bool +} + +// ServiceDetails - service details +type ServiceDetails struct { + Spec *k8s_corev1.Service + OverrideSpec service.RoutedOverrideSpec +} + // RouteDetails - route details type RouteDetails struct { - RouteName string - Namespace string - Endpoint service.Endpoint - RouteOverrideSpec *route.OverrideSpec - ServiceLabel map[string]string - ServiceSpec *k8s_corev1.Service - endpointURL string - hostname *string - route *routev1.Route + Route *routev1.Route + OverrideSpec route.OverrideSpec } // GetRoutesListWithLabel - Get all routes in namespace of the obj matching label selector @@ -86,170 +115,308 @@ func GetRoutesListWithLabel( return routeList, nil } -// EnsureRoute - -func EnsureRoute( +// EnsureEndpointConfig - +func EnsureEndpointConfig( ctx context.Context, instance *corev1.OpenStackControlPlane, helper *helper.Helper, owner metav1.Object, svcs *k8s_corev1.ServiceList, svcOverrides map[service.Endpoint]service.RoutedOverrideSpec, - overrideSpec *route.OverrideSpec, + publicOverride corev1.Override, condType condition.Type, ) (map[service.Endpoint]service.RoutedOverrideSpec, ctrl.Result, error) { - - cleanCondition := true - for _, svc := range svcs.Items { - rd := RouteDetails{ - RouteName: svc.Name, - Namespace: svc.Namespace, - Endpoint: service.Endpoint(svc.Annotations[service.AnnotationEndpointKey]), - RouteOverrideSpec: overrideSpec, - ServiceSpec: &svc, - } - svcOverride := svcOverrides[rd.Endpoint] - - // check if there is already a route with common.AppSelector from the service - if svcLabelVal, ok := svc.Labels[common.AppSelector]; ok { - routes, err := GetRoutesListWithLabel( - ctx, - helper, - instance.Namespace, - map[string]string{common.AppSelector: svcLabelVal}, - ) - if err != nil { - return svcOverrides, ctrl.Result{}, err - } + ed := EndpointDetails{ + Name: svc.Name, + Namespace: svc.Namespace, + Type: service.Endpoint(svc.Annotations[service.AnnotationEndpointKey]), + Service: ServiceDetails{ + Spec: &svc, + }, + } + if publicOverride.Route != nil { + ed.Route.OverrideSpec = *publicOverride.Route + } - // check the routes if name changed where we are the owner - for _, r := range routes.Items { - instanceRef := metav1.OwnerReference{ - APIVersion: instance.APIVersion, - Kind: instance.Kind, - Name: instance.GetName(), - UID: instance.GetUID(), - BlockOwnerDeletion: ptr.To(true), - Controller: ptr.To(true), - } + if tlsEndpointConfig := instance.Spec.TLS.Endpoint[ed.Type]; tlsEndpointConfig.Enabled { + ed.TLS.Enabled = true + ed.TLS.Issuer = DefaultCAPrefix + string(ed.Type) - owner := metav1.GetControllerOf(&r.ObjectMeta) + // TODO: (mschuppert) for TLSE create TLS cert for service + //if ed.Type == service.EndpointInternal { + // TODO: for TLSE create TLS cert for service + //} + } - // Delete the route if the service was changed not to expose a route - if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "false" && - r.Spec.To.Name == svc.Name && - owner != nil && owner.UID == instance.GetUID() { - // Delete any other owner refs from ref list to not block deletion until owners are gone - r.SetOwnerReferences([]metav1.OwnerReference{instanceRef}) + if instance.Spec.TLS.Endpoint[service.EndpointInternal].Enabled { + // TODO: (mschuppert) get the CA cert for internal CA to add it to the route + // to be able to connect to the TLS internal endpoint + ed.TLS.InternalCA = "" + } - // Delete route - err := helper.GetClient().Delete(ctx, &r) - if err != nil && !k8s_errors.IsNotFound(err) { - err = fmt.Errorf("Error deleting route %s: %w", r.Name, err) - return svcOverrides, ctrl.Result{}, err - } + ed.Service.OverrideSpec = svcOverrides[ed.Type] - if svcOverride.EndpointURL != nil { - svcOverride.EndpointURL = nil - helper.GetLogger().Info(fmt.Sprintf("Service %s override endpointURL removed", svc.Name)) - } + if ed.Type == service.EndpointPublic { + if ed.TLS.Enabled { + // if a custom cert secret was provided we'll use this for + // the route, otherwise the issuer is used to request one + // for the endpoint. + if publicOverride.TLS != nil && publicOverride.TLS.SecretName != "" { + ed.TLS.CertSecret = ptr.To(publicOverride.TLS.SecretName) } } - } - // If the service has the create ingress annotation and its a default ClusterIP service -> create route - if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "true" && svc.Spec.Type == k8s_corev1.ServiceTypeClusterIP { - if instance.Status.Conditions.Get(condType) == nil { - instance.Status.Conditions.Set(condition.UnknownCondition( - condType, - condition.InitReason, - corev1.OpenStackControlPlaneExposeServiceReadyInitMessage, - owner.GetName(), - svc.Name, - )) + ctrlResult, err := ed.ensureRoute(ctx, instance, helper, &svc, owner, condType) + if err != nil { + return svcOverrides, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return svcOverrides, ctrlResult, nil } + } - if svcOverride.EmbeddedLabelsAnnotations == nil { - svcOverride.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} - } + // update override for the service with the endpoint url + if ed.EndpointURL != "" { + // Any trailing path will be added on the service-operator level. + ed.Service.OverrideSpec.EndpointURL = &ed.EndpointURL + instance.Status.Conditions.MarkTrue(condType, corev1.OpenStackControlPlaneExposeServiceReadyMessage, owner.GetName()) + } - if labelVal, ok := svcOverride.EmbeddedLabelsAnnotations.Labels[common.AppSelector]; ok { - rd.ServiceLabel = map[string]string{common.AppSelector: labelVal} - } + svcOverrides[ed.Type] = ed.Service.OverrideSpec + } - ctrlResult, err := rd.CreateRoute(ctx, helper, owner) - if err != nil { - instance.Status.Conditions.Set(condition.FalseCondition( - condType, - condition.ErrorReason, - condition.SeverityWarning, - corev1.OpenStackControlPlaneExposeServiceReadyErrorMessage, - owner.GetName(), - rd.RouteName, - err.Error())) + return svcOverrides, ctrl.Result{}, nil +} - return svcOverrides, ctrlResult, err - } else if (ctrlResult != ctrl.Result{}) { - return svcOverrides, ctrlResult, nil +func (ed *EndpointDetails) ensureRoute( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + svc *k8s_corev1.Service, + owner metav1.Object, + condType condition.Type, +) (ctrl.Result, error) { + // check if there is already a route with common.AppSelector from the service + if svcLabelVal, ok := svc.Labels[common.AppSelector]; ok { + routes, err := GetRoutesListWithLabel( + ctx, + helper, + instance.Namespace, + map[string]string{common.AppSelector: svcLabelVal}, + ) + if err != nil { + return ctrl.Result{}, err + } + + // check the routes if name changed where we are the owner + for _, r := range routes.Items { + instanceRef := metav1.OwnerReference{ + APIVersion: instance.APIVersion, + Kind: instance.Kind, + Name: instance.GetName(), + UID: instance.GetUID(), + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(true), } - cleanCondition = false + owner := metav1.GetControllerOf(&r.ObjectMeta) - // update override for the service with the route endpoint url - if rd.endpointURL != "" { - // Any trailing path will be added on the service-operator level. - svcOverride.EndpointURL = &rd.endpointURL - instance.Status.Conditions.MarkTrue(condType, corev1.OpenStackControlPlaneExposeServiceReadyMessage, owner.GetName()) + // Delete the route if the service was changed not to expose a route + if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "false" && + r.Spec.To.Name == ed.Name && + owner != nil && owner.UID == instance.GetUID() { + // Delete any other owner refs from ref list to not block deletion until owners are gone + r.SetOwnerReferences([]metav1.OwnerReference{instanceRef}) + + // Delete route + err := helper.GetClient().Delete(ctx, &r) + if err != nil && !k8s_errors.IsNotFound(err) { + err = fmt.Errorf("Error deleting route %s: %w", r.Name, err) + return ctrl.Result{}, err + } + + if ed.Service.OverrideSpec.EndpointURL != nil { + ed.Service.OverrideSpec.EndpointURL = nil + helper.GetLogger().Info(fmt.Sprintf("Service %s override endpointURL removed", svc.Name)) + } } } - - svcOverrides[rd.Endpoint] = svcOverride } - if cleanCondition { - instance.Status.Conditions.Remove(condType) + // If the service has the create ingress annotation and its a default ClusterIP service -> create route + if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "true" && svc.Spec.Type == k8s_corev1.ServiceTypeClusterIP { + if instance.Status.Conditions.Get(condType) == nil { + instance.Status.Conditions.Set(condition.UnknownCondition( + condType, + condition.InitReason, + corev1.OpenStackControlPlaneExposeServiceReadyInitMessage, + owner.GetName(), + svc.Name, + )) + } + if ed.Service.OverrideSpec.EmbeddedLabelsAnnotations == nil { + ed.Service.OverrideSpec.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} + } + + if labelVal, ok := ed.Service.OverrideSpec.EmbeddedLabelsAnnotations.Labels[common.AppSelector]; ok { + ed.Labels = map[string]string{common.AppSelector: labelVal} + } + + ctrlResult, err := ed.CreateRoute(ctx, helper, owner) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condType, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneExposeServiceReadyErrorMessage, + owner.GetName(), + ed.Name, + err.Error())) + + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + return ctrl.Result{}, nil } + instance.Status.Conditions.Remove(condType) - return svcOverrides, ctrl.Result{}, nil + return ctrl.Result{}, nil } // CreateRoute - -func (rd *RouteDetails) CreateRoute( +func (ed *EndpointDetails) CreateRoute( ctx context.Context, helper *helper.Helper, owner metav1.Object, ) (ctrl.Result, error) { - // TODO TLS - routeOverrides := []route.OverrideSpec{} - if rd.RouteOverrideSpec != nil { - routeOverrides = append(routeOverrides, *rd.RouteOverrideSpec) - } - route, err := route.NewRoute( + // initialize the route with any custom provided route override + enptRoute, err := route.NewRoute( route.GenericRoute(&route.GenericRouteDetails{ - Name: rd.RouteName, - Namespace: rd.Namespace, - Labels: rd.ServiceLabel, - ServiceName: rd.ServiceSpec.Name, - TargetPortName: rd.ServiceSpec.Name, + Name: ed.Name, + Namespace: ed.Namespace, + Labels: ed.Labels, + ServiceName: ed.Service.Spec.Name, + TargetPortName: ed.Service.Spec.Name, }), time.Duration(5)*time.Second, - routeOverrides, + []route.OverrideSpec{ed.Route.OverrideSpec}, ) if err != nil { return ctrl.Result{}, err } - route.OwnerReferences = append(route.OwnerReferences, owner) + enptRoute.OwnerReferences = append(enptRoute.OwnerReferences, owner) + + // if route TLS is disabled -> create the route + // if TLS is enabled and the route does not yet exist -> create the route + // to get the hostname for creating the cert + serviceRoute := &routev1.Route{} + err = helper.GetClient().Get(ctx, types.NamespacedName{Name: ed.Name, Namespace: ed.Namespace}, serviceRoute) + if !ed.TLS.Enabled || (ed.TLS.Enabled && err != nil && k8s_errors.IsNotFound(err)) { + ctrlResult, err := enptRoute.CreateOrPatch(ctx, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } - ctrlResult, err := route.CreateOrPatch(ctx, helper) - if err != nil { - return ctrlResult, err - } else if (ctrlResult != ctrl.Result{}) { - return ctrlResult, nil + ed.Hostname = ptr.To(enptRoute.GetHostname()) + } else if err != nil { + return ctrl.Result{}, err + } else { + ed.Hostname = &serviceRoute.Spec.Host } - rd.hostname = ptr.To(route.GetHostname()) - rd.endpointURL = "http://" + *rd.hostname - rd.route = route.GetRoute() + // if the issuer is provided TLS is enabled + if ed.TLS.Enabled { + var ctrlResult reconcile.Result + + certSecret := &k8s_corev1.Secret{} + + // if a custom cert secret was provided, check if it exist + // and has the required cert, key and cacert + // Right now there is no check if certificate is valid for + // the hostname of the route. If the referenced secret is + // there and has the required files it is just being used. + if ed.TLS.CertSecret != nil { + certSecret, _, err = secret.GetSecret(ctx, helper, *ed.TLS.CertSecret, ed.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("certificate secret %s not found: %w", *ed.TLS.CertSecret, err) + } + + return ctrl.Result{}, err + } + + // check if secret has the expected entries tls.crt, tls.key and ca.crt + if certSecret != nil { + for _, key := range []string{"tls.crt", "tls.key", "ca.crt"} { + if _, exist := certSecret.Data[key]; !exist { + return ctrl.Result{}, fmt.Errorf("certificate secret %s does not provide %s", *ed.TLS.CertSecret, key) + } + } + } + } else { + //create the cert using our issuer for the endpoint + certSecret, ctrlResult, err = certmanager.EnsureCert( + ctx, + helper, + ed.TLS.Issuer, + ed.Name, + nil, + []string{*ed.Hostname}, + ed.Annotations, + ed.Labels) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + } + + // create default TLS route override + tlsConfig := &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: string(certSecret.Data["tls.crt"]), + Key: string(certSecret.Data["tls.key"]), + CACertificate: string(certSecret.Data["ca.crt"]), + InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, + } + + // for internal TLS (TLSE) use routev1.TLSTerminationReencrypt + if ed.TLS.InternalCA != "" { + tlsConfig.Termination = routev1.TLSTerminationReencrypt + tlsConfig.DestinationCACertificate = ed.TLS.InternalCA + } + + enptRoute, err = route.NewRoute( + enptRoute.GetRoute(), + time.Duration(5)*time.Second, + []route.OverrideSpec{ + { + Spec: &route.Spec{ + TLS: tlsConfig, + }, + }, + ed.Route.OverrideSpec, + }, + ) + if err != nil { + return ctrl.Result{}, err + } + + ctrlResult, err = enptRoute.CreateOrPatch(ctx, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + ed.EndpointURL = "https://" + *ed.Hostname + } else { + ed.EndpointURL = "http://" + *ed.Hostname + } return ctrl.Result{}, nil } diff --git a/pkg/openstack/glance.go b/pkg/openstack/glance.go index 447235f64..ca6e4fada 100644 --- a/pkg/openstack/glance.go +++ b/pkg/openstack/glance.go @@ -72,14 +72,14 @@ func ReconcileGlance(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - serviceOverrides, ctrlResult, err = EnsureRoute( + serviceOverrides, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, glance, svcs, serviceOverrides, - instance.Spec.Glance.APIOverride.Route, + instance.Spec.Glance.APIOverride, corev1beta1.OpenStackControlPlaneExposeGlanceReadyCondition, ) if err != nil { diff --git a/pkg/openstack/heat.go b/pkg/openstack/heat.go index 033699b2d..123ba56d7 100644 --- a/pkg/openstack/heat.go +++ b/pkg/openstack/heat.go @@ -77,14 +77,14 @@ func ReconcileHeat(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - instance.Spec.Heat.Template.HeatAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Heat.Template.HeatAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, heat, svcs, instance.Spec.Heat.Template.HeatAPI.Override.Service, - instance.Spec.Heat.APIOverride.Route, + instance.Spec.Heat.APIOverride, corev1beta1.OpenStackControlPlaneExposeHeatReadyCondition, ) if err != nil { @@ -107,14 +107,14 @@ func ReconcileHeat(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - instance.Spec.Heat.Template.HeatCfnAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Heat.Template.HeatCfnAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, heat, svcs, instance.Spec.Heat.Template.HeatCfnAPI.Override.Service, - instance.Spec.Heat.CnfAPIOverride.Route, + instance.Spec.Heat.CnfAPIOverride, corev1beta1.OpenStackControlPlaneExposeHeatReadyCondition, ) if err != nil { diff --git a/pkg/openstack/horizon.go b/pkg/openstack/horizon.go index ce61ed5d7..a4b31a574 100644 --- a/pkg/openstack/horizon.go +++ b/pkg/openstack/horizon.go @@ -79,14 +79,14 @@ func ReconcileHorizon(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - serviceOverrides, ctrlResult, err = EnsureRoute( + serviceOverrides, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, horizon, svcs, serviceOverrides, - instance.Spec.Horizon.APIOverride.Route, + instance.Spec.Horizon.APIOverride, corev1beta1.OpenStackControlPlaneExposeHorizonReadyCondition, ) if err != nil { diff --git a/pkg/openstack/ironic.go b/pkg/openstack/ironic.go index 44d0f5d74..66f9b2b52 100644 --- a/pkg/openstack/ironic.go +++ b/pkg/openstack/ironic.go @@ -77,14 +77,14 @@ func ReconcileIronic(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Ironic.Template.IronicAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Ironic.Template.IronicAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, ironic, svcs, instance.Spec.Ironic.Template.IronicAPI.Override.Service, - instance.Spec.Ironic.APIOverride.Route, + instance.Spec.Ironic.APIOverride, corev1beta1.OpenStackControlPlaneExposeIronicReadyCondition, ) if err != nil { @@ -107,14 +107,14 @@ func ReconcileIronic(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Ironic.Template.IronicInspector.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Ironic.Template.IronicInspector.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, ironic, svcs, instance.Spec.Ironic.Template.IronicInspector.Override.Service, - instance.Spec.Ironic.InspectorOverride.Route, + instance.Spec.Ironic.InspectorOverride, corev1beta1.OpenStackControlPlaneExposeIronicReadyCondition, ) if err != nil { diff --git a/pkg/openstack/keystone.go b/pkg/openstack/keystone.go index 4aa507c12..3a1b58c18 100644 --- a/pkg/openstack/keystone.go +++ b/pkg/openstack/keystone.go @@ -68,14 +68,14 @@ func ReconcileKeystoneAPI(ctx context.Context, instance *corev1beta1.OpenStackCo } var ctrlResult reconcile.Result - instance.Spec.Keystone.Template.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Keystone.Template.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, keystoneAPI, svcs, instance.Spec.Keystone.Template.Override.Service, - instance.Spec.Keystone.APIOverride.Route, + instance.Spec.Keystone.APIOverride, corev1beta1.OpenStackControlPlaneExposeKeystoneAPIReadyCondition, ) if err != nil { diff --git a/pkg/openstack/manila.go b/pkg/openstack/manila.go index 735a804fc..4c4412ee0 100644 --- a/pkg/openstack/manila.go +++ b/pkg/openstack/manila.go @@ -69,14 +69,14 @@ func ReconcileManila(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Manila.Template.ManilaAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Manila.Template.ManilaAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, manila, svcs, instance.Spec.Manila.Template.ManilaAPI.Override.Service, - instance.Spec.Manila.APIOverride.Route, + instance.Spec.Manila.APIOverride, corev1beta1.OpenStackControlPlaneExposeManilaReadyCondition, ) if err != nil { diff --git a/pkg/openstack/neutron.go b/pkg/openstack/neutron.go index 7a15f0e26..c933cd3d7 100644 --- a/pkg/openstack/neutron.go +++ b/pkg/openstack/neutron.go @@ -68,14 +68,14 @@ func ReconcileNeutron(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - instance.Spec.Neutron.Template.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Neutron.Template.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, neutronAPI, svcs, instance.Spec.Neutron.Template.Override.Service, - instance.Spec.Neutron.APIOverride.Route, + instance.Spec.Neutron.APIOverride, corev1beta1.OpenStackControlPlaneExposeNeutronReadyCondition, ) if err != nil { diff --git a/pkg/openstack/nova.go b/pkg/openstack/nova.go index 58ebf5e1f..0366fc730 100644 --- a/pkg/openstack/nova.go +++ b/pkg/openstack/nova.go @@ -105,14 +105,14 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, nova, svcs, instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, - instance.Spec.Nova.APIOverride.Route, + instance.Spec.Nova.APIOverride, corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition, ) if err != nil { @@ -147,7 +147,7 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - routedOverrideSpec, ctrlResult, err := EnsureRoute( + routedOverrideSpec, ctrlResult, err := EnsureEndpointConfig( ctx, instance, helper, @@ -156,7 +156,7 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl map[service.Endpoint]service.RoutedOverrideSpec{ service.EndpointPublic: *cellTemplate.NoVNCProxyServiceTemplate.Override.Service, }, - instance.Spec.Nova.CellOverride[cellName].NoVNCProxy.Route, + instance.Spec.Nova.CellOverride[cellName].NoVNCProxy, corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition, ) if err != nil { diff --git a/pkg/openstack/octavia.go b/pkg/openstack/octavia.go index 6715197f1..cb534fa8c 100644 --- a/pkg/openstack/octavia.go +++ b/pkg/openstack/octavia.go @@ -84,14 +84,14 @@ func ReconcileOctavia(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - instance.Spec.Octavia.Template.OctaviaAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Octavia.Template.OctaviaAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, octavia, svcs, instance.Spec.Octavia.Template.OctaviaAPI.Override.Service, - instance.Spec.Octavia.APIOverride.Route, + instance.Spec.Octavia.APIOverride, corev1beta1.OpenStackControlPlaneExposeOctaviaReadyCondition, ) if err != nil { diff --git a/pkg/openstack/openstackclient.go b/pkg/openstack/openstackclient.go index 2481daab4..87c1f0bf5 100644 --- a/pkg/openstack/openstackclient.go +++ b/pkg/openstack/openstackclient.go @@ -43,10 +43,14 @@ func ReconcileOpenStackClient(ctx context.Context, instance *corev1.OpenStackCon helper.GetLogger().Info("Reconciling OpenStackClient", "OpenStackClient.Namespace", instance.Namespace, "OpenStackClient.Name", openstackclient.Name) op, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), openstackclient, func() error { - // the following are created/owned by keystoneclient - openstackclient.Spec.OpenStackConfigMap = "openstack-config" - openstackclient.Spec.OpenStackConfigSecret = "openstack-config-secret" - openstackclient.Spec.NodeSelector = instance.Spec.NodeSelector + instance.Spec.OpenStackClient.Template.DeepCopyInto(&openstackclient.Spec) + + for _, config := range instance.Spec.TLS.Endpoint { + if config.Enabled { + openstackclient.Spec.Ca.CaSecretName = CombinedCASecret + break + } + } err := controllerutil.SetControllerReference(helper.GetBeforeObject(), openstackclient, helper.GetScheme()) if err != nil { diff --git a/pkg/openstack/placement.go b/pkg/openstack/placement.go index 388d5a1dd..7dc484c46 100644 --- a/pkg/openstack/placement.go +++ b/pkg/openstack/placement.go @@ -67,14 +67,14 @@ func ReconcilePlacementAPI(ctx context.Context, instance *corev1beta1.OpenStackC } var ctrlResult reconcile.Result - instance.Spec.Placement.Template.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Placement.Template.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, placementAPI, svcs, instance.Spec.Placement.Template.Override.Service, - instance.Spec.Placement.APIOverride.Route, + instance.Spec.Placement.APIOverride, corev1beta1.OpenStackControlPlaneExposePlacementAPIReadyCondition, ) if err != nil { diff --git a/pkg/openstack/swift.go b/pkg/openstack/swift.go index 006c3a50c..e152d5203 100644 --- a/pkg/openstack/swift.go +++ b/pkg/openstack/swift.go @@ -68,14 +68,14 @@ func ReconcileSwift(ctx context.Context, instance *corev1beta1.OpenStackControlP } var ctrlResult reconcile.Result - instance.Spec.Swift.Template.SwiftProxy.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Swift.Template.SwiftProxy.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, swift, svcs, instance.Spec.Swift.Template.SwiftProxy.Override.Service, - instance.Spec.Swift.ProxyOverride.Route, + instance.Spec.Swift.ProxyOverride, corev1beta1.OpenStackControlPlaneExposeSwiftReadyCondition, ) if err != nil { diff --git a/pkg/openstackclient/funcs.go b/pkg/openstackclient/funcs.go index cf9fd7044..512c3ade1 100644 --- a/pkg/openstackclient/funcs.go +++ b/pkg/openstackclient/funcs.go @@ -13,19 +13,26 @@ limitations under the License. package openstackclient import ( + "context" + + env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" clientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" ) // ClientPod func func ClientPod( + ctx context.Context, instance *clientv1.OpenStackClient, + helper *helper.Helper, labels map[string]string, configHash string, - secretHash string, -) *corev1.Pod { +) (*corev1.Pod, error) { clientPod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -34,76 +41,83 @@ func ClientPod( }, } - var terminationGracePeriodSeconds int64 = 0 - runAsUser := int64(42401) - runAsGroup := int64(42401) + envVars := map[string]env.Setter{} + envVars["OS_CLOUD"] = env.SetValue("default") + envVars["CONFIG_HASH"] = env.SetValue(configHash) + clientPod.ObjectMeta = metav1.ObjectMeta{ Name: instance.Name, Namespace: instance.Namespace, Labels: labels, } - clientPod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds + clientPod.Spec.TerminationGracePeriodSeconds = ptr.To[int64](0) clientPod.Spec.ServiceAccountName = instance.RbacResourceName() - clientPod.Spec.Containers = []corev1.Container{ - { - Name: "openstackclient", - Image: instance.Spec.ContainerImage, - Command: []string{"/bin/sleep"}, - Args: []string{"infinity"}, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: &runAsUser, - RunAsGroup: &runAsGroup, - }, - Env: []corev1.EnvVar{ - { - Name: "OS_CLOUD", - Value: "default", - }, - { - Name: "CONFIG_HASH", - Value: configHash, - }, - { - Name: "SECRET_HASH", - Value: secretHash, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "openstack-config", - MountPath: "/etc/openstack/clouds.yaml", - SubPath: "clouds.yaml", - }, - { - Name: "openstack-config-secret", - MountPath: "/etc/openstack/secure.yaml", - SubPath: "secure.yaml", + clientContainer := corev1.Container{ + Name: "openstackclient", + Image: instance.Spec.ContainerImage, + Command: []string{"/bin/sleep"}, + Args: []string{"infinity"}, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: ptr.To[int64](42401), + RunAsGroup: ptr.To[int64](42401), + RunAsNonRoot: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", }, }, }, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), } - clientPod.Spec.Volumes = clientPodVolumes(instance, labels, configHash, secretHash) + + tlsConfig, err := tls.NewTLS(ctx, helper, instance.Namespace, nil, &instance.Spec.Ca) + if err != nil { + return nil, err + } + clientContainer.VolumeMounts = clientPodVolumeMounts(tlsConfig) + + clientPod.Spec.Containers = []corev1.Container{clientContainer} + + clientPod.Spec.Volumes = clientPodVolumes(instance, tlsConfig) if instance.Spec.NodeSelector != nil && len(instance.Spec.NodeSelector) > 0 { clientPod.Spec.NodeSelector = instance.Spec.NodeSelector } - return clientPod + return clientPod, nil +} + +func clientPodVolumeMounts( + tlsConfig *tls.TLS, +) []corev1.VolumeMount { + volumeMounts := []corev1.VolumeMount{ + { + Name: "openstack-config", + MountPath: "/home/cloud-admin/.config/openstack/clouds.yaml", + SubPath: "clouds.yaml", + }, + { + Name: "openstack-config-secret", + MountPath: "/home/cloud-admin/.config/openstack/secure.yaml", + SubPath: "secure.yaml", + }, + } + volumeMounts = append(volumeMounts, tlsConfig.CreateVolumeMounts()...) + + return volumeMounts } func clientPodVolumes( instance *clientv1.OpenStackClient, - labels map[string]string, - configHash string, - secretHash string, + tlsConfig *tls.TLS, ) []corev1.Volume { - - return []corev1.Volume{ + volumes := []corev1.Volume{ { Name: "openstack-config", VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: instance.Spec.OpenStackConfigMap, + Name: *instance.Spec.OpenStackConfigMap, }, }, }, @@ -112,10 +126,12 @@ func clientPodVolumes( Name: "openstack-config-secret", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: instance.Spec.OpenStackConfigSecret, + SecretName: *instance.Spec.OpenStackConfigSecret, }, }, }, } + volumes = append(volumes, tlsConfig.CreateVolumes()...) + return volumes } From f02cce80c2d77df84f12ba26a63359c0dc31753c Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Thu, 5 Oct 2023 18:30:09 +0200 Subject: [PATCH 2/3] [TLS] basic CA/ca-bundle create envtest --- .github/workflows/golangci-lint.yaml | 55 --- .github/workflows/lints.yaml | 14 + Makefile | 16 +- apis/go.mod | 5 +- apis/go.sum | 4 +- controllers/client/suite_test.go | 77 ---- .../core/openstackcontrolplane_controller.go | 3 + controllers/core/suite_test.go | 85 ----- go.mod | 10 +- go.sum | 9 +- tests/functional/base_test.go | 234 ++++++++++++ .../openstackclient_webhook_test.go | 71 ++++ .../openstackoperator_controller_test.go | 131 +++++++ tests/functional/suite_test.go | 350 ++++++++++++++++++ 14 files changed, 831 insertions(+), 233 deletions(-) delete mode 100644 .github/workflows/golangci-lint.yaml create mode 100644 .github/workflows/lints.yaml delete mode 100644 controllers/client/suite_test.go delete mode 100644 controllers/core/suite_test.go create mode 100644 tests/functional/base_test.go create mode 100644 tests/functional/openstackclient_webhook_test.go create mode 100644 tests/functional/openstackoperator_controller_test.go create mode 100644 tests/functional/suite_test.go diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml deleted file mode 100644 index f9ac1a2e2..000000000 --- a/.github/workflows/golangci-lint.yaml +++ /dev/null @@ -1,55 +0,0 @@ -name: Golang lint, vet and unit test pipeline - -on: [push, pull_request] - -jobs: - test: - name: github (govet, golint and gotest) - runs-on: ubuntu-latest - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.19.x - - name: Checkout project code - uses: actions/checkout@v2 - - name: Checkout openstack-k8s-operators-ci project - uses: actions/checkout@v2 - with: - repository: openstack-k8s-operators/openstack-k8s-operators-ci - path: ./openstack-k8s-operators-ci - - name: Run govet.sh - run: ./openstack-k8s-operators-ci/test-runner/govet.sh - - name: Run golint.sh - run: ./openstack-k8s-operators-ci/test-runner/golint.sh - - name: Run gotest.sh - run: ./openstack-k8s-operators-ci/test-runner/gotest.sh - - golangci: - name: github (golangci) - runs-on: ubuntu-latest - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.19.x - - name: Checkout project code - uses: actions/checkout@v2 - - name: Run golangci lint - uses: golangci/golangci-lint-action@v2 - with: - version: latest - args: --timeout 5m - - operator-lint: - name: operator-lint - runs-on: ubuntu-latest - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: 1.19.x - - name: Checkout project code - uses: actions/checkout@v2 - - name: Run operator-lint - run: make operator-lint diff --git a/.github/workflows/lints.yaml b/.github/workflows/lints.yaml new file mode 100644 index 000000000..15eee2371 --- /dev/null +++ b/.github/workflows/lints.yaml @@ -0,0 +1,14 @@ +name: Lints + +on: [pull_request] + +jobs: + check-go-mod-replace-lines: + name: check for replace lines in go.mod files + runs-on: ubuntu-latest + steps: + - name: Checkout project code + uses: actions/checkout@v3 + - name: check for replace lines in go.mod files + run: | + ! egrep --invert-match -e '^replace.*/apis => \./apis|^replace.*//allow-merging$' `find . -name 'go.mod'` | egrep -e 'go.mod:replace' diff --git a/Makefile b/Makefile index f79f7c45f..39096b9b6 100644 --- a/Makefile +++ b/Makefile @@ -127,8 +127,10 @@ golangci-lint: $(LOCALBIN)/golangci-lint run --fix .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out +test: manifests generate gowork fmt vet envtest ginkgo ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" \ + OPERATOR_TEMPLATES="$(PWD)/templates" \ + $(GINKGO) --trace --cover --coverpkg=../../pkg/openstack,../../pkg/openstackclient,../../pkg/util,../../controllers,../../apis/client/v1beta1,../../apis/core/v1beta1 --coverprofile cover.out --covermode=atomic ${PROC_CMD} $(GINKGO_ARGS) ./tests/... ./apis/client/... ##@ Build @@ -204,6 +206,7 @@ $(LOCALBIN): KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest +GINKGO ?= $(LOCALBIN)/ginkgo ## Tool Versions KUSTOMIZE_VERSION ?= v3.8.7 @@ -225,6 +228,11 @@ envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +.PHONY: ginkgo +ginkgo: $(GINKGO) ## Download ginkgo locally if necessary. +$(GINKGO): $(LOCALBIN) + test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo + .PHONY: bundle bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests -q @@ -316,9 +324,7 @@ govet: get-ci-tools GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/govet.sh ./apis # Run go test against code -gotest: get-ci-tools - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gotest.sh - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gotest.sh ./apis +gotest: test # Run golangci-lint test against code golangci: get-ci-tools diff --git a/apis/go.mod b/apis/go.mod index 675f481a1..0c2b7281b 100644 --- a/apis/go.mod +++ b/apis/go.mod @@ -14,7 +14,7 @@ require ( github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 - github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 + github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20231020162407-336d5231e505 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20231019131906-d371f09abbc6 github.com/openstack-k8s-operators/octavia-operator/api v0.3.1-0.20231012052157-e3ff0e9c016d @@ -97,13 +97,12 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) -replace ( +replace ( //allow-merging // pin to k8s 0.26.x for now k8s.io/api => k8s.io/api v0.26.9 k8s.io/apimachinery => k8s.io/apimachinery v0.26.9 k8s.io/client-go => k8s.io/client-go v0.26.9 sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.14.6 - ) // mschuppert: map to latest commit from release-4.13 tag diff --git a/apis/go.sum b/apis/go.sum index 5ac24d08e..57bb86edb 100644 --- a/apis/go.sum +++ b/apis/go.sum @@ -151,8 +151,8 @@ github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.202310190 github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:baotYmNGQjRCgu/9LxjrFZzTIT2/4yLktJK8h/Fa0Ig= github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 h1:8bi5MJwe9jea0gtFXpUQpYeQuDo7ULEUpOCwWi8XrXY= github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991/go.mod h1:HZ2ZuXYMpLJPqg+rUy/ZlNTCA2FnNp0fkRadCL4z7WA= -github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 h1:FB0xB6whYM6W4XIncYo2mPiOJWkFsIOWtCT+UOtvOaQ= -github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0/go.mod h1:xhiz5wFdKWwVM7BF/VYon4TT3NuUPXp/Pyn2hWcp0CE= +github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20231020162407-336d5231e505 h1:KeaoopKRpcBvkRXeJOeV5Kzy5rE8U2nO6sIz8EI4t+U= +github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20231020162407-336d5231e505/go.mod h1:xhiz5wFdKWwVM7BF/VYon4TT3NuUPXp/Pyn2hWcp0CE= github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 h1:pymyGAni8tJ5juel1rTbSx0sUxfjKVwM55eNSK7V5Ls= github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186/go.mod h1:cKr0xZME9DePkWqXbyKFjNprjCeJl5WdE/Xn+lYWY7I= github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20231019131906-d371f09abbc6 h1:sbosbVd31OagqdtMhYYKI++4PWs4KHDTfebMvegwFw0= diff --git a/controllers/client/suite_test.go b/controllers/client/suite_test.go deleted file mode 100644 index 42e03f5bb..000000000 --- a/controllers/client/suite_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2023. -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 client - -import ( - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - clientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = clientv1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/controllers/core/openstackcontrolplane_controller.go b/controllers/core/openstackcontrolplane_controller.go index 8c6e0aa4d..3194e2e8d 100644 --- a/controllers/core/openstackcontrolplane_controller.go +++ b/controllers/core/openstackcontrolplane_controller.go @@ -20,6 +20,7 @@ import ( "context" "fmt" + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" routev1 "github.com/openshift/api/route/v1" cinderv1 "github.com/openstack-k8s-operators/cinder-operator/api/v1beta1" glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1" @@ -354,5 +355,7 @@ func (r *OpenStackControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) err Owns(&redisv1.Redis{}). Owns(&octaviav1.Octavia{}). Owns(&routev1.Route{}). + Owns(&certmgrv1.Issuer{}). + Owns(&certmgrv1.Certificate{}). Complete(r) } diff --git a/controllers/core/suite_test.go b/controllers/core/suite_test.go deleted file mode 100644 index 34cdaf35d..000000000 --- a/controllers/core/suite_test.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2022. - -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 core - -/* -import ( - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - corev1beta1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecsWithDefaultAndCustomReporters(t, - "Controller Suite", - []Reporter{printer.NewlineReporter{}}) -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = corev1beta1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - -}, 60) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) - -*/ diff --git a/go.mod b/go.mod index 5df2333f1..93a763d1c 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,9 @@ require ( github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453 github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c + github.com/openstack-k8s-operators/lib-common/modules/test v0.3.1-0.20231011150636-e8a0540a3c32 github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 - github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 + github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20231020162407-336d5231e505 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20231019131906-d371f09abbc6 github.com/openstack-k8s-operators/octavia-operator/api v0.3.1-0.20231012052157-e3ff0e9c016d @@ -45,8 +46,10 @@ require ( github.com/go-logr/zapr v1.2.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 // indirect github.com/metal3-io/baremetal-operator/apis v0.3.1 // indirect github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.2.0 // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/tools v0.14.0 // indirect sigs.k8s.io/gateway-api v0.6.0 // indirect ) @@ -68,7 +71,7 @@ require ( github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/google/uuid v1.3.1 github.com/gophercloud/gophercloud v1.7.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -110,13 +113,12 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) -replace ( +replace ( //allow-merging // pin to k8s 0.26.x for now k8s.io/api => k8s.io/api v0.26.9 k8s.io/apimachinery => k8s.io/apimachinery v0.26.9 k8s.io/client-go => k8s.io/client-go v0.26.9 sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.14.6 - ) replace github.com/openstack-k8s-operators/openstack-operator/apis => ./apis diff --git a/go.sum b/go.sum index b1a6d36bf..a7c79659d 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 h1:VzM3TYHDgqPkettiP6I6q2jOeQFL4nrJM+UcAc4f6Fs= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0/go.mod h1:nqCI7aelBJU61wiBeeZWJ6oi4bJy5nrjkM6lWIMA4j0= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -162,10 +164,12 @@ github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.2023101 github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:+J/GuQH7/JJF8SJtWYWxgDsYlqDv+fucxwFNP+QTcKk= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f h1:WBoCIlbxWeg+hIaawVvQX/XiuEw80WHEHouw4H+0Ksk= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:baotYmNGQjRCgu/9LxjrFZzTIT2/4yLktJK8h/Fa0Ig= +github.com/openstack-k8s-operators/lib-common/modules/test v0.3.1-0.20231011150636-e8a0540a3c32 h1:JCMXaDSjy46ZaHLHb1j2uzGIy2RUmYRCsbtSPkuEUV8= +github.com/openstack-k8s-operators/lib-common/modules/test v0.3.1-0.20231011150636-e8a0540a3c32/go.mod h1:Apy5OTK60yj9cQgVZ0HcGq+CDsLDaaEydJtj8ca0IBk= github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 h1:8bi5MJwe9jea0gtFXpUQpYeQuDo7ULEUpOCwWi8XrXY= github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991/go.mod h1:HZ2ZuXYMpLJPqg+rUy/ZlNTCA2FnNp0fkRadCL4z7WA= -github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 h1:FB0xB6whYM6W4XIncYo2mPiOJWkFsIOWtCT+UOtvOaQ= -github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0/go.mod h1:xhiz5wFdKWwVM7BF/VYon4TT3NuUPXp/Pyn2hWcp0CE= +github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20231020162407-336d5231e505 h1:KeaoopKRpcBvkRXeJOeV5Kzy5rE8U2nO6sIz8EI4t+U= +github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20231020162407-336d5231e505/go.mod h1:xhiz5wFdKWwVM7BF/VYon4TT3NuUPXp/Pyn2hWcp0CE= github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 h1:pymyGAni8tJ5juel1rTbSx0sUxfjKVwM55eNSK7V5Ls= github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186/go.mod h1:cKr0xZME9DePkWqXbyKFjNprjCeJl5WdE/Xn+lYWY7I= github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20231019131906-d371f09abbc6 h1:sbosbVd31OagqdtMhYYKI++4PWs4KHDTfebMvegwFw0= @@ -252,6 +256,7 @@ 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go new file mode 100644 index 000000000..7bdf28c7d --- /dev/null +++ b/tests/functional/base_test.go @@ -0,0 +1,234 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" + corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" +) + +type Names struct { + Namespace string + OpenStackControlplaneName types.NamespacedName + KeystoneAPIName types.NamespacedName + MemcachedName types.NamespacedName + DBName types.NamespacedName + DBCell1Name types.NamespacedName + RabbitMQName types.NamespacedName + RabbitMQCell1Name types.NamespacedName + ServiceAccountName types.NamespacedName + RoleName types.NamespacedName + RoleBindingName types.NamespacedName + RootCAPublicName types.NamespacedName + RootCAInternalName types.NamespacedName + SelfSignedIssuerName types.NamespacedName + CABundleName types.NamespacedName +} + +func CreateNames(openstackControlplaneName types.NamespacedName) Names { + return Names{ + Namespace: openstackControlplaneName.Namespace, + OpenStackControlplaneName: openstackControlplaneName, + RootCAPublicName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "rootca-public"}, + RootCAInternalName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "rootca-internal"}, + SelfSignedIssuerName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "selfsigned-issuer"}, + CABundleName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "combined-ca-bundle"}, + ServiceAccountName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: openstackControlplaneName.Name}, + RoleName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: openstackControlplaneName.Name + "-role"}, + RoleBindingName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: openstackControlplaneName.Name + "-rolebinding"}, + KeystoneAPIName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "keystone", + }, + MemcachedName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "memcached", + }, + DBName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "openstack", + }, + DBCell1Name: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "openstack-cell1", + }, + RabbitMQName: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "rabbitmq", + }, + RabbitMQCell1Name: types.NamespacedName{ + Namespace: openstackControlplaneName.Namespace, + Name: "rabbitmq-cell1", + }, + } +} + +func GetDefaultOpenStackClientSpec() map[string]interface{} { + return map[string]interface{}{} +} + +func CreateOpenStackClient(name types.NamespacedName, spec map[string]interface{}) client.Object { + + raw := map[string]interface{}{ + "apiVersion": "client.openstack.org/v1beta1", + "kind": "OpenStackClient", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetOpenStackClient(name types.NamespacedName) *openstackclientv1.OpenStackClient { + instance := &openstackclientv1.OpenStackClient{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func CreateOpenStackControlPlane(name types.NamespacedName, spec map[string]interface{}) client.Object { + + raw := map[string]interface{}{ + "apiVersion": "core.openstack.org/v1beta1", + "kind": "OpenStackControlPlane", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDefaultOpenStackControlPlaneSpec() map[string]interface{} { + memcachedTemplate := map[string]interface{}{ + "memcached": map[string]interface{}{ + "replicas": 1, + }, + } + rabbitTemplate := map[string]interface{}{ + names.RabbitMQName.Name: map[string]interface{}{ + "replicas": 1, + }, + names.RabbitMQCell1Name.Name: map[string]interface{}{ + "replicas": 1, + }, + } + galeraTemplate := map[string]interface{}{ + names.DBName.Name: map[string]interface{}{ + "storageRequest": "500M", + }, + names.DBCell1Name.Name: map[string]interface{}{ + "storageRequest": "500M", + }, + } + keystoneTemplate := map[string]interface{}{ + "databaseInstance": names.KeystoneAPIName.Name, + "secret": "osp-secret", + } + ironicTemplate := map[string]interface{}{ + "ironicConductors": []interface{}{}, + } + + tlsTemplate := map[string]interface{}{ + "endpoint": map[string]interface{}{ + "public": map[string]interface{}{ + "enabled": true, + }, + }, + } + + return map[string]interface{}{ + "secret": "osp-secret", + "storageClass": "local-storage", + "tls": tlsTemplate, + "galera": map[string]interface{}{ + "enabled": true, + "templates": galeraTemplate, + }, + "rabbitmq": map[string]interface{}{ + "enabled": true, + "templates": rabbitTemplate, + }, + "memcached": map[string]interface{}{ + "enabled": true, + "templates": memcachedTemplate, + }, + "keystone": map[string]interface{}{ + "enabled": true, + "template": keystoneTemplate, + }, + "placement": map[string]interface{}{ + "enabled": false, + }, + "glance": map[string]interface{}{ + "enabled": false, + }, + "cinder": map[string]interface{}{ + "enabled": false, + }, + "ovn": map[string]interface{}{ + "enabled": false, + }, + "neutron": map[string]interface{}{ + "enabled": false, + }, + "swift": map[string]interface{}{ + "enabled": false, + }, + "nova": map[string]interface{}{ + "enabled": false, + }, + "redis": map[string]interface{}{ + "enabled": false, + }, + "ironic": map[string]interface{}{ + "enabled": false, + "template": ironicTemplate, + }, + } +} + +func GetOpenStackControlPlane(name types.NamespacedName) *corev1.OpenStackControlPlane { + instance := &corev1.OpenStackControlPlane{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} diff --git a/tests/functional/openstackclient_webhook_test.go b/tests/functional/openstackclient_webhook_test.go new file mode 100644 index 000000000..1fa3eb870 --- /dev/null +++ b/tests/functional/openstackclient_webhook_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/types" + + openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" +) + +var _ = Describe("OpenStackClient Webhook", func() { + + var openstackclientName types.NamespacedName + + BeforeEach(func() { + + openstackclientName = types.NamespacedName{ + Name: "foo", + Namespace: namespace, + } + + err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + Expect(err).NotTo(HaveOccurred()) + }) + + When("A OpenStackClient instance is created without container images", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateOpenStackClient(openstackclientName, GetDefaultOpenStackClientSpec())) + }) + + It("should have the defaults initialized by webhook", func() { + OpenStackClient := GetOpenStackClient(openstackclientName) + Expect(OpenStackClient.Spec.ContainerImage).Should(Equal( + openstackclientv1.OpenStackClientContainerImage, + )) + }) + }) + + When("A OpenStackClient instance is created with container images", func() { + BeforeEach(func() { + openstackclientSpec := GetDefaultOpenStackClientSpec() + openstackclientSpec["containerImage"] = "api-container-image" + DeferCleanup(th.DeleteInstance, CreateOpenStackClient(openstackclientName, openstackclientSpec)) + }) + + It("should use the given values", func() { + OpenStackClient := GetOpenStackClient(openstackclientName) + Expect(OpenStackClient.Spec.ContainerImage).Should(Equal( + "api-container-image", + )) + }) + }) +}) diff --git a/tests/functional/openstackoperator_controller_test.go b/tests/functional/openstackoperator_controller_test.go new file mode 100644 index 000000000..6b6f2f776 --- /dev/null +++ b/tests/functional/openstackoperator_controller_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "k8s.io/utils/ptr" +) + +var _ = Describe("OpenStackOperator controller", func() { + BeforeEach(func() { + // lib-common uses OPERATOR_TEMPLATES env var to locate the "templates" + // directory of the operator. We need to set them othervise lib-common + // will fail to generate the ConfigMap as it does not find common.sh + err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + Expect(err).NotTo(HaveOccurred()) + }) + + When("A default OpenStackControlplane instance is created", func() { + BeforeEach(func() { + // (mschuppert) create root CA secret as there is no certmanager running. + // it is not used, just to make sure reconcile proceeds and creates the ca-bundle. + Eventually(func(g Gomega) { + th.CreateSecret( + names.RootCAPublicName, + map[string][]byte{ + "ca.crt": []byte("test"), + "tls.crt": []byte("test"), + "tls.key": []byte("test"), + }) + }, timeout, interval).Should(Succeed()) + + DeferCleanup( + th.DeleteInstance, + CreateOpenStackControlPlane(names.OpenStackControlplaneName, GetDefaultOpenStackControlPlaneSpec()), + ) + }) + + It("should have the Spec fields defaulted", func() { + OSCtlplane := GetOpenStackControlPlane(names.OpenStackControlplaneName) + Expect(OSCtlplane.Spec.Galera.Enabled).Should(BeTrue()) + Expect(OSCtlplane.Spec.Rabbitmq.Enabled).Should(BeTrue()) + Expect(OSCtlplane.Spec.Memcached.Enabled).Should(BeTrue()) + Expect(OSCtlplane.Spec.Keystone.Enabled).Should(BeTrue()) + Expect(OSCtlplane.Spec.TLS.Endpoint[service.EndpointPublic].Enabled).Should(BeTrue()) + Expect(OSCtlplane.Spec.TLS.Endpoint[service.EndpointInternal].Enabled).Should(BeFalse()) + + // galera exists + Eventually(func(g Gomega) { + db := mariadb.GetGalera(names.DBName) + g.Expect(db).Should(Not(BeNil())) + }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + db := mariadb.GetGalera(names.DBCell1Name) + g.Expect(db).Should(Not(BeNil())) + }, timeout, interval).Should(Succeed()) + + // memcached exists + Eventually(func(g Gomega) { + memcached := infra.GetMemcached(names.MemcachedName) + g.Expect(memcached).Should(Not(BeNil())) + g.Expect(memcached.Spec.Replicas).Should(Equal(ptr.To[int32](1))) + }, timeout, interval).Should(Succeed()) + + // TODO rabbitmq exists + + // keystone exists + Eventually(func(g Gomega) { + keystoneAPI := keystone.GetKeystoneAPI(names.KeystoneAPIName) + g.Expect(keystoneAPI).Should(Not(BeNil())) + }, timeout, interval).Should(Succeed()) + }) + + It("should create selfsigned issuer and public CA and issuer", func() { + OSCtlplane := GetOpenStackControlPlane(names.OpenStackControlplaneName) + + Expect(OSCtlplane.Spec.TLS.Endpoint[service.EndpointPublic].Enabled).Should(BeTrue()) + Expect(OSCtlplane.Spec.TLS.Endpoint[service.EndpointInternal].Enabled).Should(BeFalse()) + + // creates selfsigned issuer + Eventually(func(g Gomega) { + crtmgr.GetIssuer(names.SelfSignedIssuerName) + }, timeout, interval).Should(Succeed()) + + // creates public root CA and issuer + Eventually(func(g Gomega) { + // ca cert + cert := crtmgr.GetCert(names.RootCAPublicName) + g.Expect(cert).Should(Not(BeNil())) + g.Expect(cert.Spec.CommonName).Should(Equal(names.RootCAPublicName.Name)) + g.Expect(cert.Spec.IsCA).Should(BeTrue()) + g.Expect(cert.Spec.IssuerRef.Name).Should(Equal(names.SelfSignedIssuerName.Name)) + g.Expect(cert.Spec.SecretName).Should(Equal(names.RootCAPublicName.Name)) + // issuer + issuer := crtmgr.GetIssuer(names.RootCAPublicName) + g.Expect(issuer).Should(Not(BeNil())) + g.Expect(issuer.Spec.CA.SecretName).Should(Equal(names.RootCAPublicName.Name)) + + }, timeout, interval).Should(Succeed()) + }) + + It("should create ca bundle", func() { + crtmgr.GetCert(names.RootCAPublicName) + crtmgr.GetIssuer(names.RootCAPublicName) + + Eventually(func(g Gomega) { + th.GetSecret(names.RootCAPublicName) + caBundle := th.GetSecret(names.CABundleName) + g.Expect(len(caBundle.Data)).Should(Equal(int(1))) + }, timeout, interval).Should(Succeed()) + }) + }) +}) diff --git a/tests/functional/suite_test.go b/tests/functional/suite_test.go new file mode 100644 index 000000000..0866bbd71 --- /dev/null +++ b/tests/functional/suite_test.go @@ -0,0 +1,350 @@ +package functional_test + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/zap/zapcore" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + routev1 "github.com/openshift/api/route/v1" + rabbitmqv2 "github.com/rabbitmq/cluster-operator/v2/api/v1beta1" + + cinderv1 "github.com/openstack-k8s-operators/cinder-operator/api/v1beta1" + glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1" + heatv1 "github.com/openstack-k8s-operators/heat-operator/api/v1beta1" + horizonv1 "github.com/openstack-k8s-operators/horizon-operator/api/v1beta1" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" + networkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" + ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1" + novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" + octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" + corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + ovnv1 "github.com/openstack-k8s-operators/ovn-operator/api/v1beta1" + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + swiftv1 "github.com/openstack-k8s-operators/swift-operator/api/v1beta1" + telemetryv1 "github.com/openstack-k8s-operators/telemetry-operator/api/v1beta1" + + client_ctrl "github.com/openstack-k8s-operators/openstack-operator/controllers/client" + core_ctrl "github.com/openstack-k8s-operators/openstack-operator/controllers/core" + + infra_test "github.com/openstack-k8s-operators/infra-operator/apis/test/helpers" + keystone_test "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" + certmanager_test "github.com/openstack-k8s-operators/lib-common/modules/certmanager/test/helpers" + common_test "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + test "github.com/openstack-k8s-operators/lib-common/modules/test" + mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + logger logr.Logger + th *common_test.TestHelper + keystone *keystone_test.TestHelper + mariadb *mariadb_test.TestHelper + infra *infra_test.TestHelper + crtmgr *certmanager_test.TestHelper + namespace string + names Names +) + +const ( + timeout = time.Second * 5 + + SecretName = "test-osp-secret" + + interval = time.Millisecond * 200 +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), func(o *zap.Options) { + o.Development = true + o.TimeEncoder = zapcore.ISO8601TimeEncoder + })) + + ctx, cancel = context.WithCancel(context.TODO()) + + routev1CRDs, err := test.GetOpenShiftCRDDir("route/v1", "../../go.mod") + Expect(err).ShouldNot(HaveOccurred()) + mariaDBCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/mariadb-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + infraCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/infra-operator/apis", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + cinderv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/cinder-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + glancev1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/glance-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + heatv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/heat-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + horizonv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/horizon-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + ironicv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/ironic-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + keystonev1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/keystone-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + manilav1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/manila-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + neutronv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/neutron-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + novav1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/nova-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + octaviav1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/octavia-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + ovnv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/ovn-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + placementv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/placement-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + swiftv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/swift-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + telemetryv1CRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/telemetry-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + rabbitmqv2CRDs, err := test.GetCRDDirFromModule( + "github.com/rabbitmq/cluster-operator/v2", "../../go.mod", "config/crd/bases") + Expect(err).ShouldNot(HaveOccurred()) + certmgrv1CRDs, err := test.GetOpenShiftCRDDir("cert-manager/v1", "../../go.mod") + Expect(err).ShouldNot(HaveOccurred()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + routev1CRDs, + mariaDBCRDs, + infraCRDs, + cinderv1CRDs, + glancev1CRDs, + heatv1CRDs, + horizonv1CRDs, + ironicv1CRDs, + keystonev1CRDs, + manilav1CRDs, + neutronv1CRDs, + novav1CRDs, + octaviav1CRDs, + ovnv1CRDs, + placementv1CRDs, + swiftv1CRDs, + telemetryv1CRDs, + rabbitmqv2CRDs, + certmgrv1CRDs, + }, + ErrorIfCRDPathMissing: true, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + // NOTE(gibi): if localhost is resolved to ::1 (ipv6) then starting + // the webhook fails as it try to parse the address as ipv4 and + // failing on the colons in ::1 + LocalServingHost: "127.0.0.1", + }, + } + + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = openstackclientv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = corev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = routev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = cinderv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = glancev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = heatv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = horizonv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = memcachedv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = redisv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = ironicv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = keystonev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = manilav1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = mariadbv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = neutronv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = novav1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = octaviav1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = ovnv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = placementv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = swiftv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = telemetryv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = rabbitmqv2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = certmgrv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = networkv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + logger = ctrl.Log.WithName("---Test---") + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + th = common_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(th).NotTo(BeNil()) + keystone = keystone_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(keystone).NotTo(BeNil()) + mariadb = mariadb_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(mariadb).NotTo(BeNil()) + infra = infra_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(infra).NotTo(BeNil()) + crtmgr = certmanager_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(crtmgr).NotTo(BeNil()) + + // Start the controller-manager if goroutine + webhookInstallOptions := &testEnv.WebhookInstallOptions + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + // NOTE(gibi): disable metrics reporting in test to allow + // parallel test execution. Otherwise each instance would like to + // bind to the same port + MetricsBindAddress: "0", + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + }) + Expect(err).ToNot(HaveOccurred()) + + kclient, err := kubernetes.NewForConfig(cfg) + Expect(err).ToNot(HaveOccurred(), "failed to create kclient") + + err = (&openstackclientv1.OpenStackClient{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + err = (&corev1.OpenStackControlPlane{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + openstackclientv1.SetupDefaults() + corev1.SetupDefaults() + + err = (&client_ctrl.OpenStackClientReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + Log: ctrl.Log.WithName("controllers").WithName("OpenStackClient"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&core_ctrl.OpenStackControlPlaneReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + Log: ctrl.Log.WithName("controllers").WithName("OpenStackControlPlane"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: 10 * time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = BeforeEach(func() { + // NOTE(gibi): We need to create a unique namespace for each test run + // as namespaces cannot be deleted in a locally running envtest. See + // https://book.kubebuilder.io/reference/envtest.html#namespace-usage-limitation + namespace = uuid.New().String() + th.CreateNamespace(namespace) + // We still request the delete of the Namespace to properly cleanup if + // we run the test in an existing cluster. + DeferCleanup(th.DeleteNamespace, namespace) + + openstackControlplaneName := types.NamespacedName{ + Namespace: namespace, + Name: uuid.New().String()[:25], + } + + names = CreateNames(openstackControlplaneName) +}) From 094241c440415412378c422deef717f82cb2b4ec Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Mon, 23 Oct 2023 12:23:53 +0200 Subject: [PATCH 3/3] [TLS] update kuttl tests for tls public endpoints --- tests/kuttl/common/assert-sample-deployment.yaml | 10 ++++++++++ .../tests/collapsed/01-assert-collapsed-cell.yaml | 10 ++++++++++ .../galera-3replicas/01-assert-galera-3replicas.yaml | 10 ++++++++++ tests/kuttl/tests/galera-basic/01-assert-galera.yaml | 10 ++++++++++ 4 files changed, 40 insertions(+) diff --git a/tests/kuttl/common/assert-sample-deployment.yaml b/tests/kuttl/common/assert-sample-deployment.yaml index 9e1b69611..2e193af53 100644 --- a/tests/kuttl/common/assert-sample-deployment.yaml +++ b/tests/kuttl/common/assert-sample-deployment.yaml @@ -139,12 +139,22 @@ spec: replicas: 1 swiftProxy: replicas: 1 + tls: + endpoint: + internal: + enabled: false + public: + enabled: true status: conditions: - message: Setup complete reason: Ready status: "True" type: Ready + - message: OpenStackControlPlane CAs completed + reason: Ready + status: "True" + type: OpenStackControlPlaneCAReadyCondition - message: OpenStackControlPlane Ceilometer completed reason: Ready status: "True" diff --git a/tests/kuttl/tests/collapsed/01-assert-collapsed-cell.yaml b/tests/kuttl/tests/collapsed/01-assert-collapsed-cell.yaml index 2038be6c9..e1e22c63f 100644 --- a/tests/kuttl/tests/collapsed/01-assert-collapsed-cell.yaml +++ b/tests/kuttl/tests/collapsed/01-assert-collapsed-cell.yaml @@ -104,12 +104,22 @@ spec: service: CeilometerPassword secret: osp-secret serviceUser: ceilometer + tls: + endpoint: + internal: + enabled: false + public: + enabled: true status: conditions: - message: Setup complete reason: Ready status: "True" type: Ready + - message: OpenStackControlPlane CAs completed + reason: Ready + status: "True" + type: OpenStackControlPlaneCAReadyCondition - message: OpenStackControlPlane Ceilometer completed reason: Ready status: "True" diff --git a/tests/kuttl/tests/galera-3replicas/01-assert-galera-3replicas.yaml b/tests/kuttl/tests/galera-3replicas/01-assert-galera-3replicas.yaml index 794f381df..1d0b51309 100644 --- a/tests/kuttl/tests/galera-3replicas/01-assert-galera-3replicas.yaml +++ b/tests/kuttl/tests/galera-3replicas/01-assert-galera-3replicas.yaml @@ -114,12 +114,22 @@ spec: service: CeilometerPassword secret: osp-secret serviceUser: ceilometer + tls: + endpoint: + internal: + enabled: false + public: + enabled: true status: conditions: - message: Setup complete reason: Ready status: "True" type: Ready + - message: OpenStackControlPlane CAs completed + reason: Ready + status: "True" + type: OpenStackControlPlaneCAReadyCondition - message: OpenStackControlPlane Ceilometer completed reason: Ready status: "True" diff --git a/tests/kuttl/tests/galera-basic/01-assert-galera.yaml b/tests/kuttl/tests/galera-basic/01-assert-galera.yaml index ccd7a9cc0..96650f9ab 100644 --- a/tests/kuttl/tests/galera-basic/01-assert-galera.yaml +++ b/tests/kuttl/tests/galera-basic/01-assert-galera.yaml @@ -133,12 +133,22 @@ spec: replicas: 1 swiftProxy: replicas: 1 + tls: + endpoint: + internal: + enabled: false + public: + enabled: true status: conditions: - message: Setup complete reason: Ready status: "True" type: Ready + - message: OpenStackControlPlane CAs completed + reason: Ready + status: "True" + type: OpenStackControlPlaneCAReadyCondition - message: OpenStackControlPlane Ceilometer completed reason: Ready status: "True"