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) {