From 0e18ceed626dee4f330834424bc94fd239bafc06 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 23:00:08 +0000 Subject: [PATCH 01/10] Update kubernetes monorepo to v0.36.1 (#1093) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> --- go.mod | 14 +++++++------- go.sum | 24 ++++++++++++------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 4d78bb08..93763d71 100644 --- a/go.mod +++ b/go.mod @@ -22,13 +22,13 @@ require ( google.golang.org/grpc v1.81.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.36.0 - k8s.io/apimachinery v0.36.0 - k8s.io/client-go v0.36.0 - k8s.io/cloud-provider v0.36.0 - k8s.io/component-base v0.36.0 + k8s.io/api v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 + k8s.io/cloud-provider v0.36.1 + k8s.io/component-base v0.36.1 k8s.io/klog/v2 v2.140.0 - k8s.io/mount-utils v0.36.0 + k8s.io/mount-utils v0.36.1 k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 ) @@ -132,7 +132,7 @@ require ( k8s.io/controller-manager v0.36.0 // indirect k8s.io/kms v0.36.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/streaming v0.36.0 // indirect + k8s.io/streaming v0.36.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 375834af..ce295cb7 100644 --- a/go.sum +++ b/go.sum @@ -350,16 +350,16 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= -k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= -k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= -k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= -k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= -k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA= +k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI= k8s.io/component-helpers v0.36.0 h1:KznLAOD7oPxjaeheW4SOQijz9UtMO8Nvp89+lR8FYks= k8s.io/component-helpers v0.36.0/go.mod h1:BqZG+01Z97KR8GN9Stb8SiRmtn/EpZogriuQtpMCsLg= k8s.io/controller-manager v0.36.0 h1:SQoi2QplC2mI7v+rRRVeHtlQcGJVdz8qE86AN+uIT34= @@ -370,10 +370,10 @@ k8s.io/kms v0.36.0 h1:DPy0VDWi6hCgFMgzV5cNuSDrIROMRcJpTZ1GnB+D368= k8s.io/kms v0.36.0/go.mod h1:g91diTD9h0oJCCHkTb00krlF+Qm5HTnkWLi9Q/TpRoc= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/mount-utils v0.36.0 h1:ufsqGyCoPDh7p+6OIa1wv6oH9GqkQQ8XIfEOVfCV3g0= -k8s.io/mount-utils v0.36.0/go.mod h1:+I47UOG6FiUGVSy7VanjU/mQXLShMo3M7xBpGLzCub8= -k8s.io/streaming v0.36.0 h1:agnTxU+NFulUrtYzXUGKO3ndEa8jKwht1Kwn9nu9x+4= -k8s.io/streaming v0.36.0/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/mount-utils v0.36.1 h1:NWDFsdv+jfqPfa/LisnbEn1QyPNYjMNkmfEORXhyvZA= +k8s.io/mount-utils v0.36.1/go.mod h1:+I47UOG6FiUGVSy7VanjU/mQXLShMo3M7xBpGLzCub8= +k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= +k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= From c65b2bb3f93d59c7da3f221905466106c7304833 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 11:39:42 +0000 Subject: [PATCH 02/10] Update module github.com/stackitcloud/stackit-sdk-go/services/loadbalancer to v1.13.0 (#1104) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 93763d71..9b9772ef 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stackitcloud/stackit-sdk-go/core v0.26.0 github.com/stackitcloud/stackit-sdk-go/services/iaas v1.11.1 - github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.12.2 + github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.13.0 go.uber.org/mock v0.6.0 golang.org/x/sync v0.20.0 golang.org/x/sys v0.44.0 diff --git a/go.sum b/go.sum index ce295cb7..bf4e4122 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10 github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.11.1 h1:HcKqjwIjv4OAW1aWI0U/JWjnzTwzSvdr6DLasH940EU= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.11.1/go.mod h1:Ts06id0KejUlQWbpR+/rm+tKng6QkTuFV1VQTPJ4dA4= -github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.12.2 h1:3Xnt5lnMmqVWChvH8lYJwpRoRatoqXfHlZ12wgNwUD4= -github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.12.2/go.mod h1:+Ld3dn648I+YKcBV3fEkYpDSr3fel421+LurJGywSBs= +github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.13.0 h1:UuLNwFHjJCpL11y4F7B9oBKtZkxpu01VkNPILNkpex4= +github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.13.0/go.mod h1:+Ld3dn648I+YKcBV3fEkYpDSr3fel421+LurJGywSBs= github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.23.0 h1:u2C3oHNcc41Ba5cUqSPuqviDrYSRhpaC5+ELbuHHdwM= github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.23.0/go.mod h1:NEz3f+GV5G++BE9/MmZCsXJyCih7jtg0pZuSyG2sLEs= github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= From cb531ed28c1750c810adf73f3e390687d67cb9a9 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 17:59:42 +0000 Subject: [PATCH 03/10] Update module google.golang.org/grpc to v1.81.1 (#1109) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9b9772ef..4d4a000d 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( go.uber.org/mock v0.6.0 golang.org/x/sync v0.20.0 golang.org/x/sys v0.44.0 - google.golang.org/grpc v1.81.0 + google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.36.1 diff --git a/go.sum b/go.sum index bf4e4122..1590456d 100644 --- a/go.sum +++ b/go.sum @@ -332,8 +332,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= -google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 273f05bab2b3dd169e550aaa1126e6d23c33844f Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 17:59:46 +0000 Subject: [PATCH 04/10] Update module google.golang.org/grpc to v1.81.1 (#1109) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> From 6f42608dfe9649e678bf3ec5946d337ee11cec65 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 00:26:48 +0000 Subject: [PATCH 05/10] Update module github.com/onsi/ginkgo/v2 to v2.29.0 (#1120) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4d4a000d..cdaa88ff 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/uuid v1.6.0 github.com/kubernetes-csi/csi-lib-utils v0.23.2 github.com/kubernetes-csi/csi-test/v5 v5.4.0 - github.com/onsi/ginkgo/v2 v2.28.3 + github.com/onsi/ginkgo/v2 v2.29.0 github.com/onsi/gomega v1.40.0 github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 1590456d..efc84550 100644 --- a/go.sum +++ b/go.sum @@ -155,8 +155,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.28.3 h1:4JvMdwtFU0imd8fHx25OJXoDMRexnf8v5NHKYSTTji4= -github.com/onsi/ginkgo/v2 v2.28.3/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= +github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From 92e51b669bdeeb2fee88ca24fa053b79563b47a7 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 00:39:47 +0000 Subject: [PATCH 06/10] Update module github.com/onsi/gomega to v1.41.0 (#1121) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cdaa88ff..38fd68e3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/kubernetes-csi/csi-lib-utils v0.23.2 github.com/kubernetes-csi/csi-test/v5 v5.4.0 github.com/onsi/ginkgo/v2 v2.29.0 - github.com/onsi/gomega v1.40.0 + github.com/onsi/gomega v1.41.0 github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index efc84550..ef1a613a 100644 --- a/go.sum +++ b/go.sum @@ -157,8 +157,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.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From da47086862ab43998fd64dcef53af4c3b239cc46 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 01:17:46 +0000 Subject: [PATCH 07/10] Update dependency chainguard-dev/apko to v1.2.13 (#1130) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> --- hack/tools.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/tools.mk b/hack/tools.mk index 8cd7f127..d33b4034 100644 --- a/hack/tools.mk +++ b/hack/tools.mk @@ -11,7 +11,7 @@ GOLANGCI_LINT_VERSION ?= v2.12.2 # renovate: datasource=github-releases depName=uber-go/mock MOCKGEN_VERSION ?= v0.6.0 # renovate: datasource=github-releases depName=chainguard-dev/apko -APKO_VERSION ?= v1.2.12 +APKO_VERSION ?= v1.2.13 # renovate: datasource=github-releases depName=ko-build/ko KO_VERSION ?= v0.18.1 ENVTEST_VERSION ?= v0.0.0-20260317052337-b8d2b5b862fa From ef95fb7a0c3c109577013b1cd839c6e70568f8a6 Mon Sep 17 00:00:00 2001 From: "ske-renovate-ce[bot]" <163154779+ske-renovate-ce[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 17:59:46 +0000 Subject: [PATCH 08/10] Update module google.golang.org/grpc to v1.81.1 (#1109) Co-authored-by: ske-renovate-ce[bot] <163154779+ske-renovate-ce[bot]@users.noreply.github.com> From c6cb456d4e4ad03bc07a985a6adeb124c2a8017e Mon Sep 17 00:00:00 2001 From: Menekse Ceylan Date: Mon, 11 May 2026 10:26:48 +0200 Subject: [PATCH 09/10] added labels upon creation of alb and storing certificate --- pkg/alb/ingress/alb_spec.go | 38 ++++++++++++++++++++++++++++++-- pkg/alb/ingress/alb_spec_test.go | 13 +++++++++++ pkg/alb/ingress/certificate.go | 10 ++++++++- pkg/alb/ingress/update.go | 10 +++++++++ pkg/stackit/config/config.go | 3 ++- 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/pkg/alb/ingress/alb_spec.go b/pkg/alb/ingress/alb_spec.go index 207c60a6..cc513f50 100644 --- a/pkg/alb/ingress/alb_spec.go +++ b/pkg/alb/ingress/alb_spec.go @@ -17,6 +17,15 @@ import ( certsdk "github.com/stackitcloud/stackit-sdk-go/services/certificates/v2api" ) +const ( + // prefixCustomerLabel is the api prefix for all custom labels + prefixCustomerLabel = "lb.customer.label/" + + // LabelIngressClassUID is the unique key that identifies resources + // owned by a specific IngressClass. + LabelIngressClassUID = prefixCustomerLabel + "ingress-class-uid" +) + func (r *IngressClassReconciler) getAlbSpecForIngressClass(ctx context.Context, class *networkingv1.IngressClass) (*albsdk.CreateLoadBalancerPayload, []errorEvents, error) { ingresses, err := r.getIngressesForIngressClass(ctx, class) if err != nil { @@ -45,7 +54,7 @@ func (r *IngressClassReconciler) getAlbSpecForIngresses(ctx context.Context, cla errorList = append(errorList, listenerMergeError...) } - certNameToId, certificateErrorEvents := r.applyCertificates(ctx, certificates) + certNameToId, certificateErrorEvents := r.applyCertificates(ctx, class, certificates) errorList = append(errorList, certificateErrorEvents...) alb, albSpecErrorList, err := r.getAlbSpecForResources(ctx, class, listeners, targetPools, certNameToId) @@ -83,6 +92,28 @@ func (r *IngressClassReconciler) getAlbSpecForResources(ctx context.Context, cla alb.PlanId = &plan } + mergedLabels := make(map[string]string) + + // Add user labels, mind the limit + for k, v := range class.Labels { + if len(mergedLabels) < 64 { + mergedLabels[k] = v + } + } + + // Merge with existing global config labels + if r.ALBConfig.ApplicationLoadBalancer.ExtraLabels != nil { + for k, v := range r.ALBConfig.ApplicationLoadBalancer.ExtraLabels { + if len(mergedLabels) < 64 { + mergedLabels[k] = v + } + } + } + + // Add ownership label + mergedLabels[LabelIngressClassUID] = string(class.UID) + alb.Labels = &mergedLabels + for port, listener := range listeners { albsdkListener := albsdk.Listener{ Http: nil, @@ -379,7 +410,7 @@ func (r *IngressClassReconciler) getCertificateForSecretName(ctx context.Context }, nil } -func (r *IngressClassReconciler) applyCertificates(ctx context.Context, certificates albCertificates) (map[string]string, []errorEvents) { +func (r *IngressClassReconciler) applyCertificates(ctx context.Context, class *networkingv1.IngressClass, certificates albCertificates) (map[string]string, []errorEvents) { errorList := []errorEvents{} nameToID := map[string]string{} for name, certificate := range certificates { @@ -388,6 +419,9 @@ func (r *IngressClassReconciler) applyCertificates(ctx context.Context, certific ProjectId: &r.ALBConfig.Global.ProjectID, PrivateKey: new(string(certificate.privateKey)), PublicKey: new(string(certificate.publicKey)), + Labels: &map[string]string{ + LabelIngressClassUID: string(class.UID), + }, } response, err := r.CertificateClient.CreateCertificate(ctx, r.ALBConfig.Global.ProjectID, r.ALBConfig.Global.Region, createCertificatePayload) if err != nil { diff --git a/pkg/alb/ingress/alb_spec_test.go b/pkg/alb/ingress/alb_spec_test.go index 5f5dc4ce..dfc3f785 100644 --- a/pkg/alb/ingress/alb_spec_test.go +++ b/pkg/alb/ingress/alb_spec_test.go @@ -142,6 +142,19 @@ var _ = Describe("Node Controller", func() { Expect(*spec).To(BeEquivalentTo(albSpec)) }) + It("should work with labels", func() { + + reconciler.ALBConfig.ApplicationLoadBalancer.ExtraLabels = map[string]string{"managed-by": "alb-ingressClass"} + spec, errorEventList, err := reconciler.getAlbSpecForIngressClass(context.Background(), &ingressClass) + Expect(err).To(Succeed()) + Expect(errorEventList).To(BeEmpty()) + + albSpec.Labels = new(map[string]string{"managed-by": "alb-ingressClass"}) + + Expect(spec).ToNot(BeNil()) + Expect(*spec).To(BeEquivalentTo(albSpec)) + }) + It("should work with 2 ingresses different path", func() { ingress2 := testIngress(&ingressClass, &service) ingress2.Name = "ingress2" diff --git a/pkg/alb/ingress/certificate.go b/pkg/alb/ingress/certificate.go index 2c5f510c..e821732a 100644 --- a/pkg/alb/ingress/certificate.go +++ b/pkg/alb/ingress/certificate.go @@ -19,8 +19,16 @@ func (r *IngressClassReconciler) deleteAllCertsForClass(ctx context.Context, cla return nil // No certificates to clean up } + // using labels for certificates + targetUID := string(class.UID) + for _, cert := range certificatesList.Items { - if strings.HasPrefix(*cert.Name, shortUUID(string(class.UID))) { + if cert.Labels == nil { + // This part will go away when Labels are supported by Cert API + // do I need to check if nil + } + + if val, ok := (*cert.Labels)[LabelIngressClassUID]; ok && val == targetUID { err := r.CertificateClient.DeleteCertificate(ctx, r.ALBConfig.Global.ProjectID, r.ALBConfig.Global.Region, *cert.Id) if err != nil { return fmt.Errorf("failed to delete orphaned certificate %s: %v", *cert.Name, err) diff --git a/pkg/alb/ingress/update.go b/pkg/alb/ingress/update.go index cc28d304..97823b7a 100644 --- a/pkg/alb/ingress/update.go +++ b/pkg/alb/ingress/update.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "reflect" "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" albsdk "github.com/stackitcloud/stackit-sdk-go/services/alb/v2api" @@ -161,5 +162,14 @@ func updateNeeded(alb *albsdk.LoadBalancer, albPayload *albsdk.CreateLoadBalance } } + // Label comparison + // normalize pointers to prevent nil vs empty map issue + currentLabels := ptr.Deref(alb.Labels, map[string]string{}) + desiredLabels := ptr.Deref(albPayload.Labels, map[string]string{}) + + if !reflect.DeepEqual(currentLabels, desiredLabels) { + return true + } + return false } diff --git a/pkg/stackit/config/config.go b/pkg/stackit/config/config.go index db543fab..76ce03c1 100644 --- a/pkg/stackit/config/config.go +++ b/pkg/stackit/config/config.go @@ -49,7 +49,8 @@ type ALBConfig struct { ApplicationLoadBalancer ApplicationLoadBalancerOpts `yaml:"applicationLoadBalancer"` } type ApplicationLoadBalancerOpts struct { - NetworkID string `yaml:"networkId"` + NetworkID string `yaml:"networkId"` + ExtraLabels map[string]string `yaml:"extraLabels,omitempty"` } func readFile(path string) ([]byte, error) { From f7de4f0e2971d0b457d6f7eb90e4276e2c61470f Mon Sep 17 00:00:00 2001 From: Menekse Ceylan Date: Tue, 19 May 2026 09:07:19 +0200 Subject: [PATCH 10/10] Add label related alb tests to alb_spec_test.go Write gingko test for ingressclass_controller.go update done with "go mod tidy" --- go.mod | 7 + go.sum | 18 + pkg/alb/ingress/alb_spec_test.go | 27 +- .../ingress/ingressclass_controller_test.go | 511 ++++++++++-------- .../ingressclass_controller_test_old.go | 247 +++++++++ 5 files changed, 585 insertions(+), 225 deletions(-) create mode 100644 pkg/alb/ingress/ingressclass_controller_test_old.go diff --git a/go.mod b/go.mod index 38fd68e3..95ed5a52 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stackitcloud/stackit-sdk-go/core v0.26.0 + github.com/stackitcloud/stackit-sdk-go/services/alb v0.14.2 + github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0 github.com/stackitcloud/stackit-sdk-go/services/iaas v1.11.1 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.13.0 go.uber.org/mock v0.6.0 @@ -30,6 +32,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/mount-utils v0.36.1 k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 + sigs.k8s.io/controller-runtime v0.24.1 ) replace k8s.io/cloud-provider => github.com/stackitcloud/cloud-provider v0.36.0-ske-1 @@ -48,11 +51,13 @@ require ( github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/swag v0.25.1 // indirect @@ -121,12 +126,14 @@ require ( golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.44.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/apiextensions-apiserver v0.36.0 // indirect k8s.io/apiserver v0.36.0 // indirect k8s.io/component-helpers v0.36.0 // indirect k8s.io/controller-manager v0.36.0 // indirect diff --git a/go.sum b/go.sum index ef1a613a..3594341f 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -105,6 +109,8 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -159,6 +165,8 @@ github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -186,6 +194,10 @@ github.com/stackitcloud/cloud-provider v0.36.0-ske-1 h1:CZaL+8FH1rOjPnlPkhmvfKUk github.com/stackitcloud/cloud-provider v0.36.0-ske-1/go.mod h1:y/3sksoC0taJZR0PcAAYUqVyD6Jzu2X0lD4yCEPXPuI= github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10cz4l0KM2L6hqYBH2QA= github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA= +github.com/stackitcloud/stackit-sdk-go/services/alb v0.14.2 h1:hGzfOJjlCRoFpri5eYIiwhE27qu02pKZLprKvbsTC/w= +github.com/stackitcloud/stackit-sdk-go/services/alb v0.14.2/go.mod h1:eK6oRB5Tmpt6KbXQ4UYBGg2LgW5bPtVoncL9E8JSRww= +github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0 h1:J7BVVHjRTS5YUyGf6DZEIE1uD9f/S4v9dDbpZWVEd3U= +github.com/stackitcloud/stackit-sdk-go/services/certificates v1.7.0/go.mod h1:eJpB3/pukz+KzVPVHQ4g3DVtQkxGga18VbFBhq9ugdY= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.11.1 h1:HcKqjwIjv4OAW1aWI0U/JWjnzTwzSvdr6DLasH940EU= github.com/stackitcloud/stackit-sdk-go/services/iaas v1.11.1/go.mod h1:Ts06id0KejUlQWbpR+/rm+tKng6QkTuFV1VQTPJ4dA4= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.13.0 h1:UuLNwFHjJCpL11y4F7B9oBKtZkxpu01VkNPILNkpex4= @@ -326,6 +338,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= @@ -352,6 +366,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= +k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= @@ -378,6 +394,8 @@ k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7 k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/pkg/alb/ingress/alb_spec_test.go b/pkg/alb/ingress/alb_spec_test.go index dfc3f785..018f00f9 100644 --- a/pkg/alb/ingress/alb_spec_test.go +++ b/pkg/alb/ingress/alb_spec_test.go @@ -34,7 +34,7 @@ var _ = Describe("Node Controller", func() { networkID := "my-network" ingressClass = networkingv1.IngressClass{ - ObjectMeta: metav1.ObjectMeta{Name: "test-ingress-class"}, + ObjectMeta: metav1.ObjectMeta{Name: "test-ingress-class", UID: "test-ingress-class-uid"}, Spec: networkingv1.IngressClassSpec{Controller: controllerName}, } @@ -82,6 +82,7 @@ var _ = Describe("Node Controller", func() { albSpec = albsdk.CreateLoadBalancerPayload{ DisableTargetSecurityGroupAssignment: new(true), + Labels: new(map[string]string{"lb.customer.label/ingress-class-uid": "test-ingress-class-uid"}), Listeners: []albsdk.Listener{ { Http: new(albsdk.ProtocolOptionsHTTP{ @@ -145,12 +146,14 @@ var _ = Describe("Node Controller", func() { It("should work with labels", func() { reconciler.ALBConfig.ApplicationLoadBalancer.ExtraLabels = map[string]string{"managed-by": "alb-ingressClass"} + // adding extra labels to albSpec.Labels map + for k, v := range reconciler.ALBConfig.ApplicationLoadBalancer.ExtraLabels { + (*albSpec.Labels)[k] = v + } spec, errorEventList, err := reconciler.getAlbSpecForIngressClass(context.Background(), &ingressClass) Expect(err).To(Succeed()) Expect(errorEventList).To(BeEmpty()) - albSpec.Labels = new(map[string]string{"managed-by": "alb-ingressClass"}) - Expect(spec).ToNot(BeNil()) Expect(*spec).To(BeEquivalentTo(albSpec)) }) @@ -162,13 +165,19 @@ var _ = Describe("Node Controller", func() { Expect(k8sClient.Create(context.Background(), &ingress2)).To(Succeed()) - albSpec.Listeners[0].Http.Hosts[0].Rules = append( - albSpec.Listeners[0].Http.Hosts[0].Rules, - albsdk.Rule{ - Path: new(albsdk.Path{Prefix: new("/foobar")}), - TargetPool: albSpec.Listeners[0].Http.Hosts[0].Rules[0].TargetPool, + secTargetPool := *albSpec.Listeners[0].Http.Hosts[0].Rules[0].TargetPool + albSpec.Listeners[0].Http.Hosts[0].Rules = []albsdk.Rule{ + { + Path: &albsdk.Path{Prefix: new("/foobar")}, + TargetPool: new(secTargetPool), + WebSocket: new(false), + }, + { + Path: &albsdk.Path{Prefix: new("/")}, + TargetPool: new(secTargetPool), WebSocket: new(false), - }) + }, + } spec, errorEventList, err := reconciler.getAlbSpecForIngressClass(context.Background(), &ingressClass) Expect(err).To(Succeed()) diff --git a/pkg/alb/ingress/ingressclass_controller_test.go b/pkg/alb/ingress/ingressclass_controller_test.go index 5556f032..9c86615a 100644 --- a/pkg/alb/ingress/ingressclass_controller_test.go +++ b/pkg/alb/ingress/ingressclass_controller_test.go @@ -1,247 +1,326 @@ -package ingress - -/* -TODO +package ingress_test import ( "context" - "testing" - "time" - "github.com/google/go-cmp/cmp" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stackitcloud/cloud-provider-stackit/pkg/alb/ingress" stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" - gomock "go.uber.org/mock/gomock" + albsdk "github.com/stackitcloud/stackit-sdk-go/services/alb/v2api" + corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" - albsdk "github.com/stackitcloud/stackit-sdk-go/services/alb/v2api" + "go.uber.org/mock/gomock" + "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - testProjectID = "test-project" - testRegion = "test-region" - testALBName = "k8s-ingress-test-ingressclass" - testNamespace = "test-namespace" - testPublicIP = "1.2.3.4" - testPrivateIP = "10.0.0.1" + projectID = "dummy-project-id" + region = "eu01" + networkID = "my-network" + controllerName = "stackit.cloud/alb-ingress" + finalizerName = "stackit.cloud/alb-ingress" + LabelIngressClassUID = "lb.customer.label/ingress-class-uid" ) -//nolint:funlen // Just many test cases. -func TestIngressClassReconciler_updateStatus(t *testing.T) { - testIngressClass := &networkingv1.IngressClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: testIngressClassName, - }, - } +var _ = Describe("IngressClassReconciler", func() { + var ( + recorder *record.FakeRecorder + namespace *corev1.Namespace - tests := []struct { - name string - ingresses []*networkingv1.Ingress - mockK8sClient func(client.Client) error - mockALBClient func(*stackit.MockApplicationLoadBalancerClient) - wantResult reconcile.Result - wantErr bool - }{ - { - name: "ALB not ready (Terminating), should requeue", - mockK8sClient: func(c client.Client) error { - return c.Create(context.Background(), &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, - }) - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_TERMINATING"), - }, nil) - }, - wantResult: reconcile.Result{RequeueAfter: 10 * time.Second}, - wantErr: false, - }, - // This case only checks the reconcile result, not whether the ingress status was actually updated. - // The actual update logic will be verified in integration tests. - { - name: "ALB ready, public IP available, ingress status needs update", - ingresses: []*networkingv1.Ingress{ - {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, - }, - mockK8sClient: func(c client.Client) error { - return c.Create(context.Background(), &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, - }) - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_READY"), - ExternalAddress: new(testPublicIP), - }, nil) - }, - wantResult: reconcile.Result{}, - wantErr: false, - }, - // This case only checks the reconcile result, not whether the ingress status was actually updated. - // The actual update logic will be verified in integration tests. - { - name: "ALB ready, private IP available, ingress status needs update", - ingresses: []*networkingv1.Ingress{ - {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, - }, - mockK8sClient: func(c client.Client) error { - return c.Create(context.Background(), &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, - }) - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_READY"), - PrivateAddress: new(testPrivateIP), - }, nil) + mockCtrl *gomock.Controller + albClient *stackit.MockApplicationLoadBalancerClient + certClient *stackit.MockCertificatesClient + + service corev1.Service + node corev1.Node + + managedIngressClass *networkingv1.IngressClass + ignoredIngressClass *networkingv1.IngressClass + testIngressObj *networkingv1.Ingress + + mgrContext context.Context + mgrCancel context.CancelFunc + + // The dynamic function hook that controls mock behaviors per context + setupMocks func(m *stackit.MockApplicationLoadBalancerClient) + ) + + BeforeEach(func() { + + mockCtrl = gomock.NewController(GinkgoT()) + recorder = record.NewFakeRecorder(10) + + albClient = stackit.NewMockApplicationLoadBalancerClient(mockCtrl) + certClient = stackit.NewMockCertificatesClient(mockCtrl) + mgrContext, mgrCancel = context.WithCancel(context.Background()) + + // 1. Define standard operational defaults so 90% of your tests stay empty + setupMocks = func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), projectID, region, gomock.Any()). + Return(&albsdk.LoadBalancer{Status: new("READY")}, nil). + AnyTimes() + m.EXPECT(). + UpdateLoadBalancer(gomock.Any(), projectID, region, gomock.Any(), gomock.Any()). + Return(&albsdk.LoadBalancer{Status: new("READY")}, nil). + AnyTimes() + } + certClient.EXPECT(). + ListCertificate(gomock.Any(), projectID, region). // allow list cert call + Return(nil, nil). + AnyTimes() + }) + + // JustBeforeEach triggers right before the 'It' blocks run, ensuring that + // whatever configuration 'setupMocks' currently holds is armed on the client. + JustBeforeEach(func() { + setupMocks(albClient) + + // Safe global wildcard for asynchronous background deletions + albClient.EXPECT(). + DeleteLoadBalancer(gomock.Any(), projectID, region, gomock.Any()). + Return(nil). + AnyTimes() + + namespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "stackit-alb-ingress-test-", }, - wantResult: reconcile.Result{}, - wantErr: false, - }, - // This case only checks the reconcile result, not whether the ingress status was actually updated. - // The actual update logic will be verified in integration tests. - { - name: "ALB ready, IP already correct, no update", - ingresses: []*networkingv1.Ingress{ - { - ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, - Status: networkingv1.IngressStatus{ - LoadBalancer: networkingv1.IngressLoadBalancerStatus{ - Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: testPublicIP}}, - }, + } + Expect(k8sClient.Create(ctx, namespace)).To(Succeed()) + + service = corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "test-service", Namespace: namespace.Name}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + Port: 8080, + NodePort: 30123, }, }, }, - mockK8sClient: func(c client.Client) error { - return c.Create(context.Background(), &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, - Status: networkingv1.IngressStatus{ - LoadBalancer: networkingv1.IngressLoadBalancerStatus{ - Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: testPublicIP}}, - }, - }, - }) - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_READY"), - PrivateAddress: new(testPublicIP), - }, nil) - }, - wantResult: reconcile.Result{}, - wantErr: false, - }, - { - name: "failed to get load balancer", - ingresses: []*networkingv1.Ingress{ - {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT().GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName).Return(nil, stackit.ErrorNotFound) - }, - wantResult: reconcile.Result{}, - wantErr: true, - }, - { - name: "failed to get latest ingress", - ingresses: []*networkingv1.Ingress{ - {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_READY"), - PrivateAddress: new(testPublicIP), - }, nil) - }, - wantResult: reconcile.Result{}, - wantErr: true, - }, - // This case only checks the reconcile result, not whether the ingress status was actually updated. - // The actual update logic will be verified in integration tests. - { - name: "failed to update ingress status", - ingresses: []*networkingv1.Ingress{ - {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_READY"), - PrivateAddress: new(testPublicIP), - }, nil) - }, - wantResult: reconcile.Result{}, - wantErr: true, - }, - { - name: "ALB ready, no public or private IP, should requeue", - ingresses: []*networkingv1.Ingress{ - {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, - }, - mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { - m.EXPECT(). - GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). - Return(&albsdk.LoadBalancer{ - Status: new("STATUS_READY"), - }, nil) + } + Expect(k8sClient.Create(ctx, &service)).To(Succeed()) + + node = corev1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "test-node"}, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: "10.10.10.10"}}, }, - wantResult: reconcile.Result{RequeueAfter: 10 * time.Second}, - wantErr: false, - }, - } + } + Expect(k8sClient.Create(ctx, &node)).To(Succeed()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - - mockAlbClient := stackit.NewMockApplicationLoadBalancerClient(ctrl) - fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() - r := &IngressClassReconciler{ - Client: fakeClient, - ALBClient: mockAlbClient, - ALBConfig: stackitconfig.ALBConfig{ - Global: stackitconfig.GlobalOpts{ - ProjectID: testProjectID, - Region: testRegion, - }, + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).NotTo(HaveOccurred()) + + reconciler := ingress.IngressClassReconciler{ + Recorder: recorder, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ALBClient: albClient, + CertificateClient: certClient, + ALBConfig: stackitconfig.ALBConfig{ + Global: stackitconfig.GlobalOpts{ + ProjectID: projectID, + Region: region, }, - } + ApplicationLoadBalancer: stackitconfig.ApplicationLoadBalancerOpts{NetworkID: networkID}}, + } - if tt.mockK8sClient != nil { - if err := tt.mockK8sClient(fakeClient); err != nil { - t.Fatalf("mockK8sClient failed: %v", err) - } - } + err = ctrl.NewControllerManagedBy(mgr). + Named("ingressclass-test-" + namespace.Name). + For(&networkingv1.IngressClass{}). + Complete(&reconciler) + Expect(err).NotTo(HaveOccurred()) - if tt.mockALBClient != nil { - tt.mockALBClient(mockAlbClient) - } + // Start the manager engine in the background + go func() { + defer GinkgoRecover() + err = mgr.Start(mgrContext) + Expect(err).NotTo(HaveOccurred()) + }() + + }) - got, err := r.updateStatus(context.Background(), testIngressClass) - if (err != nil) != tt.wantErr { - t.Fatalf("expected error %v, got %v", tt.wantErr, err) + AfterEach(func() { + mgrCancel() // Terminate background manager routines + mockCtrl.Finish() + + // Clean up infrastructure resources using global client + _ = k8sClient.Delete(ctx, &service) + _ = k8sClient.Delete(ctx, &node) + if managedIngressClass != nil { + + _ = k8sClient.Delete(ctx, managedIngressClass) + } + if ignoredIngressClass != nil { + _ = k8sClient.Delete(ctx, ignoredIngressClass) + } + if testIngressObj != nil { + _ = k8sClient.Delete(ctx, testIngressObj) + } + }) + + Context("when the IngressClass does NOT point to the ALB controller", func() { + It("should ignore the IngressClass and not append finalizers", func() { + ignoredIngressClass = &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "ignored-ingressclass-", + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "some.other/controller", + }, } - if diff := cmp.Diff(tt.wantResult, got); diff != "" { - t.Fatalf("unexpected result (-want +got):\n%s", diff) + Expect(k8sClient.Create(ctx, ignoredIngressClass)).To(Succeed()) + + // Verify the object state after reconciliation + reconciledIngressClass := &networkingv1.IngressClass{} + + Consistently(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(ignoredIngressClass), reconciledIngressClass) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(reconciledIngressClass.Finalizers).To(BeEmpty()) + }, "2s", "200ms").Should(Succeed()) + + }) + }) + + Context("when the IngressClass points to the ALB controller", func() { + + BeforeEach(func() { + managedIngressClass = &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "managed-ingressclass-", + Labels: map[string]string{"app": "stackit-alb-ingress"}, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: controllerName, + }, } + + testIngressObj = testIngress(managedIngressClass, &service) + + }) + + JustBeforeEach(func() { + testIngressObj = testIngress(managedIngressClass, &service) + }) + + Context("When reconciling an IngressClass", func() { + It("should successfully reconcile the resource and append the finalizer", func() { + + Expect(k8sClient.Create(ctx, managedIngressClass)).To(Succeed()) + // Check if the finalizer was added + reconciledIngressClass := &networkingv1.IngressClass{} + + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(managedIngressClass), reconciledIngressClass) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(reconciledIngressClass.Finalizers).To(ContainElement(finalizerName)) + }, "5s", "200ms").Should(Succeed()) + + }) + It("should add the labels", func() {}) + }) + Context("When deleting an IngressClass", func() { + BeforeEach(func() { + // 1. Point our managed IngressClass definition to include the target testing labels + managedIngressClass = &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "managed-ingressclass-", + UID: "envtest-ic-uid", + Labels: map[string]string{ + "app": "stackit-alb-ingress", + LabelIngressClassUID: "target-cloud-alb-id", + }, + }, + Spec: networkingv1.IngressClassSpec{Controller: controllerName}, + } + + setupMocks = func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), projectID, region, gomock.Any()). + Return(&albsdk.LoadBalancer{Status: new("READY")}, nil). + AnyTimes() + m.EXPECT(). + UpdateLoadBalancer(gomock.Any(), projectID, region, gomock.Any(), gomock.Any()). + Return(&albsdk.LoadBalancer{Status: new("READY")}, nil). + AnyTimes() // "allow background threads update safely without breaking my test" + + m.EXPECT(). + DeleteLoadBalancer(gomock.Any(), projectID, region, gomock.Any()). + Return(nil). + Times(1) // Asserts that the controller MUST call this exactly 1 time! + } + }) + + It("should read the UID label, invoke the cloud delete call, and drop the resource from K8s", func() { + // Publish the labeled IngressClass to the test cluster + Expect(k8sClient.Create(ctx, managedIngressClass)).To(Succeed()) + + // Wait for the controller background loop to notice it and attach the finalizer + reconciledIngressClass := &networkingv1.IngressClass{} + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(managedIngressClass), reconciledIngressClass) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(reconciledIngressClass.Finalizers).To(ContainElement(finalizerName)) + }, "5s", "200ms").Should(Succeed()) + + // 3. Issue the Delete call to test the teardown pipeline + Expect(k8sClient.Delete(ctx, managedIngressClass)).To(Succeed()) + + // 4. Verify the finalizer gets scrubbed and the object disappears from the API Server + Eventually(func(g Gomega) { + var ic networkingv1.IngressClass + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(managedIngressClass), &ic) + + // Import "k8s.io/apimachinery/pkg/api/errors" as apierrors to use IsNotFound + g.Expect(apierrors.IsNotFound(err)).To(BeTrue(), "The object must be deleted completely") + }, "5s", "200ms").Should(Succeed()) + }) + }) + }) + +}) + +func testIngress(class *networkingv1.IngressClass, service *corev1.Service) *networkingv1.Ingress { + return &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ingress", Namespace: service.Namespace}, + Spec: networkingv1.IngressSpec{ + IngressClassName: new(class.Name), + Rules: []networkingv1.IngressRule{ + { + Host: "example.com", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + PathType: new(networkingv1.PathTypePrefix), + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: service.Name, + Port: networkingv1.ServiceBackendPort{Number: service.Spec.Ports[0].Port}, + }, + }, + }, + }, + }, + }, + }, + }, + }, } } -*/ diff --git a/pkg/alb/ingress/ingressclass_controller_test_old.go b/pkg/alb/ingress/ingressclass_controller_test_old.go new file mode 100644 index 00000000..5556f032 --- /dev/null +++ b/pkg/alb/ingress/ingressclass_controller_test_old.go @@ -0,0 +1,247 @@ +package ingress + +/* +TODO + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" + gomock "go.uber.org/mock/gomock" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" + albsdk "github.com/stackitcloud/stackit-sdk-go/services/alb/v2api" +) + +const ( + testProjectID = "test-project" + testRegion = "test-region" + testALBName = "k8s-ingress-test-ingressclass" + testNamespace = "test-namespace" + testPublicIP = "1.2.3.4" + testPrivateIP = "10.0.0.1" +) + +//nolint:funlen // Just many test cases. +func TestIngressClassReconciler_updateStatus(t *testing.T) { + testIngressClass := &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: testIngressClassName, + }, + } + + tests := []struct { + name string + ingresses []*networkingv1.Ingress + mockK8sClient func(client.Client) error + mockALBClient func(*stackit.MockApplicationLoadBalancerClient) + wantResult reconcile.Result + wantErr bool + }{ + { + name: "ALB not ready (Terminating), should requeue", + mockK8sClient: func(c client.Client) error { + return c.Create(context.Background(), &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, + }) + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_TERMINATING"), + }, nil) + }, + wantResult: reconcile.Result{RequeueAfter: 10 * time.Second}, + wantErr: false, + }, + // This case only checks the reconcile result, not whether the ingress status was actually updated. + // The actual update logic will be verified in integration tests. + { + name: "ALB ready, public IP available, ingress status needs update", + ingresses: []*networkingv1.Ingress{ + {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, + }, + mockK8sClient: func(c client.Client) error { + return c.Create(context.Background(), &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, + }) + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_READY"), + ExternalAddress: new(testPublicIP), + }, nil) + }, + wantResult: reconcile.Result{}, + wantErr: false, + }, + // This case only checks the reconcile result, not whether the ingress status was actually updated. + // The actual update logic will be verified in integration tests. + { + name: "ALB ready, private IP available, ingress status needs update", + ingresses: []*networkingv1.Ingress{ + {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, + }, + mockK8sClient: func(c client.Client) error { + return c.Create(context.Background(), &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, + }) + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_READY"), + PrivateAddress: new(testPrivateIP), + }, nil) + }, + wantResult: reconcile.Result{}, + wantErr: false, + }, + // This case only checks the reconcile result, not whether the ingress status was actually updated. + // The actual update logic will be verified in integration tests. + { + name: "ALB ready, IP already correct, no update", + ingresses: []*networkingv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, + Status: networkingv1.IngressStatus{ + LoadBalancer: networkingv1.IngressLoadBalancerStatus{ + Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: testPublicIP}}, + }, + }, + }, + }, + mockK8sClient: func(c client.Client) error { + return c.Create(context.Background(), &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}, + Status: networkingv1.IngressStatus{ + LoadBalancer: networkingv1.IngressLoadBalancerStatus{ + Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: testPublicIP}}, + }, + }, + }) + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_READY"), + PrivateAddress: new(testPublicIP), + }, nil) + }, + wantResult: reconcile.Result{}, + wantErr: false, + }, + { + name: "failed to get load balancer", + ingresses: []*networkingv1.Ingress{ + {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT().GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName).Return(nil, stackit.ErrorNotFound) + }, + wantResult: reconcile.Result{}, + wantErr: true, + }, + { + name: "failed to get latest ingress", + ingresses: []*networkingv1.Ingress{ + {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_READY"), + PrivateAddress: new(testPublicIP), + }, nil) + }, + wantResult: reconcile.Result{}, + wantErr: true, + }, + // This case only checks the reconcile result, not whether the ingress status was actually updated. + // The actual update logic will be verified in integration tests. + { + name: "failed to update ingress status", + ingresses: []*networkingv1.Ingress{ + {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_READY"), + PrivateAddress: new(testPublicIP), + }, nil) + }, + wantResult: reconcile.Result{}, + wantErr: true, + }, + { + name: "ALB ready, no public or private IP, should requeue", + ingresses: []*networkingv1.Ingress{ + {ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testIngressName}}, + }, + mockALBClient: func(m *stackit.MockApplicationLoadBalancerClient) { + m.EXPECT(). + GetLoadBalancer(gomock.Any(), testProjectID, testRegion, testALBName). + Return(&albsdk.LoadBalancer{ + Status: new("STATUS_READY"), + }, nil) + }, + wantResult: reconcile.Result{RequeueAfter: 10 * time.Second}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + + mockAlbClient := stackit.NewMockApplicationLoadBalancerClient(ctrl) + fakeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + r := &IngressClassReconciler{ + Client: fakeClient, + ALBClient: mockAlbClient, + ALBConfig: stackitconfig.ALBConfig{ + Global: stackitconfig.GlobalOpts{ + ProjectID: testProjectID, + Region: testRegion, + }, + }, + } + + if tt.mockK8sClient != nil { + if err := tt.mockK8sClient(fakeClient); err != nil { + t.Fatalf("mockK8sClient failed: %v", err) + } + } + + if tt.mockALBClient != nil { + tt.mockALBClient(mockAlbClient) + } + + got, err := r.updateStatus(context.Background(), testIngressClass) + if (err != nil) != tt.wantErr { + t.Fatalf("expected error %v, got %v", tt.wantErr, err) + } + if diff := cmp.Diff(tt.wantResult, got); diff != "" { + t.Fatalf("unexpected result (-want +got):\n%s", diff) + } + }) + } +} +*/