diff --git a/modules/certmanager/certificate.go b/modules/certmanager/certificate.go index d4e89fea..850e5469 100644 --- a/modules/certmanager/certificate.go +++ b/modules/certmanager/certificate.go @@ -19,11 +19,13 @@ package certmanager import ( "context" "fmt" + "sort" "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/net" "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" @@ -66,10 +68,15 @@ func NewCertificate( certificate *certmgrv1.Certificate, timeout time.Duration, ) *Certificate { - return &Certificate{ + crt := &Certificate{ certificate: certificate, timeout: timeout, } + + crt.certificate.Spec.IPAddresses = net.SortIPs(crt.certificate.Spec.IPAddresses) + sort.Strings(crt.certificate.Spec.DNSNames) + + return crt } // Cert returns an initialized certificate request obj. diff --git a/modules/certmanager/test/functional/certmanager_test.go b/modules/certmanager/test/functional/certmanager_test.go index 3423ec1b..f7ead818 100644 --- a/modules/certmanager/test/functional/certmanager_test.go +++ b/modules/certmanager/test/functional/certmanager_test.go @@ -166,6 +166,66 @@ var _ = Describe("certmanager module", func() { Expect(cert.Labels["f"]).To(Equal("l")) }) + It("creates certificate with orderdered DNSNames", func() { + c := certmanager.NewCertificate( + certmanager.Cert( + names.CertName.Name, + names.CertName.Namespace, + map[string]string{"f": "l"}, + certmgrv1.CertificateSpec{ + CommonName: "keystone-public-openstack.apps-crc.testing", + DNSNames: []string{ + "keystone-public-openstack.apps-crc.testing", + "keystone-public-openstack", + }, + IssuerRef: certmgrmetav1.ObjectReference{ + Kind: "Issuer", + Name: "issuerName", + }, + SecretName: "secret", + }, + ), + timeout, + ) + + _, _, err := c.CreateOrPatch(ctx, h, nil) + Expect(err).ShouldNot(HaveOccurred()) + cert := th.GetCert(names.CertName) + Expect(cert.Spec.DNSNames[0]).To(Equal("keystone-public-openstack")) + Expect(cert.Spec.DNSNames[1]).To(Equal("keystone-public-openstack.apps-crc.testing")) + }) + + It("creates certificate with orderdered IPAddresses", func() { + c := certmanager.NewCertificate( + certmanager.Cert( + names.CertName.Name, + names.CertName.Namespace, + map[string]string{"f": "l"}, + certmgrv1.CertificateSpec{ + CommonName: "keystone-public-openstack.apps-crc.testing", + IPAddresses: []string{ + "2.2.2.2", + "1.1.1.1", + "2.2.2.1", + }, + IssuerRef: certmgrmetav1.ObjectReference{ + Kind: "Issuer", + Name: "issuerName", + }, + SecretName: "secret", + }, + ), + timeout, + ) + + _, _, err := c.CreateOrPatch(ctx, h, nil) + Expect(err).ShouldNot(HaveOccurred()) + cert := th.GetCert(names.CertName) + Expect(cert.Spec.IPAddresses[0]).To(Equal("1.1.1.1")) + Expect(cert.Spec.IPAddresses[1]).To(Equal("2.2.2.1")) + Expect(cert.Spec.IPAddresses[2]).To(Equal("2.2.2.2")) + }) + It("deletes certificate", func() { c := certmanager.NewCertificate( certmanager.Cert( diff --git a/modules/common/net/ip.go b/modules/common/net/ip.go new file mode 100644 index 00000000..6d3bb774 --- /dev/null +++ b/modules/common/net/ip.go @@ -0,0 +1,46 @@ +/* +Copyright 2023 Red Hat + +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 net + +import ( + "bytes" + "net" + "sort" +) + +// SortIPs - Get network-attachment-definition with name in namespace +func SortIPs( + ips []string, +) []string { + netIPs := make([]net.IP, 0, len(ips)) + + for _, ip := range ips { + netIPs = append(netIPs, net.ParseIP(ip)) + } + + sort.Slice(netIPs, func(i, j int) bool { + return bytes.Compare(netIPs[i], netIPs[j]) < 0 + }) + + sortedIPs := make([]string, 0, len(netIPs)) + + for _, ip := range netIPs { + sortedIPs = append(sortedIPs, ip.String()) + } + + return sortedIPs +} diff --git a/modules/common/net/ip_test.go b/modules/common/net/ip_test.go new file mode 100644 index 00000000..34653544 --- /dev/null +++ b/modules/common/net/ip_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2023 Red Hat + +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 net + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestSortIPs(t *testing.T) { + + tests := []struct { + name string + ips []string + want []string + }{ + { + name: "empty ip list", + ips: []string{}, + want: []string{}, + }, + { + name: "IPv4 - single ip in list", + ips: []string{"1.1.1.1"}, + want: []string{"1.1.1.1"}, + }, + { + name: "IPv4 - already sorted list", + ips: []string{"1.1.1.1", "2.2.2.2"}, + want: []string{"1.1.1.1", "2.2.2.2"}, + }, + { + name: "IPv4 - unsorted sorted list", + ips: []string{"2.2.2.2", "1.1.1.1"}, + want: []string{"1.1.1.1", "2.2.2.2"}, + }, + { + name: "IPv4 - another unsorted sorted list", + ips: []string{"2.2.2.2", "1.1.1.2", "1.1.1.1"}, + want: []string{"1.1.1.1", "1.1.1.2", "2.2.2.2"}, + }, + { + name: "IPv6 - single ip in list", + ips: []string{"fd00:bbbb::1"}, + want: []string{"fd00:bbbb::1"}, + }, + { + name: "IPv6 - already sorted list", + ips: []string{"fd00:bbbb::1", "fd00:bbbb::2"}, + want: []string{"fd00:bbbb::1", "fd00:bbbb::2"}, + }, + { + name: "IPv6 - unsorted sorted list", + ips: []string{"fd00:bbbb::2", "fd00:bbbb::1"}, + want: []string{"fd00:bbbb::1", "fd00:bbbb::2"}, + }, + { + name: "IPv6 - another unsorted sorted list", + ips: []string{"fd00:bbbb::2", "fd00:aaaa::1", "fd00:bbbb::1"}, + want: []string{"fd00:aaaa::1", "fd00:bbbb::1", "fd00:bbbb::2"}, + }, + { + name: "IPV4 and IPv6 - unsorted sorted list", + ips: []string{"fd00:bbbb::2", "fd00:aaaa::1", "fd00:bbbb::1", "1.1.1.1"}, + want: []string{"1.1.1.1", "fd00:aaaa::1", "fd00:bbbb::1", "fd00:bbbb::2"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + sortedIPs := SortIPs(tt.ips) + g.Expect(sortedIPs).NotTo(BeNil()) + g.Expect(sortedIPs).To(HaveLen(len(tt.want))) + g.Expect(sortedIPs).To(BeEquivalentTo(tt.want)) + }) + } +}