diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 65b560cb0..0f2b697be 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -47,6 +47,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string parentRefs: description: |- ParentRefs defines references to parent IngressRoute resources for multi-layer routing. @@ -509,6 +513,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string routes: description: Routes defines the list of routes. items: @@ -765,6 +773,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string routes: description: Routes defines the list of routes. items: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 9149b8607..8acdad5e0 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -48,6 +48,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string parentRefs: description: |- ParentRefs defines references to parent IngressRoute resources for multi-layer routing. diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml index 6bbfae936..d8466818a 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml @@ -48,6 +48,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string routes: description: Routes defines the list of routes. items: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml index 2b7e66887..b0206dc0f 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml @@ -48,6 +48,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string routes: description: Routes defines the list of routes. items: diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md index b68030294..3b705e657 100644 --- a/docs/content/reference/install-configuration/configuration-options.md +++ b/docs/content/reference/install-configuration/configuration-options.md @@ -354,7 +354,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | providers.kubernetescrd.certauthfilepath | Kubernetes certificate authority file path (not needed for in-cluster client). | | | providers.kubernetescrd.disableclusterscoperesources | Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). | false | | providers.kubernetescrd.endpoint | Kubernetes server endpoint (required for external cluster client). | | -| providers.kubernetescrd.ingressclass | Value of kubernetes.io/ingress.class annotation to watch for. | | +| providers.kubernetescrd.ingressclass | Value of ingressClassName field or kubernetes.io/ingress.class annotation to watch for. | | | providers.kubernetescrd.labelselector | Kubernetes label selector to use. | | | providers.kubernetescrd.namespaces | Kubernetes namespaces. | | | providers.kubernetescrd.nativelbbydefault | Defines whether to use Native Kubernetes load-balancing mode by default. | false | diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md index 9965dfebf..f44cd38cb 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md @@ -60,7 +60,7 @@ providers: | `providers.kubernetesCRD.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | | `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | [] | No | | `providers.kubernetesCRD.labelselector` | Allow filtering on specific resource objects only using label selectors.
Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | -| `providers.kubernetesCRD.ingressClass` | Value of `kubernetes.io/ingress.class` annotation that identifies resource objects to be processed.
If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No | +| `providers.kubernetesCRD.ingressClass` | Value of `spec.ingressClassName` field (or the deprecated `kubernetes.io/ingress.class` annotation) that identifies resource objects to be processed.
If empty, resources missing the field/annotation, having an empty value, or the value `traefik` are processed.
The `spec.ingressClassName` field takes precedence over the annotation. | "" | No | | `providers.kubernetesCRD.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | | `providers.kubernetesCRD.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.
It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No | | `providers.kubernetesCRD.allowCrossNamespace` | Allows the `IngressRoutes` to reference resources in namespaces other than theirs. | false | No | diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md index fc7ae06b2..1261dd97e 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md @@ -21,6 +21,7 @@ metadata: namespace: apps spec: + ingressClassName: traefik-lb entryPoints: - web parentRefs: @@ -79,6 +80,7 @@ spec: | Field | Description | Default | Required | |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `ingressClassName` | Defines the [IngressClass](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) cluster resource to use. It replaces the deprecated `kubernetes.io/ingress.class` annotation.
The spec field takes precedence over the annotation. | | No | | `entryPoints` | List of [entry points](../../../../install-configuration/entrypoints.md) names.
If not specified, HTTP routers will accept requests from all EntryPoints in the list of default EntryPoints. | | No | | `parentRefs` | List of references to parent IngressRoute resources for multi-layer routing. When specified, this IngressRoute's routers become children of the referenced parent IngressRoute's routers. See [Multi-Layer Routing](#multi-layer-routing-with-ingressroutes) section for details. | | No | | `parentRefs[n].name` | Name of the referenced parent IngressRoute resource. | | Yes | diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md index 4ae214dae..8ebfe4e30 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md @@ -24,6 +24,7 @@ metadata: namespace: apps spec: + ingressClassName: traefik-lb entryPoints: - footcp routes: @@ -58,6 +59,7 @@ spec: | Field | Description | Default | Required | |-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|-----------------------| +| `ingressClassName` | Defines the [IngressClass](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) cluster resource to use. It replaces the deprecated `kubernetes.io/ingress.class` annotation.
The spec field takes precedence over the annotation. | | No | | `entryPoints` | List of entrypoints names. | | No | | `routes` | List of routes. | | Yes | | `routes[n].match` | Defines the [rule](../../../tcp/routing/rules-and-priority.md#rules) of the underlying router. | | Yes | diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md b/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md index eadfd1773..7ef8922f2 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md @@ -18,6 +18,7 @@ metadata: name: ingressrouteudpfoo namespace: apps spec: + ingressClassName: traefik-lb entryPoints: - fooudp # The entry point where Traefik listens for incoming traffic. routes: @@ -32,6 +33,7 @@ spec: | Field | Description | Default | Required | |------------------------------------|-----------------------------|-------------------------------------------|-----------------------| +| `ingressClassName` | Defines the [IngressClass](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) cluster resource to use. It replaces the deprecated `kubernetes.io/ingress.class` annotation.
The spec field takes precedence over the annotation. | | No | | `entryPoints` | List of entrypoints names. | | No | | ` routes ` | List of routes. | | Yes | | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions. See [here](#externalname-service) for `ExternalName Service` setup. | | No | diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index a2af326f9..43085de3b 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -48,6 +48,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string parentRefs: description: |- ParentRefs defines references to parent IngressRoute resources for multi-layer routing. @@ -510,6 +514,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string routes: description: Routes defines the list of routes. items: @@ -766,6 +774,10 @@ spec: items: type: string type: array + ingressClassName: + description: IngressClassName defines the name of the IngressClass + cluster resource. + type: string routes: description: Routes defines the list of routes. items: diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_deprecated_annotation_only.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_deprecated_annotation_only.yml new file mode 100644 index 000000000..19efc7038 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_deprecated_annotation_only.yml @@ -0,0 +1,17 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.route + namespace: default + annotations: + kubernetes.io/ingress.class: traefik-lb + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_ingressclassname.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_ingressclassname.yml new file mode 100644 index 000000000..8d9171c4f --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_ingressclassname.yml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + ingressClassName: traefik-lb + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_ingressclassname_and_deprecated_annotation.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_ingressclassname_and_deprecated_annotation.yml new file mode 100644 index 000000000..b3b3dcb4b --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_ingressclassname_and_deprecated_annotation.yml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.route + namespace: default + annotations: + kubernetes.io/ingress.class: traefik + +spec: + entryPoints: + - foo + + ingressClassName: traefik-lb + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_deprecated_annotation_only.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_deprecated_annotation_only.yml new file mode 100644 index 000000000..892cfdb14 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_deprecated_annotation_only.yml @@ -0,0 +1,16 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + annotations: + kubernetes.io/ingress.class: traefik-lb + +spec: + entryPoints: + - foo + + routes: + - services: + - name: whoamiudp + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_ingressclassname.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_ingressclassname.yml new file mode 100644 index 000000000..0481c6a06 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_ingressclassname.yml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + ingressClassName: traefik-lb + + routes: + - services: + - name: whoamiudp + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_ingressclassname_and_deprecated_annotation.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_ingressclassname_and_deprecated_annotation.yml new file mode 100644 index 000000000..db436d1ca --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_ingressclassname_and_deprecated_annotation.yml @@ -0,0 +1,25 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + annotations: + kubernetes.io/ingress.class: traefik + +spec: + entryPoints: + - foo + + ingressClassName: traefik-lb + + routes: + - services: + - name: whoamiudp + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_deprecated_annotation_only.yml b/pkg/provider/kubernetes/crd/fixtures/with_deprecated_annotation_only.yml new file mode 100644 index 000000000..c131ec9c3 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_deprecated_annotation_only.yml @@ -0,0 +1,19 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + annotations: + kubernetes.io/ingress.class: traefik-lb + +spec: + entryPoints: + - foo + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_ingressclassname.yml b/pkg/provider/kubernetes/crd/fixtures/with_ingressclassname.yml new file mode 100644 index 000000000..13d964d27 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_ingressclassname.yml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + ingressClassName: traefik-lb + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/with_ingressclassname_and_deprecated_annotation.yml b/pkg/provider/kubernetes/crd/fixtures/with_ingressclassname_and_deprecated_annotation.yml new file mode 100644 index 000000000..df8d9ee70 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_ingressclassname_and_deprecated_annotation.yml @@ -0,0 +1,28 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: traefik-lb +spec: + controller: traefik.io/ingress-controller +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + annotations: + kubernetes.io/ingress.class: traefik + +spec: + entryPoints: + - foo + + ingressClassName: traefik-lb + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 181724942..80dbc65ed 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -57,7 +57,7 @@ type Provider struct { AllowCrossNamespace bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"` AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"` LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"` - IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + IngressClass string `description:"Value of ingressClassName field or kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"` @@ -1447,9 +1447,21 @@ func makeID(namespace, name string) string { return namespace + "-" + name } -func shouldProcessIngress(ingressClass, ingressClassAnnotation string) bool { - return ingressClass == ingressClassAnnotation || - (len(ingressClass) == 0 && ingressClassAnnotation == traefikDefaultIngressClass) +func shouldProcessIngress(ingressClass, ingressClassName string) bool { + return ingressClass == ingressClassName || + (len(ingressClass) == 0 && ingressClassName == traefikDefaultIngressClass) +} + +// getIngressClassName returns the ingress class name from the spec field or falls back to the +// deprecated annotation. Returns the class name and whether the deprecated annotation was used. +func getIngressClassName(specIngressClassName *string, annotations map[string]string) (string, bool) { + if specIngressClassName != nil { + return *specIngressClassName, false + } + if annotation, ok := annotations[annotationKubernetesIngressClass]; ok && annotation != "" { + return annotation, true + } + return "", false } func getTLS(k8sClient Client, secretName, namespace string) (*tls.CertAndStores, error) { diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 26f8b1bfd..430b46ce3 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -37,8 +37,11 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli for _, ingressRoute := range client.GetIngressRoutes() { logger := log.Ctx(ctx).With().Str("ingress", ingressRoute.Name).Str("namespace", ingressRoute.Namespace).Logger() - // TODO keep the name ingressClass? - if !shouldProcessIngress(p.IngressClass, ingressRoute.Annotations[annotationKubernetesIngressClass]) { + ingressClassName, usingDeprecatedAnnotation := getIngressClassName(ingressRoute.Spec.IngressClassName, ingressRoute.Annotations) + if usingDeprecatedAnnotation { + logger.Warn().Msgf("'%s' is a deprecated annotation, please use spec.ingressClassName instead.", annotationKubernetesIngressClass) + } + if !shouldProcessIngress(p.IngressClass, ingressClassName) { continue } diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index bbe48f638..77a363606 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -28,7 +28,11 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client for _, ingressRouteTCP := range client.GetIngressRouteTCPs() { logger := log.Ctx(ctx).With().Str("ingress", ingressRouteTCP.Name).Str("namespace", ingressRouteTCP.Namespace).Logger() - if !shouldProcessIngress(p.IngressClass, ingressRouteTCP.Annotations[annotationKubernetesIngressClass]) { + ingressClassName, usingDeprecatedAnnotation := getIngressClassName(ingressRouteTCP.Spec.IngressClassName, ingressRouteTCP.Annotations) + if usingDeprecatedAnnotation { + logger.Warn().Msgf("'%s' is a deprecated annotation, please use spec.ingressClassName instead.", annotationKubernetesIngressClass) + } + if !shouldProcessIngress(p.IngressClass, ingressClassName) { continue } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 758844bbb..af3be57e3 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -1300,6 +1300,135 @@ func TestLoadIngressRouteTCPs(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple TCP Ingress Route, with ingressClassName", + paths: []string{"tcp/services.yml", "tcp/with_ingressclassname.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{}, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-test.route-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-test.route-fdd3e9338e47a45efefc": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "Simple TCP Ingress Route, with ingressClassName and deprecated annotation", + paths: []string{"tcp/services.yml", "tcp/with_ingressclassname_and_deprecated_annotation.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{}, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-test.route-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-test.route-fdd3e9338e47a45efefc": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "Simple TCP Ingress Route, with deprecated annotation only", + paths: []string{"tcp/services.yml", "tcp/with_deprecated_annotation_only.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{}, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-test.route-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-test.route-fdd3e9338e47a45efefc": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, { desc: "Ingress Route with IPv6 backends", paths: []string{ @@ -4131,6 +4260,153 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "Simple Ingress Route, with ingressClassName", + paths: []string{"services.yml", "with_ingressclassname.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "Simple Ingress Route, with ingressClassName and deprecated annotation", + paths: []string{"services.yml", "with_ingressclassname_and_deprecated_annotation.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, + { + desc: "Simple Ingress Route, with deprecated annotation only", + paths: []string{"services.yml", "with_deprecated_annotation_only.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-6b204d94623b3df4370c": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + }, + }, { desc: "Simple Ingress Route, with basic auth middleware", paths: []string{"services.yml", "with_auth.yml"}, @@ -5911,6 +6187,132 @@ func TestLoadIngressRouteUDPs(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple UDP Ingress Route, with ingressClassName", + paths: []string{"udp/services.yml", "udp/with_ingressclassname.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple UDP Ingress Route, with ingressClassName and deprecated annotation", + paths: []string{"udp/services.yml", "udp/with_ingressclassname_and_deprecated_annotation.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple UDP Ingress Route, with deprecated annotation only", + paths: []string{"udp/services.yml", "udp/with_deprecated_annotation_only.yml"}, + ingressClass: "traefik-lb", + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "One ingress Route with two different routes", paths: []string{"udp/services.yml", "udp/with_two_routes.yml"}, diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 5b0e01e78..719348b06 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -22,7 +22,11 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client for _, ingressRouteUDP := range client.GetIngressRouteUDPs() { logger := log.Ctx(ctx).With().Str("ingress", ingressRouteUDP.Name).Str("namespace", ingressRouteUDP.Namespace).Logger() - if !shouldProcessIngress(p.IngressClass, ingressRouteUDP.Annotations[annotationKubernetesIngressClass]) { + ingressClassName, usingDeprecatedAnnotation := getIngressClassName(ingressRouteUDP.Spec.IngressClassName, ingressRouteUDP.Annotations) + if usingDeprecatedAnnotation { + logger.Warn().Msgf("'%s' is a deprecated annotation, please use spec.ingressClassName instead.", annotationKubernetesIngressClass) + } + if !shouldProcessIngress(p.IngressClass, ingressClassName) { continue } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index 36c50286b..9a188ec96 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -9,13 +9,15 @@ import ( // IngressRouteSpec defines the desired state of IngressRoute. type IngressRouteSpec struct { - // Routes defines the list of routes. - Routes []Route `json:"routes"` + // IngressClassName defines the name of the IngressClass cluster resource. + IngressClassName *string `json:"ingressClassName,omitempty"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` + // Routes defines the list of routes. + Routes []Route `json:"routes"` // TLS defines the TLS configuration. // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/routing/router/#tls TLS *TLS `json:"tls,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go index 1d719ee9a..a5fe29f93 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go @@ -9,13 +9,15 @@ import ( // IngressRouteTCPSpec defines the desired state of IngressRouteTCP. type IngressRouteTCPSpec struct { - // Routes defines the list of routes. - Routes []RouteTCP `json:"routes"` + // IngressClassName defines the name of the IngressClass cluster resource. + IngressClassName *string `json:"ingressClassName,omitempty"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` + // Routes defines the list of routes. + Routes []RouteTCP `json:"routes"` // TLS defines the TLS configuration on a layer 4 / TCP Route. // More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/tcp/routing/router/#tls TLS *TLSTCP `json:"tls,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go index e7c8d1c6d..fc0d52f35 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go @@ -7,13 +7,15 @@ import ( // IngressRouteUDPSpec defines the desired state of a IngressRouteUDP. type IngressRouteUDPSpec struct { - // Routes defines the list of routes. - Routes []RouteUDP `json:"routes"` + // IngressClassName defines the name of the IngressClass cluster resource. + IngressClassName *string `json:"ingressClassName,omitempty"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. // More info: https://doc.traefik.io/traefik/v3.6/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` + // Routes defines the list of routes. + Routes []RouteUDP `json:"routes"` } // RouteUDP holds the UDP route configuration. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index 6e50ab3dd..d577fef82 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -451,6 +451,16 @@ func (in *IngressRouteRef) DeepCopy() *IngressRouteRef { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) { *out = *in + if in.IngressClassName != nil { + in, out := &in.IngressClassName, &out.IngressClassName + *out = new(string) + **out = **in + } + if in.EntryPoints != nil { + in, out := &in.EntryPoints, &out.EntryPoints + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Routes != nil { in, out := &in.Routes, &out.Routes *out = make([]Route, len(*in)) @@ -458,11 +468,6 @@ func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.EntryPoints != nil { - in, out := &in.EntryPoints, &out.EntryPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLS) @@ -549,6 +554,16 @@ func (in *IngressRouteTCPList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRouteTCPSpec) DeepCopyInto(out *IngressRouteTCPSpec) { *out = *in + if in.IngressClassName != nil { + in, out := &in.IngressClassName, &out.IngressClassName + *out = new(string) + **out = **in + } + if in.EntryPoints != nil { + in, out := &in.EntryPoints, &out.EntryPoints + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Routes != nil { in, out := &in.Routes, &out.Routes *out = make([]RouteTCP, len(*in)) @@ -556,11 +571,6 @@ func (in *IngressRouteTCPSpec) DeepCopyInto(out *IngressRouteTCPSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.EntryPoints != nil { - in, out := &in.EntryPoints, &out.EntryPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSTCP) @@ -642,6 +652,16 @@ func (in *IngressRouteUDPList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRouteUDPSpec) DeepCopyInto(out *IngressRouteUDPSpec) { *out = *in + if in.IngressClassName != nil { + in, out := &in.IngressClassName, &out.IngressClassName + *out = new(string) + **out = **in + } + if in.EntryPoints != nil { + in, out := &in.EntryPoints, &out.EntryPoints + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Routes != nil { in, out := &in.Routes, &out.Routes *out = make([]RouteUDP, len(*in)) @@ -649,11 +669,6 @@ func (in *IngressRouteUDPSpec) DeepCopyInto(out *IngressRouteUDPSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.EntryPoints != nil { - in, out := &in.EntryPoints, &out.EntryPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } return }