From d946cabd26d8ab94ed735753dce2e77cfc9fd8bd Mon Sep 17 00:00:00 2001 From: AlexFenlon Date: Tue, 14 May 2024 10:37:08 +0100 Subject: [PATCH] Add Ingress Annotation List to Telemetry (#5516) --- docs/content/overview/product-telemetry.md | 1 + internal/configs/annotations.go | 8 + internal/configs/configurator.go | 54 ++- internal/configs/configurator_test.go | 141 ++++++ internal/telemetry/cluster.go | 9 + internal/telemetry/collector.go | 5 + internal/telemetry/collector_test.go | 435 +++++++++++++++++- internal/telemetry/data.avdl | 3 + internal/telemetry/exporter.go | 2 + .../nicresourcecounts_attributes_generated.go | 1 + 10 files changed, 649 insertions(+), 10 deletions(-) diff --git a/docs/content/overview/product-telemetry.md b/docs/content/overview/product-telemetry.md index fd25e0257d..5d0f29234a 100644 --- a/docs/content/overview/product-telemetry.md +++ b/docs/content/overview/product-telemetry.md @@ -37,6 +37,7 @@ These are the data points collected and reported by NGINX Ingress Controller: - **Services** Number of Services referenced by VirtualServers, VirtualServerRoutes, TransportServers and Ingresses. - **Ingresses** The number of Ingress resources managed by the NGINX Ingress Controller. - **IngressClasses** Number of Ingress Classes in the cluster. +- **IngressAnnotations** List of Ingress annotations managed by NGINX Ingress Controller - **AccessControlPolicies** Number of AccessControl policies. - **RateLimitPolicies** Number of RateLimit policies. - **JWTAuthPolicies** Number of JWTAuth policies. diff --git a/internal/configs/annotations.go b/internal/configs/annotations.go index cb518c11b8..97447f2849 100644 --- a/internal/configs/annotations.go +++ b/internal/configs/annotations.go @@ -98,6 +98,14 @@ var validPathRegex = map[string]bool{ "exact": true, } +// List of Ingress Annotation Keys used by the Ingress Controller +var allowedAnnotationKeys = []string{ + "nginx.org", + "nginx.com", + "f5.com", + "ingress.kubernetes.io/ssl-redirect", +} + func parseAnnotations(ingEx *IngressEx, baseCfgParams *ConfigParams, isPlus bool, hasAppProtect bool, hasAppProtectDos bool, enableInternalRoutes bool) ConfigParams { cfgParams := *baseCfgParams diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index 59ab0a6ffc..9828c04a61 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -772,7 +772,7 @@ func (cnf *Configurator) addOrUpdateTransportServer(transportServerEx *Transport if err != nil { return false, nil, err } - return (changed || ptChanged), warnings, nil + return changed || ptChanged, warnings, nil } return changed, warnings, nil } @@ -1548,6 +1548,58 @@ func (cnf *Configurator) GetIngressCounts() map[string]int { return counters } +// GetIngressAnnotations returns a list of annotation keys set across all Ingress resources +func (cnf *Configurator) GetIngressAnnotations() []string { + if cnf == nil || cnf.ingresses == nil { + return nil + } + + annotationSet := make(map[string]bool) + annotationSet = cnf.getMinionIngressAnnotations(annotationSet) + annotationSet = cnf.getStandardIngressAnnotations(annotationSet) + + var annotations []string + for key := range annotationSet { + annotations = append(annotations, key) + } + + return annotations +} + +// getStandardIngressAnnotations returns a map of allowedAnnotations detected in Standard or Master Ingresses +func (cnf *Configurator) getStandardIngressAnnotations(annotationSet map[string]bool) map[string]bool { + for _, ing := range cnf.ingresses { + if ing != nil && ing.Ingress != nil && ing.Ingress.Annotations != nil { + for key := range ing.Ingress.Annotations { + for _, allowedAnnotationKey := range allowedAnnotationKeys { + if strings.Contains(key, allowedAnnotationKey) { + annotationSet[key] = true + } + } + } + } + } + return annotationSet +} + +// getMinionIngressAnnotations returns a map of allowedAnnotations detected in Minion Ingresses +func (cnf *Configurator) getMinionIngressAnnotations(annotationSet map[string]bool) map[string]bool { + for _, ing := range cnf.mergeableIngresses { + for _, minionIng := range ing.Minions { + if minionIng != nil && minionIng.Ingress.Annotations != nil { + for key := range minionIng.Ingress.Annotations { + for _, allowedAnnotationKey := range allowedAnnotationKeys { + if strings.Contains(key, allowedAnnotationKey) { + annotationSet[key] = true + } + } + } + } + } + } + return annotationSet +} + // GetServiceCount returns the total number of unique services referenced by Ingresses, VS's, VSR's, and TS's func (cnf *Configurator) GetServiceCount() int { setOfUniqueServices := make(map[string]bool) diff --git a/internal/configs/configurator_test.go b/internal/configs/configurator_test.go index 6a9720494c..90a83ccc00 100644 --- a/internal/configs/configurator_test.go +++ b/internal/configs/configurator_test.go @@ -1426,6 +1426,147 @@ func TestStreamUpstreamsForName_ReturnsStreamUpstreamsNamesOnValidServiceName(t } } +func TestGetIngressAnnotations(t *testing.T) { + t.Parallel() + + tcnf := createTestConfigurator(t) + + ingress := &IngressEx{ + Ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "appprotect.f5.com/app-protect-enable": "False", + "nginx.org/proxy-set-header": "X-Forwarded-ABC", + "ingress.kubernetes.io/ssl-redirect": "True", + }, + }, + }, + } + + _, err := tcnf.AddOrUpdateIngress(ingress) + if err != nil { + t.Fatalf("AddOrUpdateIngress returned error: %v", err) + } + + annotationList := tcnf.GetIngressAnnotations() + + expectedAnnotations := []string{ + "appprotect.f5.com/app-protect-enable", + "nginx.org/proxy-set-header", + "ingress.kubernetes.io/ssl-redirect", + } + + if len(annotationList) != len(expectedAnnotations) { + t.Errorf("got %d annotations, want %d", len(annotationList), len(expectedAnnotations)) + } + + foundAnnotations := make(map[string]bool) + for _, annotation := range annotationList { + foundAnnotations[annotation] = true + } + + for _, expected := range expectedAnnotations { + if !foundAnnotations[expected] { + t.Errorf("expected annotation %q not found", expected) + } + } +} + +func TestGetInvalidIngressAnnotations(t *testing.T) { + t.Parallel() + + tcnf := createTestConfigurator(t) + + ingress := &IngressEx{ + Ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "kubectl.kubernetes.io/last-applied-configuration": "s", + "alb.ingress.kubernetes.io/group.order": "0", + "alb.ingress.kubernetes.io/ip-address-type": "ipv4", + "alb.ingress.kubernetes.io/scheme": "internal", + }, + }, + }, + } + + _, err := tcnf.AddOrUpdateIngress(ingress) + if err != nil { + t.Fatalf("AddOrUpdateIngress returned error: %v", err) + } + + expectedAnnotations := []string{ + "alb.ingress.kubernetes.io/scheme", + "alb.ingress.kubernetes.io/group.order", + "alb.ingress.kubernetes.io/ip-address-type", + } + + annotationList := tcnf.GetIngressAnnotations() + + foundAnnotations := make(map[string]bool) + for _, annotation := range annotationList { + foundAnnotations[annotation] = true + } + + for _, expected := range expectedAnnotations { + if foundAnnotations[expected] { + t.Errorf("expected annotation %q not found", expected) + } + } +} + +func TestGetMixedIngressAnnotations(t *testing.T) { + t.Parallel() + + tcnf := createTestConfigurator(t) + + ingress := &IngressEx{ + Ingress: &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "test-ingress", + Namespace: "default", + Annotations: map[string]string{ + "kubectl.kubernetes.io/last-applied-configuration": "s", + "alb.ingress.kubernetes.io/group.order": "0", + "alb.ingress.kubernetes.io/ip-address-type": "ipv4", + "alb.ingress.kubernetes.io/scheme": "internal", + "appprotect.f5.com/app-protect-enable": "False", + "nginx.org/proxy-set-header": "X-Forwarded-ABC", + "ingress.kubernetes.io/ssl-redirect": "True", + }, + }, + }, + } + + _, err := tcnf.AddOrUpdateIngress(ingress) + if err != nil { + t.Fatalf("AddOrUpdateIngress returned error: %v", err) + } + + expectedAnnotations := []string{ + "ingress.kubernetes.io/ssl-redirect", + "nginx.org/proxy-set-header", + "appprotect.f5.com/app-protect-enable", + } + + annotationList := tcnf.GetIngressAnnotations() + + foundAnnotations := make(map[string]bool) + for _, annotation := range annotationList { + foundAnnotations[annotation] = true + } + + for _, expected := range expectedAnnotations { + if !foundAnnotations[expected] { + t.Errorf("expected annotation %q not found", expected) + } + } +} + var ( invalidVirtualServerEx = &VirtualServerEx{ VirtualServer: &conf_v1.VirtualServer{}, diff --git a/internal/telemetry/cluster.go b/internal/telemetry/cluster.go index d91df0635e..896ee7e2cb 100644 --- a/internal/telemetry/cluster.go +++ b/internal/telemetry/cluster.go @@ -136,6 +136,15 @@ func (c *Collector) IngressCount() int { return total } +// IngressAnnotations returns a list of all the unique annotations found in Ingresses. +func (c *Collector) IngressAnnotations() []string { + if c.Config.Configurator == nil { + return nil + } + annotations := c.Config.Configurator.GetIngressAnnotations() + return annotations +} + // IngressClassCount returns number of Ingress Classes. func (c *Collector) IngressClassCount(ctx context.Context) (int, error) { ic, err := c.Config.K8sClientReader.NetworkingV1().IngressClasses().List(ctx, metaV1.ListOptions{}) diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go index ce287b5e64..21a5bf98ea 100644 --- a/internal/telemetry/collector.go +++ b/internal/telemetry/collector.go @@ -132,6 +132,7 @@ func (c *Collector) Collect(ctx context.Context) { OIDCPolicies: int64(report.OIDCCount), WAFPolicies: int64(report.WAFCount), GlobalConfiguration: report.GlobalConfiguration, + IngressAnnotations: report.IngressAnnotations, }, } @@ -171,6 +172,7 @@ type Report struct { OIDCCount int WAFCount int GlobalConfiguration bool + IngressAnnotations []string } // BuildReport takes context, collects telemetry data and builds the report. @@ -237,6 +239,8 @@ func (c *Collector) BuildReport(ctx context.Context) (Report, error) { oidcCount := policies["OIDC"] wafCount := policies["WAF"] + ingressAnnotations := c.IngressAnnotations() + return Report{ Name: "NIC", Version: c.Config.Version, @@ -263,5 +267,6 @@ func (c *Collector) BuildReport(ctx context.Context) (Report, error) { OIDCCount: oidcCount, WAFCount: wafCount, GlobalConfiguration: c.Config.GlobalConfiguration, + IngressAnnotations: ingressAnnotations, }, err } diff --git a/internal/telemetry/collector_test.go b/internal/telemetry/collector_test.go index 677866de47..55b0275ff9 100644 --- a/internal/telemetry/collector_test.go +++ b/internal/telemetry/collector_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "runtime" + "strings" "testing" "time" @@ -154,8 +155,8 @@ func TestCollectNodeCountInClusterWithThreeNodes(t *testing.T) { } td := telemetry.Data{ - telData, - nicResourceCounts, + Data: telData, + NICResourceCounts: nicResourceCounts, } want := fmt.Sprintf("%+v", &td) @@ -423,8 +424,8 @@ func TestIngressCountReportsNoDeployedIngresses(t *testing.T) { } td := telemetry.Data{ - telData, - nicResourceCounts, + Data: telData, + NICResourceCounts: nicResourceCounts, } want := fmt.Sprintf("%+v", &td) @@ -434,6 +435,186 @@ func TestIngressCountReportsNoDeployedIngresses(t *testing.T) { } } +func TestMergeableIngressAnnotations(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + + masterAnnotations := map[string]string{ + "nginx.org/mergeable-ingress-type": "master", + } + coffeeAnnotations := map[string]string{ + "nginx.org/mergeable-ingress-type": "minion", + "nginx.org/proxy-set-header": "X-Forwarded-ABC", + "appprotect.f5.com/app-protect-enable": "False", + } + teaAnnotations := map[string]string{ + "nginx.org/mergeable-ingress-type": "minion", + "nginx.org/proxy-set-header": "X-Forwarded-Tea: Chai", + "nginx.com/health-checks": "False", + } + + configurator := newConfiguratorWithMergeableIngressCustomAnnotations(t, masterAnnotations, coffeeAnnotations, teaAnnotations) + + cfg := telemetry.CollectorConfig{ + Configurator: configurator, + K8sClientReader: newTestClientset(node1, kubeNS), + Version: telemetryNICData.ProjectVersion, + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + expectedAnnotations := []string{ + "nginx.org/mergeable-ingress-type", + "nginx.org/proxy-set-header", + "nginx.com/health-checks", + "appprotect.f5.com/app-protect-enable", + } + + got := buf.String() + for _, expectedAnnotation := range expectedAnnotations { + if !strings.Contains(got, expectedAnnotation) { + t.Errorf("expected %v in %v", expectedAnnotation, got) + } + } +} + +func TestInvalidMergeableIngressAnnotations(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + + masterAnnotations := map[string]string{ + "nginx.org/ingress-type": "master", + "kubectl.kubernetes.io/last-applied-configuration": "s", + } + coffeeAnnotations := map[string]string{ + "nginx.org/mergeable-ingress-type": "minion", + } + teaAnnotations := map[string]string{ + "nginx.org/mergeable-type": "minion", + "nginx.org/proxy-set": "X-$-ABC", + "nginx.ingress.kubernetes.io/rewrite-target": "/", + } + + configurator := newConfiguratorWithMergeableIngressCustomAnnotations(t, masterAnnotations, coffeeAnnotations, teaAnnotations) + + cfg := telemetry.CollectorConfig{ + Configurator: configurator, + K8sClientReader: newTestClientset(node1, kubeNS), + Version: telemetryNICData.ProjectVersion, + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + expectedAnnotations := []string{ + "kubectl.kubernetes.io/last-applied-configuration", + "nginx.org/proxy-set-header", + "nginx.ingress.kubernetes.io/rewrite-target", + } + + got := buf.String() + for _, expectedAnnotation := range expectedAnnotations { + if strings.Contains(got, expectedAnnotation) { + t.Errorf("expected %v in %v", expectedAnnotation, got) + } + } +} + +func TestStandardIngressAnnotations(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + + annotations := map[string]string{ + "appprotect.f5.com/app-protect-enable": "False", + "nginx.org/proxy-set-header": "X-Forwarded-ABC", + "ingress.kubernetes.io/ssl-redirect": "True", + "nginx.com/slow-start": "0s", + } + + configurator := newConfiguratorWithIngressWithCustomAnnotations(t, annotations) + + cfg := telemetry.CollectorConfig{ + Configurator: configurator, + K8sClientReader: newTestClientset(node1, kubeNS), + Version: telemetryNICData.ProjectVersion, + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + expectedAnnotations := []string{ + "appprotect.f5.com/app-protect-enable", + "nginx.org/proxy-set-header", + "nginx.com/slow-start", + "ingress.kubernetes.io/ssl-redirect", + } + + got := buf.String() + for _, expectedAnnotation := range expectedAnnotations { + if !strings.Contains(got, expectedAnnotation) { + t.Errorf("expected %v in %v", expectedAnnotation, got) + } + } +} + +func TestInvalidStandardIngressAnnotations(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + exp := &telemetry.StdoutExporter{Endpoint: buf} + + annotations := map[string]string{ + "alb.ingress.kubernetes.io/group.order": "0", + "alb.ingress.kubernetes.io/ip-address-type": "ipv4", + "alb.ingress.kubernetes.io/scheme": "internal", + "nginx.ingress.kubernetes.io/rewrite-target": "/", + } + + configurator := newConfiguratorWithIngressWithCustomAnnotations(t, annotations) + + cfg := telemetry.CollectorConfig{ + Configurator: configurator, + K8sClientReader: newTestClientset(node1, kubeNS), + Version: telemetryNICData.ProjectVersion, + } + + c, err := telemetry.NewCollector(cfg, telemetry.WithExporter(exp)) + if err != nil { + t.Fatal(err) + } + c.Collect(context.Background()) + + expectedAnnotations := []string{ + "alb.ingress.kubernetes.io/scheme", + "alb.ingress.kubernetes.io/group.order", + "alb.ingress.kubernetes.io/ip-address-type", + "nginx.ingress.kubernetes.io/rewrite-target", + } + + got := buf.String() + for _, expectedAnnotation := range expectedAnnotations { + if strings.Contains(got, expectedAnnotation) { + t.Errorf("expected %v in %v", expectedAnnotation, got) + } + } +} + func TestIngressCountReportsNumberOfDeployedIngresses(t *testing.T) { t.Parallel() @@ -473,8 +654,8 @@ func TestIngressCountReportsNumberOfDeployedIngresses(t *testing.T) { } td := telemetry.Data{ - telData, - nicResourceCounts, + Data: telData, + NICResourceCounts: nicResourceCounts, } want := fmt.Sprintf("%+v", &td) @@ -1392,9 +1573,6 @@ func createCafeIngressEx() configs.IngressEx { ObjectMeta: metaV1.ObjectMeta{ Name: "cafe-ingress", Namespace: "default", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "nginx", - }, }, Spec: networkingV1.IngressSpec{ TLS: []networkingV1.IngressTLS{ @@ -1613,6 +1791,221 @@ func createMergeableCafeIngress() *configs.MergeableIngresses { return mergeableIngresses } +func createMergeableIngressWithCustomAnnotations(masterAnnotations, coffeeAnnotations, teaAnnotations map[string]string) *configs.MergeableIngresses { + master := networkingV1.Ingress{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "cafe-ingress-master", + Namespace: "default", + Annotations: masterAnnotations, + }, + Spec: networkingV1.IngressSpec{ + TLS: []networkingV1.IngressTLS{ + { + Hosts: []string{"cafe.example.com"}, + SecretName: "cafe-secret", + }, + }, + Rules: []networkingV1.IngressRule{ + { + Host: "cafe.example.com", + IngressRuleValue: networkingV1.IngressRuleValue{ + HTTP: &networkingV1.HTTPIngressRuleValue{ // HTTP must not be nil for Master + Paths: []networkingV1.HTTPIngressPath{}, + }, + }, + }, + }, + }, + } + + coffeeMinion := networkingV1.Ingress{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "cafe-ingress-coffee-minion", + Namespace: "default", + Annotations: coffeeAnnotations, + }, + Spec: networkingV1.IngressSpec{ + Rules: []networkingV1.IngressRule{ + { + Host: "cafe.example.com", + IngressRuleValue: networkingV1.IngressRuleValue{ + HTTP: &networkingV1.HTTPIngressRuleValue{ + Paths: []networkingV1.HTTPIngressPath{ + { + Path: "/coffee", + Backend: networkingV1.IngressBackend{ + Service: &networkingV1.IngressServiceBackend{ + Name: "coffee-svc", + Port: networkingV1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + teaMinion := networkingV1.Ingress{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "cafe-ingress-tea-minion", + Namespace: "default", + Annotations: teaAnnotations, + }, + Spec: networkingV1.IngressSpec{ + Rules: []networkingV1.IngressRule{ + { + Host: "cafe.example.com", + IngressRuleValue: networkingV1.IngressRuleValue{ + HTTP: &networkingV1.HTTPIngressRuleValue{ + Paths: []networkingV1.HTTPIngressPath{ + { + Path: "/tea", + Backend: networkingV1.IngressBackend{ + Service: &networkingV1.IngressServiceBackend{ + Name: "tea-svc", + Port: networkingV1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + mergeableIngresses := &configs.MergeableIngresses{ + Master: &configs.IngressEx{ + Ingress: &master, + Endpoints: map[string][]string{ + "coffee-svc80": {"10.0.0.1:80"}, + "tea-svc80": {"10.0.0.2:80"}, + }, + ValidHosts: map[string]bool{ + "cafe.example.com": true, + }, + SecretRefs: map[string]*secrets.SecretReference{ + "cafe-secret": { + Secret: &coreV1.Secret{ + Type: coreV1.SecretTypeTLS, + }, + Path: "/etc/nginx/secrets/default-cafe-secret", + Error: nil, + }, + }, + }, + Minions: []*configs.IngressEx{ + { + Ingress: &coffeeMinion, + Endpoints: map[string][]string{ + "coffee-svc80": {"10.0.0.1:80"}, + }, + ValidHosts: map[string]bool{ + "cafe.example.com": true, + }, + ValidMinionPaths: map[string]bool{ + "/coffee": true, + }, + SecretRefs: map[string]*secrets.SecretReference{}, + }, + { + Ingress: &teaMinion, + Endpoints: map[string][]string{ + "tea-svc80": {"10.0.0.2:80"}, + }, + ValidHosts: map[string]bool{ + "cafe.example.com": true, + }, + ValidMinionPaths: map[string]bool{ + "/tea": true, + }, + SecretRefs: map[string]*secrets.SecretReference{}, + }, + }, + } + + return mergeableIngresses +} + +func createCafeIngressExWithCustomAnnotations(annotations map[string]string) configs.IngressEx { + cafeIngress := networkingV1.Ingress{ + ObjectMeta: metaV1.ObjectMeta{ + Name: "cafe-ingress", + Namespace: "default", + Annotations: annotations, + }, + Spec: networkingV1.IngressSpec{ + TLS: []networkingV1.IngressTLS{ + { + Hosts: []string{"cafe.example.com"}, + SecretName: "cafe-secret", + }, + }, + Rules: []networkingV1.IngressRule{ + { + Host: "cafe.example.com", + IngressRuleValue: networkingV1.IngressRuleValue{ + HTTP: &networkingV1.HTTPIngressRuleValue{ + Paths: []networkingV1.HTTPIngressPath{ + { + Path: "/coffee", + Backend: networkingV1.IngressBackend{ + Service: &networkingV1.IngressServiceBackend{ + Name: "coffee-svc", + Port: networkingV1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + { + Path: "/tea", + Backend: networkingV1.IngressBackend{ + Service: &networkingV1.IngressServiceBackend{ + Name: "tea-svc", + Port: networkingV1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + cafeIngressEx := configs.IngressEx{ + Ingress: &cafeIngress, + Endpoints: map[string][]string{ + "coffee-svc80": {"10.0.0.1:80"}, + "tea-svc80": {"10.0.0.2:80"}, + }, + ExternalNameSvcs: map[string]bool{}, + ValidHosts: map[string]bool{ + "cafe.example.com": true, + }, + SecretRefs: map[string]*secrets.SecretReference{ + "cafe-secret": { + Secret: &coreV1.Secret{ + Type: coreV1.SecretTypeTLS, + }, + Path: "/etc/nginx/secrets/default-cafe-secret", + }, + }, + } + return cafeIngressEx +} + func getResourceKey(namespace, name string) string { return fmt.Sprintf("%s_%s", namespace, name) } @@ -1629,6 +2022,30 @@ func newConfiguratorWithIngress(t *testing.T) *configs.Configurator { return c } +func newConfiguratorWithIngressWithCustomAnnotations(t *testing.T, annotations map[string]string) *configs.Configurator { + t.Helper() + + ingressEx := createCafeIngressExWithCustomAnnotations(annotations) + c := newConfigurator(t) + _, err := c.AddOrUpdateIngress(&ingressEx) + if err != nil { + t.Fatal(err) + } + return c +} + +func newConfiguratorWithMergeableIngressCustomAnnotations(t *testing.T, masterAnnotations, coffeeAnnotations, teaAnnotations map[string]string) *configs.Configurator { + t.Helper() + + ingressEx := createMergeableIngressWithCustomAnnotations(masterAnnotations, coffeeAnnotations, teaAnnotations) + c := newConfigurator(t) + _, err := c.AddOrUpdateMergeableIngress(ingressEx) + if err != nil { + t.Fatal(err) + } + return c +} + func newConfigurator(t *testing.T) *configs.Configurator { t.Helper() diff --git a/internal/telemetry/data.avdl b/internal/telemetry/data.avdl index 74dd96e514..d70633f21a 100644 --- a/internal/telemetry/data.avdl +++ b/internal/telemetry/data.avdl @@ -84,5 +84,8 @@ It is the UID of the `kube-system` Namespace. */ /** GlobalConfiguration indicates if a GlobalConfiguration resource is used. */ boolean? GlobalConfiguration = null; + /** IngressAnnotations is the list of annotations resources managed by NGINX Ingress Controller */ + union {null, array} IngressAnnotations = null; + } } diff --git a/internal/telemetry/exporter.go b/internal/telemetry/exporter.go index dc0b59f0f3..05b687009f 100644 --- a/internal/telemetry/exporter.go +++ b/internal/telemetry/exporter.go @@ -99,4 +99,6 @@ type NICResourceCounts struct { WAFPolicies int64 // GlobalConfiguration indicates if a GlobalConfiguration resource is used. GlobalConfiguration bool + // IngressAnnotations is the list of annotations resources managed by NGINX Ingress Controller + IngressAnnotations []string } diff --git a/internal/telemetry/nicresourcecounts_attributes_generated.go b/internal/telemetry/nicresourcecounts_attributes_generated.go index 269c605790..896180518a 100644 --- a/internal/telemetry/nicresourcecounts_attributes_generated.go +++ b/internal/telemetry/nicresourcecounts_attributes_generated.go @@ -30,6 +30,7 @@ func (d *NICResourceCounts) Attributes() []attribute.KeyValue { attrs = append(attrs, attribute.Int64("OIDCPolicies", d.OIDCPolicies)) attrs = append(attrs, attribute.Int64("WAFPolicies", d.WAFPolicies)) attrs = append(attrs, attribute.Bool("GlobalConfiguration", d.GlobalConfiguration)) + attrs = append(attrs, attribute.StringSlice("IngressAnnotations", d.IngressAnnotations)) return attrs }