diff --git a/modules/certmanager/certificate.go b/modules/certmanager/certificate.go index a15f4811..562ea824 100644 --- a/modules/certmanager/certificate.go +++ b/modules/certmanager/certificate.go @@ -22,13 +22,18 @@ import ( "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/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/util" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + 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" ) // Certificate - @@ -65,7 +70,6 @@ func Cert( namespace string, labels map[string]string, spec certmgrv1.CertificateSpec, - ) *certmgrv1.Certificate { return &certmgrv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ @@ -128,3 +132,75 @@ func (c *Certificate) Delete( return nil } + +// EnsureCert - creates a certificate for hostnames, ensures the sercret has the required key/cert and return the secret +func EnsureCert( + ctx context.Context, + helper *helper.Helper, + issuerName string, + certName string, + duration *time.Duration, + hostnames []string, + labels map[string]string, +) (*k8s_corev1.Secret, ctrl.Result, error) { + // get issuer + issuer := &certmgrv1.Issuer{} + namespace := helper.GetBeforeObject().GetNamespace() + + err := helper.GetClient().Get(ctx, types.NamespacedName{Name: issuerName, Namespace: namespace}, issuer) + if err != nil { + err = fmt.Errorf("Error getting issuer %s/%s - %w", issuerName, namespace, err) + + return nil, ctrl.Result{}, err + } + + // default the cert duration to one year (default is 90days) + if duration == nil { + duration = ptr.To(time.Hour * 24 * 365) + } + + certSecretName := "cert-" + certName + certReq := Cert( + certName, + namespace, + labels, + certmgrv1.CertificateSpec{ + CommonName: hostnames[0], + DNSNames: hostnames, + Duration: &metav1.Duration{ + Duration: *duration, + }, + IssuerRef: certmgrmetav1.ObjectReference{ + Name: issuer.Name, + Kind: issuer.Kind, + Group: issuer.GroupVersionKind().Group, + }, + SecretName: certSecretName, + // TODO Usages, e.g. for client cert + }, + ) + + cert := NewCertificate(certReq, 5) + ctrlResult, err := cert.CreateOrPatch(ctx, helper) + if err != nil { + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return nil, ctrlResult, nil + } + + // get cert secret + certSecret, _, err := secret.GetSecret(ctx, helper, certSecretName, namespace) + if err != nil { + return nil, ctrl.Result{}, err + } + + // check if secret has the right keys + _, hasTLSKey := certSecret.Data["tls.key"] + _, hasTLSCert := certSecret.Data["tls.crt"] + if !hasTLSCert || !hasTLSKey { + err := fmt.Errorf("TLS secret %s in namespace %s does not have the fields tls.crt and tls.key", certSecretName, namespace) + return nil, ctrl.Result{}, err + } + + return certSecret, ctrl.Result{}, nil +} diff --git a/modules/certmanager/go.mod b/modules/certmanager/go.mod index e6da58bb..b0340cfd 100644 --- a/modules/certmanager/go.mod +++ b/modules/certmanager/go.mod @@ -75,7 +75,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect; indirect // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect; indirect // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect ) diff --git a/modules/common/endpoint/endpoint.go b/modules/common/endpoint/endpoint.go index 4f513346..ee69758c 100644 --- a/modules/common/endpoint/endpoint.go +++ b/modules/common/endpoint/endpoint.go @@ -194,6 +194,10 @@ func ExposeEndpoints( // Create the route if it is public endpoint if endpointType == service.EndpointPublic { // Create the route + routeOverride := []route.OverrideSpec{} + if data.RouteOverride != nil { + routeOverride = append(routeOverride, *data.RouteOverride) + } // TODO TLS route, err := route.NewRoute( route.GenericRoute(&route.GenericRouteDetails{ @@ -204,7 +208,7 @@ func ExposeEndpoints( TargetPortName: endpointName, }), timeout, - data.RouteOverride, + routeOverride, ) if err != nil { return endpointMap, ctrl.Result{}, err diff --git a/modules/common/route/route.go b/modules/common/route/route.go index 60845063..67cc3c52 100644 --- a/modules/common/route/route.go +++ b/modules/common/route/route.go @@ -38,7 +38,7 @@ import ( func NewRoute( route *routev1.Route, timeout time.Duration, - override *OverrideSpec, + overrides []OverrideSpec, ) (*Route, error) { r := &Route{ route: route, @@ -46,7 +46,7 @@ func NewRoute( } // patch route with possible overrides of Labels, Annotations and Spec - if override != nil { + for _, override := range overrides { if override.EmbeddedLabelsAnnotations != nil { if override.Labels != nil { r.route.Labels = util.MergeStringMaps(override.Labels, r.route.Labels) diff --git a/modules/common/test/functional/route_test.go b/modules/common/test/functional/route_test.go index 7020d661..317df880 100644 --- a/modules/common/test/functional/route_test.go +++ b/modules/common/test/functional/route_test.go @@ -75,7 +75,7 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{}, + nil, ) Expect(err).ShouldNot(HaveOccurred()) @@ -97,11 +97,13 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ - Labels: map[string]string{ - "foo": "b", - "replace": "b", + []route.OverrideSpec{ + { + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Labels: map[string]string{ + "foo": "b", + "replace": "b", + }, }, }, }, @@ -123,11 +125,13 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ - Annotations: map[string]string{ - "foo": "b", - "replace": "b", + []route.OverrideSpec{ + { + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Annotations: map[string]string{ + "foo": "b", + "replace": "b", + }, }, }, }, @@ -149,9 +153,11 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - Host: "custom.host.domain", + []route.OverrideSpec{ + { + Spec: &route.Spec{ + Host: "custom.host.domain", + }, }, }, ) @@ -167,9 +173,11 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - Subdomain: "subdomain", + []route.OverrideSpec{ + { + Spec: &route.Spec{ + Subdomain: "subdomain", + }, }, }, ) @@ -185,9 +193,11 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - Path: "/some/path", + []route.OverrideSpec{ + { + Spec: &route.Spec{ + Path: "/some/path", + }, }, }, ) @@ -203,11 +213,13 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - To: route.TargetReference{ - Name: "my-custom-service", - Weight: ptr.To[int32](10), + []route.OverrideSpec{ + { + Spec: &route.Spec{ + To: route.TargetReference{ + Name: "my-custom-service", + Weight: ptr.To[int32](10), + }, }, }, }, @@ -226,13 +238,15 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - AlternateBackends: []route.TargetReference{ - { - Kind: "Service", - Name: "my-alternate-service", - Weight: ptr.To[int32](200), + []route.OverrideSpec{ + { + Spec: &route.Spec{ + AlternateBackends: []route.TargetReference{ + { + Kind: "Service", + Name: "my-alternate-service", + Weight: ptr.To[int32](200), + }, }, }, }, @@ -251,10 +265,12 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - Port: &routev1.RoutePort{ - TargetPort: intstr.FromInt(8080), + []route.OverrideSpec{ + { + Spec: &route.Spec{ + Port: &routev1.RoutePort{ + TargetPort: intstr.FromInt(8080), + }, }, }, }, @@ -271,13 +287,56 @@ var _ = Describe("route package", func() { r, err := route.NewRoute( getExampleRoute(namespace), timeout, - &route.OverrideSpec{ - Spec: &route.Spec{ - TLS: &routev1.TLSConfig{ - Termination: routev1.TLSTerminationEdge, - Certificate: "cert", - Key: "key", - CACertificate: "cacert", + []route.OverrideSpec{ + { + Spec: &route.Spec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: "cert", + Key: "key", + CACertificate: "cacert", + }, + }, + }, + }, + ) + Expect(err).ShouldNot(HaveOccurred()) + + _, err = r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + Expect(rv1.Spec.TLS.Termination).To(Equal(routev1.TLSTerminationEdge)) + Expect(rv1.Spec.TLS.Certificate).To(Equal("cert")) + Expect(rv1.Spec.TLS.Key).To(Equal("key")) + Expect(rv1.Spec.TLS.CACertificate).To(Equal("cacert")) + }) + + It("merges multipe overrides different parameters", func() { + r, err := route.NewRoute( + getExampleRoute(namespace), + timeout, + []route.OverrideSpec{ + { + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Labels: map[string]string{ + "foo": "b", + "replace": "b", + }, + }, + }, + { + Spec: &route.Spec{ + Host: "custom.host.domain", + }, + }, + { + Spec: &route.Spec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: "cert", + Key: "key", + CACertificate: "cacert", + }, }, }, }, @@ -287,9 +346,53 @@ var _ = Describe("route package", func() { _, err = r.CreateOrPatch(ctx, h) Expect(err).ShouldNot(HaveOccurred()) rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + // non overridden label exists + Expect(rv1.Labels["label"]).To(Equal("a")) + // adds new label + Expect(rv1.Labels["foo"]).To(Equal("b")) + // override replaces existing label + Expect(rv1.Labels["replace"]).To(Equal("b")) + + Expect(rv1.Spec.Host).To(Equal("custom.host.domain")) + Expect(rv1.Spec.TLS.Termination).To(Equal(routev1.TLSTerminationEdge)) Expect(rv1.Spec.TLS.Certificate).To(Equal("cert")) Expect(rv1.Spec.TLS.Key).To(Equal("key")) Expect(rv1.Spec.TLS.CACertificate).To(Equal("cacert")) }) + + It("merges multipe overrides same parameters, last wins", func() { + r, err := route.NewRoute( + getExampleRoute(namespace), + timeout, + []route.OverrideSpec{ + { + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Labels: map[string]string{ + "foo": "b", + "replace": "b", + }, + }, + }, + { + EmbeddedLabelsAnnotations: &route.EmbeddedLabelsAnnotations{ + Labels: map[string]string{ + "replace": "2ndoverridewins", + }, + }, + }, + }, + ) + Expect(err).ShouldNot(HaveOccurred()) + + _, err = r.CreateOrPatch(ctx, h) + Expect(err).ShouldNot(HaveOccurred()) + rv1 := th.AssertRouteExists(types.NamespacedName{Namespace: namespace, Name: "test-route"}) + // non overridden label exists + Expect(rv1.Labels["label"]).To(Equal("a")) + // adds new label + Expect(rv1.Labels["foo"]).To(Equal("b")) + // override replaces existing label + Expect(rv1.Labels["replace"]).To(Equal("2ndoverridewins")) + }) })