mirror of
https://github.com/traefik/traefik.git
synced 2025-08-06 22:57:14 +02:00
NGINX Ingress Provider
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
b39ee8ede5
commit
9bd5c61782
@ -245,6 +245,10 @@ linters:
|
|||||||
text: Function 'buildConstructor' has too many statements
|
text: Function 'buildConstructor' has too many statements
|
||||||
linters:
|
linters:
|
||||||
- funlen
|
- funlen
|
||||||
|
- path: pkg/provider/kubernetes/ingress-nginx/kubernetes.go
|
||||||
|
text: Function 'loadConfiguration' has too many statements
|
||||||
|
linters:
|
||||||
|
- funlen
|
||||||
- path: pkg/tracing/haystack/logger.go
|
- path: pkg/tracing/haystack/logger.go
|
||||||
linters:
|
linters:
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
title: "Traefik Kubernetes Ingress NGINX Documentation"
|
||||||
|
description: "Understand the requirements, routing configuration, and how to set up the Kubernetes Ingress NGINX provider. Read the technical documentation."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Traefik & Ingresses with NGINX Annotations
|
||||||
|
|
||||||
|
The experimental Traefik Kubernetes Ingress NGINX provider is a Kubernetes Ingress controller; i.e,
|
||||||
|
it manages access to cluster services by supporting the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) specification.
|
||||||
|
It also supports some of the [ingress-nginx](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/) annotations on ingresses to customize their behavior.
|
||||||
|
|
||||||
|
!!! warning "Ingress Discovery"
|
||||||
|
|
||||||
|
The Kubernetes Ingress NGINX provider is discovering by default all Ingresses in the cluster,
|
||||||
|
which may lead to duplicated routers if you are also using the Kubernetes Ingress provider.
|
||||||
|
We recommend to use IngressClass for the Ingresses you want to be handled by this provider,
|
||||||
|
or to use the `watchNamespace` or `watchNamespaceSelector` options to limit the discovery of Ingresses to a specific namespace or set of namespaces.
|
||||||
|
|
||||||
|
## Configuration Example
|
||||||
|
|
||||||
|
As this provider is an experimental feature, it needs to be enabled in the experimental and in the provider sections of the configuration.
|
||||||
|
You can enable the Kubernetes Ingress NGINX provider as detailed below:
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
experimental:
|
||||||
|
kubernetesIngressNGINX: true
|
||||||
|
|
||||||
|
providers:
|
||||||
|
kubernetesIngressNGINX: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[experimental.kubernetesIngressNGINX]
|
||||||
|
|
||||||
|
[providers.kubernetesIngressNGINX]
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--experimental.kubernetesingressnginx=true
|
||||||
|
--providers.kubernetesingressnginx=true
|
||||||
|
```
|
||||||
|
|
||||||
|
The provider then watches for incoming ingresses events, such as the example below,
|
||||||
|
and derives the corresponding dynamic configuration from it,
|
||||||
|
which in turn creates the resulting routers, services, handlers, etc.
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
<!-- markdownlint-disable MD013 -->
|
||||||
|
|
||||||
|
| Field | Description | Default | Required |
|
||||||
|
|:------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
|
||||||
|
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.watchNamespace` | Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.watchNamespaceSelector` | Selector selects namespaces the controller watches for updates to Kubernetes objects. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.ingressClass` | Name of the ingress class this controller satisfies. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.controllerClass` | Ingress Class Controller value this controller satisfies. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.watchIngressWithoutClass` | Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. | false | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.ingressClassByName` | Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. | false | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.publishService` | Service fronting the Ingress controller. Takes the form namespace/name. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.publishStatusAddress` | Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.defaultBackendService` | Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'. | "" | No |
|
||||||
|
| `providers.kubernetesIngressNGINX.disableSvcExternalName` | Disable support for Services of type ExternalName. | false | No |
|
||||||
|
|
||||||
|
<!-- markdownlint-enable MD013 -->
|
||||||
|
|
||||||
|
### `endpoint`
|
||||||
|
|
||||||
|
The Kubernetes server endpoint URL.
|
||||||
|
|
||||||
|
When deployed into Kubernetes, Traefik reads the environment variables `KUBERNETES_SERVICE_HOST`
|
||||||
|
and `KUBERNETES_SERVICE_PORT` or `KUBECONFIG` to construct the endpoint.
|
||||||
|
|
||||||
|
The access token is looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token`
|
||||||
|
and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`.
|
||||||
|
Both are mounted automatically when deployed inside Kubernetes.
|
||||||
|
|
||||||
|
The endpoint may be specified to override the environment variable values inside
|
||||||
|
a cluster.
|
||||||
|
|
||||||
|
When the environment variables are not found, Traefik tries to connect to the
|
||||||
|
Kubernetes API server with an external-cluster client.
|
||||||
|
|
||||||
|
In this case, the endpoint is required.
|
||||||
|
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes
|
||||||
|
cluster using the granted authentication and authorization of the associated kubeconfig.
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
providers:
|
||||||
|
kubernetesIngressNGINX:
|
||||||
|
endpoint: "http://localhost:8080"
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[providers.kubernetesIngressNGINX]
|
||||||
|
endpoint = "http://localhost:8080"
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--providers.kubernetesingressnginx.endpoint=http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## Routing Configuration
|
||||||
|
|
||||||
|
See the dedicated section in [routing](../../../routing-configuration/kubernetes/ingress-nginx.md).
|
||||||
|
|
||||||
|
{!traefik-for-business-applications.md!}
|
@ -0,0 +1,394 @@
|
|||||||
|
---
|
||||||
|
title: "Traefik Kubernetes Ingress NGINX Routing Configuration"
|
||||||
|
description: "Understand the routing configuration for the Kubernetes Ingress NGINX Controller and Traefik Proxy. Read the technical documentation."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Traefik & Ingresses with NGINX Annotations
|
||||||
|
|
||||||
|
The experimental Kubernetes Controller for Ingresses with NGINX annotations.
|
||||||
|
{: .subtitle }
|
||||||
|
|
||||||
|
!!! warning "Ingress Discovery"
|
||||||
|
|
||||||
|
The Kubernetes Ingress NGINX provider is discovering by default all Ingresses in the cluster,
|
||||||
|
which may lead to duplicated routers if you are also using the Kubernetes Ingress provider.
|
||||||
|
We recommend to use IngressClass for the Ingresses you want to be handled by this provider,
|
||||||
|
or to use the `watchNamespace` or `watchNamespaceSelector` options to limit the discovery of Ingresses to a specific namespace or set of namespaces.
|
||||||
|
|
||||||
|
## Routing Configuration
|
||||||
|
|
||||||
|
The Kubernetes Ingress NGINX provider watches for incoming ingresses events, such as the example below,
|
||||||
|
and derives the corresponding dynamic configuration from it,
|
||||||
|
which in turn will create the resulting routers, services, handlers, etc.
|
||||||
|
|
||||||
|
## Configuration Example
|
||||||
|
|
||||||
|
??? example "Configuring Kubernetes Ingress NGINX Controller"
|
||||||
|
|
||||||
|
```yaml tab="RBAC"
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
- pods
|
||||||
|
- secrets
|
||||||
|
- endpoints
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingresses/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingressclasses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- discovery.k8s.io
|
||||||
|
resources:
|
||||||
|
- endpointslices
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- get
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: default
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Traefik"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
labels:
|
||||||
|
app: traefik
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: traefik
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: traefik
|
||||||
|
spec:
|
||||||
|
serviceAccountName: traefik-ingress-controller
|
||||||
|
containers:
|
||||||
|
- name: traefik
|
||||||
|
image: traefik:v3.4
|
||||||
|
args:
|
||||||
|
- --entryPoints.web.address=:80
|
||||||
|
- --providers.kubernetesingressnginx
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
containerPort: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: traefik
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
selector:
|
||||||
|
app: traefik
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Whoami"
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
labels:
|
||||||
|
app: whoami
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: whoami
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: whoami
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: whoami
|
||||||
|
image: traefik/whoami
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: whoami
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Ingress"
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: IngressClass
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
controller: k8s.io/ingress-nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: myingress
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: whoami.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /foo
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
## Annotations Support
|
||||||
|
|
||||||
|
This section lists all known NGINX Ingress annotations, split between those currently implemented (with limitations if any) and those not implemented.
|
||||||
|
Limitations or behavioral differences are indicated where relevant.
|
||||||
|
|
||||||
|
!!! warning "Global configuration"
|
||||||
|
|
||||||
|
Traefik does not expose all global configuration options to control default behaviors for ingresses.
|
||||||
|
|
||||||
|
Some behaviors that are globally configurable in NGINX (such as default SSL redirect, rate limiting, or affinity) are currently not supported and cannot be overridden per-ingress as in NGINX.
|
||||||
|
|
||||||
|
### Caveats and Key Behavioral Differences
|
||||||
|
|
||||||
|
- **Authentication**: Forward auth behaves differently and session caching is not supported. NGINX supports sub-request based auth, while Traefik forwards the original request.
|
||||||
|
- **Session Affinity**: Only persistent mode is supported.
|
||||||
|
- **Leader Election**: Not supported; no cluster mode with leader election.
|
||||||
|
- **Default Backend**: Only `defaultBackend` in Ingress spec is supported; the annotation is ignored.
|
||||||
|
- **Load Balancing**: Only round_robin is supported; EWMA and IP hash are not supported.
|
||||||
|
- **CORS**: NGINX responds with all configured headers unconditionally; Traefik handles headers differently between pre-flight and regular requests.
|
||||||
|
- **TLS/Backend Protocols**: AUTO_HTTP, FCGI and some TLS options are not supported in Traefik.
|
||||||
|
- **Path Handling**: Traefik preserves trailing slashes by default; NGINX removes them unless configured otherwise.
|
||||||
|
|
||||||
|
### Supported NGINX Annotations
|
||||||
|
|
||||||
|
| Annotation | Limitations / Notes |
|
||||||
|
|-------------------------------------------------------|--------------------------------------------------------------------------------------------|
|
||||||
|
| `nginx.ingress.kubernetes.io/affinity` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/affinity-mode` | Only persistent mode supported; balanced/canary not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-type` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-secret` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-secret-type` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-realm` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-url` | Only URL and response headers copy supported. Forward auth behaves differently than NGINX. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-method` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-response-headers` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/ssl-redirect` | Cannot opt-out per route if enabled globally. |
|
||||||
|
| `nginx.ingress.kubernetes.io/force-ssl-redirect` | Cannot opt-out per route if enabled globally. |
|
||||||
|
| `nginx.ingress.kubernetes.io/ssl-passthrough` | Some differences in SNI/default backend handling. |
|
||||||
|
| `nginx.ingress.kubernetes.io/use-regex` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-name` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-path` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-domain` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-samesite` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/load-balance` | Only round_robin supported; ewma and IP hash not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/backend-protocol` | FCGI and AUTO_HTTP not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-cors` | Partial support. |
|
||||||
|
| `nginx.ingress.kubernetes.io/cors-allow-credentials` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/cors-allow-headers` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/cors-allow-methods` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/cors-allow-origin` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/cors-max-age` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-server-name` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-name` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-verify` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-secret` | |
|
||||||
|
| `nginx.ingress.kubernetes.io/service-upstream` | |
|
||||||
|
|
||||||
|
### Unsupported NGINX Annotations
|
||||||
|
|
||||||
|
All other NGINX annotations not listed above, including but not limited to:
|
||||||
|
|
||||||
|
| Annotation | Notes |
|
||||||
|
|-----------------------------------------------------------------------------|------------------------------------------------------|
|
||||||
|
| `nginx.ingress.kubernetes.io/app-root` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/affinity-canary-behavior` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-tls-secret` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-tls-verify-depth` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-tls-verify-client` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-tls-error-page` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-tls-match-cn` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-cache-key` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-cache-duration` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-keepalive` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-keepalive-share-vars` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-keepalive-requests` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-keepalive-timeout` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-proxy-set-headers` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/auth-snippet` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-global-auth` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary-by-header` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary-by-header-value` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary-by-header-pattern` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary-by-cookie` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary-weight` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/canary-weight-total` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/client-body-buffer-size` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/configuration-snippet` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/custom-http-errors` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/disable-proxy-intercept-errors` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/default-backend` | Not supported; use `defaultBackend` in Ingress spec. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-rate-after` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-rate` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-whitelist` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-rps` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-rpm` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-burst-multiplier` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/limit-connections` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/global-rate-limit` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/global-rate-limit-window` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/global-rate-limit-key` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/global-rate-limit-ignored-cidrs` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/permanent-redirect` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/permanent-redirect-code` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/temporal-redirect` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/preserve-trailing-slash` | Not supported; Traefik preserves by default. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-cookie-domain` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-cookie-path` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-connect-timeout` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-send-timeout` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-read-timeout` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-next-upstream` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-next-upstream-timeout` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-next-upstream-tries` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-request-buffering` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-redirect-from` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-redirect-to` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-http-version` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-ciphers` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-verify-depth` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-ssl-protocols` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-rewrite-log` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/rewrite-target` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/satisfy` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/server-alias` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/server-snippet` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-expires` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/session-cookie-change-on-failure` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/ssl-ciphers` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/ssl-prefer-server-ciphers` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/connection-proxy-header` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-access-log` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-opentracing` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/opentracing-trust-incoming-span` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-opentelemetry` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-modsecurity` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/enable-owasp-core-rules` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/modsecurity-transaction-id` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/modsecurity-snippet` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/mirror-request-body` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/mirror-target` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/mirror-host` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/x-forwarded-prefix` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/upstream-hash-by` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/upstream-vhost` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/denylist-source-range` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/whitelist-source-range` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-buffering` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-buffers-number` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-buffer-size` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/proxy-max-temp-file-size` | Not supported. |
|
||||||
|
| `nginx.ingress.kubernetes.io/stream-snippet` | Not supported. |
|
@ -339,6 +339,9 @@ Enable debug mode for the FastProxy implementation. (Default: ```false```)
|
|||||||
`--experimental.kubernetesgateway`:
|
`--experimental.kubernetesgateway`:
|
||||||
(Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
(Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
||||||
|
|
||||||
|
`--experimental.kubernetesingressnginx`:
|
||||||
|
Allow the Kubernetes Ingress NGINX provider usage. (Default: ```false```)
|
||||||
|
|
||||||
`--experimental.localplugins.<name>`:
|
`--experimental.localplugins.<name>`:
|
||||||
Local plugins configuration. (Default: ```false```)
|
Local plugins configuration. (Default: ```false```)
|
||||||
|
|
||||||
@ -1047,6 +1050,51 @@ Ingress refresh throttle duration (Default: ```0```)
|
|||||||
`--providers.kubernetesingress.token`:
|
`--providers.kubernetesingress.token`:
|
||||||
Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token.
|
Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token.
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx`:
|
||||||
|
Enable Kubernetes Ingress NGINX provider. (Default: ```false```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.certauthfilepath`:
|
||||||
|
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.controllerclass`:
|
||||||
|
Ingress Class Controller value this controller satisfies. (Default: ```k8s.io/ingress-nginx```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.defaultbackendservice`:
|
||||||
|
Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'.
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.disablesvcexternalname`:
|
||||||
|
Disable support for Services of type ExternalName. (Default: ```false```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.endpoint`:
|
||||||
|
Kubernetes server endpoint (required for external cluster client).
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.ingressclass`:
|
||||||
|
Name of the ingress class this controller satisfies. (Default: ```nginx```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.ingressclassbyname`:
|
||||||
|
Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. (Default: ```false```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.publishservice`:
|
||||||
|
Service fronting the Ingress controller. Takes the form 'namespace/name'.
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.publishstatusaddress`:
|
||||||
|
Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies.
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.throttleduration`:
|
||||||
|
Ingress refresh throttle duration. (Default: ```0```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.token`:
|
||||||
|
Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token.
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.watchingresswithoutclass`:
|
||||||
|
Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. (Default: ```false```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.watchnamespace`:
|
||||||
|
Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty.
|
||||||
|
|
||||||
|
`--providers.kubernetesingressnginx.watchnamespaceselector`:
|
||||||
|
Selector selects namespaces the controller watches for updates to Kubernetes objects.
|
||||||
|
|
||||||
`--providers.nomad`:
|
`--providers.nomad`:
|
||||||
Enable Nomad backend with default settings. (Default: ```false```)
|
Enable Nomad backend with default settings. (Default: ```false```)
|
||||||
|
|
||||||
|
@ -339,6 +339,9 @@ Enable debug mode for the FastProxy implementation. (Default: ```false```)
|
|||||||
`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`:
|
`TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`:
|
||||||
(Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
(Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_EXPERIMENTAL_KUBERNETESINGRESSNGINX`:
|
||||||
|
Allow the Kubernetes Ingress NGINX provider usage. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_EXPERIMENTAL_LOCALPLUGINS_<NAME>`:
|
`TRAEFIK_EXPERIMENTAL_LOCALPLUGINS_<NAME>`:
|
||||||
Local plugins configuration. (Default: ```false```)
|
Local plugins configuration. (Default: ```false```)
|
||||||
|
|
||||||
@ -999,6 +1002,51 @@ Kubernetes bearer token (not needed for in-cluster client). It accepts either a
|
|||||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS`:
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS`:
|
||||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX`:
|
||||||
|
Enable Kubernetes Ingress NGINX provider. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_CERTAUTHFILEPATH`:
|
||||||
|
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_CONTROLLERCLASS`:
|
||||||
|
Ingress Class Controller value this controller satisfies. (Default: ```k8s.io/ingress-nginx```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_DEFAULTBACKENDSERVICE`:
|
||||||
|
Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_DISABLESVCEXTERNALNAME`:
|
||||||
|
Disable support for Services of type ExternalName. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_ENDPOINT`:
|
||||||
|
Kubernetes server endpoint (required for external cluster client).
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_INGRESSCLASS`:
|
||||||
|
Name of the ingress class this controller satisfies. (Default: ```nginx```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_INGRESSCLASSBYNAME`:
|
||||||
|
Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_PUBLISHSERVICE`:
|
||||||
|
Service fronting the Ingress controller. Takes the form 'namespace/name'.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_PUBLISHSTATUSADDRESS`:
|
||||||
|
Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_THROTTLEDURATION`:
|
||||||
|
Ingress refresh throttle duration. (Default: ```0```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_TOKEN`:
|
||||||
|
Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_WATCHINGRESSWITHOUTCLASS`:
|
||||||
|
Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_WATCHNAMESPACE`:
|
||||||
|
Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_WATCHNAMESPACESELECTOR`:
|
||||||
|
Selector selects namespaces the controller watches for updates to Kubernetes objects.
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ALLOWEMPTYSERVICES`:
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ALLOWEMPTYSERVICES`:
|
||||||
Allow creation of services without endpoints. (Default: ```false```)
|
Allow creation of services without endpoints. (Default: ```false```)
|
||||||
|
|
||||||
|
@ -143,6 +143,21 @@
|
|||||||
ip = "foobar"
|
ip = "foobar"
|
||||||
hostname = "foobar"
|
hostname = "foobar"
|
||||||
publishedService = "foobar"
|
publishedService = "foobar"
|
||||||
|
[providers.kubernetesIngressNGINX]
|
||||||
|
endpoint = "foobar"
|
||||||
|
token = "foobar"
|
||||||
|
certAuthFilePath = "foobar"
|
||||||
|
throttleDuration = "42s"
|
||||||
|
watchNamespace = "foobar"
|
||||||
|
watchNamespaceSelector = "foobar"
|
||||||
|
ingressClass = "foobar"
|
||||||
|
controllerClass = "foobar"
|
||||||
|
watchIngressWithoutClass = true
|
||||||
|
ingressClassByName = true
|
||||||
|
publishService = "foobar"
|
||||||
|
publishStatusAddress = ["foobar", "foobar"]
|
||||||
|
defaultBackendService = "foobar"
|
||||||
|
disableSvcExternalName = true
|
||||||
[providers.kubernetesCRD]
|
[providers.kubernetesCRD]
|
||||||
endpoint = "foobar"
|
endpoint = "foobar"
|
||||||
token = "foobar"
|
token = "foobar"
|
||||||
@ -572,6 +587,7 @@
|
|||||||
[experimental]
|
[experimental]
|
||||||
abortOnPluginFailure = true
|
abortOnPluginFailure = true
|
||||||
otlplogs = true
|
otlplogs = true
|
||||||
|
kubernetesIngressNGINX = true
|
||||||
kubernetesGateway = true
|
kubernetesGateway = true
|
||||||
[experimental.plugins]
|
[experimental.plugins]
|
||||||
[experimental.plugins.Descriptor0]
|
[experimental.plugins.Descriptor0]
|
||||||
|
@ -158,6 +158,23 @@ providers:
|
|||||||
disableClusterScopeResources: true
|
disableClusterScopeResources: true
|
||||||
nativeLBByDefault: true
|
nativeLBByDefault: true
|
||||||
strictPrefixMatching: true
|
strictPrefixMatching: true
|
||||||
|
kubernetesIngressNGINX:
|
||||||
|
endpoint: foobar
|
||||||
|
token: foobar
|
||||||
|
certAuthFilePath: foobar
|
||||||
|
throttleDuration: 42s
|
||||||
|
watchNamespace: foobar
|
||||||
|
watchNamespaceSelector: foobar
|
||||||
|
ingressClass: foobar
|
||||||
|
controllerClass: foobar
|
||||||
|
watchIngressWithoutClass: true
|
||||||
|
ingressClassByName: true
|
||||||
|
publishService: foobar
|
||||||
|
publishStatusAddress:
|
||||||
|
- foobar
|
||||||
|
- foobar
|
||||||
|
defaultBackendService: foobar
|
||||||
|
disableSvcExternalName: true
|
||||||
kubernetesCRD:
|
kubernetesCRD:
|
||||||
endpoint: foobar
|
endpoint: foobar
|
||||||
token: foobar
|
token: foobar
|
||||||
@ -670,6 +687,7 @@ experimental:
|
|||||||
fastProxy:
|
fastProxy:
|
||||||
debug: true
|
debug: true
|
||||||
otlplogs: true
|
otlplogs: true
|
||||||
|
kubernetesIngressNGINX: true
|
||||||
kubernetesGateway: true
|
kubernetesGateway: true
|
||||||
core:
|
core:
|
||||||
defaultRuleSyntax: foobar
|
defaultRuleSyntax: foobar
|
||||||
|
@ -79,6 +79,7 @@ nav:
|
|||||||
- 'Swarm': 'providers/swarm.md'
|
- 'Swarm': 'providers/swarm.md'
|
||||||
- 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md'
|
- 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md'
|
||||||
- 'Kubernetes Ingress': 'providers/kubernetes-ingress.md'
|
- 'Kubernetes Ingress': 'providers/kubernetes-ingress.md'
|
||||||
|
- 'Kubernetes Ingress NGINX': 'reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md'
|
||||||
- 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md'
|
- 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md'
|
||||||
- 'Consul Catalog': 'providers/consul-catalog.md'
|
- 'Consul Catalog': 'providers/consul-catalog.md'
|
||||||
- 'Nomad': 'providers/nomad.md'
|
- 'Nomad': 'providers/nomad.md'
|
||||||
@ -99,6 +100,7 @@ nav:
|
|||||||
- 'Swarm': 'routing/providers/swarm.md'
|
- 'Swarm': 'routing/providers/swarm.md'
|
||||||
- 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md'
|
- 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md'
|
||||||
- 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md'
|
- 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md'
|
||||||
|
- 'Kubernetes Ingress NGINX': 'reference/routing-configuration/kubernetes/ingress-nginx.md'
|
||||||
- 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md'
|
- 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md'
|
||||||
- 'Consul Catalog': 'routing/providers/consul-catalog.md'
|
- 'Consul Catalog': 'routing/providers/consul-catalog.md'
|
||||||
- 'Nomad': 'routing/providers/nomad.md'
|
- 'Nomad': 'routing/providers/nomad.md'
|
||||||
@ -205,6 +207,7 @@ nav:
|
|||||||
- 'Kubernetes Gateway API' : 'reference/install-configuration/providers/kubernetes/kubernetes-gateway.md'
|
- 'Kubernetes Gateway API' : 'reference/install-configuration/providers/kubernetes/kubernetes-gateway.md'
|
||||||
- 'Kubernetes CRD' : 'reference/install-configuration/providers/kubernetes/kubernetes-crd.md'
|
- 'Kubernetes CRD' : 'reference/install-configuration/providers/kubernetes/kubernetes-crd.md'
|
||||||
- 'Kubernetes Ingress' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress.md'
|
- 'Kubernetes Ingress' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress.md'
|
||||||
|
- 'Kubernetes Ingress NGINX' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md'
|
||||||
- 'Docker': 'reference/install-configuration/providers/docker.md'
|
- 'Docker': 'reference/install-configuration/providers/docker.md'
|
||||||
- 'Swarm': 'reference/install-configuration/providers/swarm.md'
|
- 'Swarm': 'reference/install-configuration/providers/swarm.md'
|
||||||
- 'Hashicorp':
|
- 'Hashicorp':
|
||||||
@ -307,6 +310,7 @@ nav:
|
|||||||
- 'UDP' :
|
- 'UDP' :
|
||||||
- 'IngressRouteUDP' : 'reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md'
|
- 'IngressRouteUDP' : 'reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md'
|
||||||
- 'Ingress' : 'reference/routing-configuration/kubernetes/ingress.md'
|
- 'Ingress' : 'reference/routing-configuration/kubernetes/ingress.md'
|
||||||
|
- 'Ingress NGINX' : 'reference/routing-configuration/kubernetes/ingress-nginx.md'
|
||||||
- 'Label & Tag Providers' :
|
- 'Label & Tag Providers' :
|
||||||
- 'Docker' : 'reference/routing-configuration/other-providers/docker.md'
|
- 'Docker' : 'reference/routing-configuration/other-providers/docker.md'
|
||||||
- 'Swarm' : 'reference/routing-configuration/other-providers/swarm.md'
|
- 'Swarm' : 'reference/routing-configuration/other-providers/swarm.md'
|
||||||
|
@ -4,11 +4,12 @@ import "github.com/traefik/traefik/v3/pkg/plugins"
|
|||||||
|
|
||||||
// Experimental experimental Traefik features.
|
// Experimental experimental Traefik features.
|
||||||
type Experimental struct {
|
type Experimental struct {
|
||||||
Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"`
|
Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"`
|
||||||
LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"`
|
LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"`
|
||||||
AbortOnPluginFailure bool `description:"Defines whether all plugins must be loaded successfully for Traefik to start." json:"abortOnPluginFailure,omitempty" toml:"abortOnPluginFailure,omitempty" yaml:"abortOnPluginFailure,omitempty" export:"true"`
|
AbortOnPluginFailure bool `description:"Defines whether all plugins must be loaded successfully for Traefik to start." json:"abortOnPluginFailure,omitempty" toml:"abortOnPluginFailure,omitempty" yaml:"abortOnPluginFailure,omitempty" export:"true"`
|
||||||
FastProxy *FastProxyConfig `description:"Enables the FastProxy implementation." json:"fastProxy,omitempty" toml:"fastProxy,omitempty" yaml:"fastProxy,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
FastProxy *FastProxyConfig `description:"Enables the FastProxy implementation." json:"fastProxy,omitempty" toml:"fastProxy,omitempty" yaml:"fastProxy,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
OTLPLogs bool `description:"Enables the OpenTelemetry logs integration." json:"otlplogs,omitempty" toml:"otlplogs,omitempty" yaml:"otlplogs,omitempty" export:"true"`
|
OTLPLogs bool `description:"Enables the OpenTelemetry logs integration." json:"otlplogs,omitempty" toml:"otlplogs,omitempty" yaml:"otlplogs,omitempty" export:"true"`
|
||||||
|
KubernetesIngressNGINX bool `description:"Allow the Kubernetes Ingress NGINX provider usage." json:"kubernetesIngressNGINX,omitempty" toml:"kubernetesIngressNGINX,omitempty" yaml:"kubernetesIngressNGINX,omitempty" export:"true"`
|
||||||
|
|
||||||
// Deprecated: KubernetesGateway provider is not an experimental feature starting with v3.1. Please remove its usage from the static configuration.
|
// Deprecated: KubernetesGateway provider is not an experimental feature starting with v3.1. Please remove its usage from the static configuration.
|
||||||
KubernetesGateway bool `description:"(Deprecated) Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
|
KubernetesGateway bool `description:"(Deprecated) Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd"
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway"
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress"
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress"
|
||||||
|
ingressnginx "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress-nginx"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kv/consul"
|
"github.com/traefik/traefik/v3/pkg/provider/kv/consul"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kv/etcd"
|
"github.com/traefik/traefik/v3/pkg/provider/kv/etcd"
|
||||||
"github.com/traefik/traefik/v3/pkg/provider/kv/redis"
|
"github.com/traefik/traefik/v3/pkg/provider/kv/redis"
|
||||||
@ -233,19 +234,20 @@ type Providers struct {
|
|||||||
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Swarm *docker.SwarmProvider `description:"Enable Docker Swarm backend with default settings." json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Swarm *docker.SwarmProvider `description:"Enable Docker Swarm backend with default settings." json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
|
||||||
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
|
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
|
||||||
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
KubernetesIngressNGINX *ingressnginx.Provider `description:"Enable Kubernetes Ingress NGINX provider." json:"kubernetesIngressNGINX,omitempty" toml:"kubernetesIngressNGINX,omitempty" yaml:"kubernetesIngressNGINX,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
|
||||||
Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
|
Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
@ -391,6 +393,16 @@ func (c *Configuration) ValidateConfiguration() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Providers != nil && c.Providers.KubernetesIngressNGINX != nil {
|
||||||
|
if c.Experimental == nil || !c.Experimental.KubernetesIngressNGINX {
|
||||||
|
return errors.New("the experimental KubernetesIngressNGINX feature must be enabled to use the KubernetesIngressNGINX provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Providers.KubernetesIngressNGINX.WatchNamespace != "" && c.Providers.KubernetesIngressNGINX.WatchNamespaceSelector != "" {
|
||||||
|
return errors.New("watchNamespace and watchNamespaceSelector options are mutually exclusive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.AccessLog != nil && c.AccessLog.OTLP != nil {
|
if c.AccessLog != nil && c.AccessLog.OTLP != nil {
|
||||||
if c.Experimental == nil || !c.Experimental.OTLPLogs {
|
if c.Experimental == nil || !c.Experimental.OTLPLogs {
|
||||||
return errors.New("the experimental OTLPLogs feature must be enabled to use OTLP access logging")
|
return errors.New("the experimental OTLPLogs feature must be enabled to use OTLP access logging")
|
||||||
|
@ -92,6 +92,10 @@ func NewProviderAggregator(conf static.Providers) *ProviderAggregator {
|
|||||||
p.quietAddProvider(conf.KubernetesIngress)
|
p.quietAddProvider(conf.KubernetesIngress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.KubernetesIngressNGINX != nil {
|
||||||
|
p.quietAddProvider(conf.KubernetesIngressNGINX)
|
||||||
|
}
|
||||||
|
|
||||||
if conf.KubernetesCRD != nil {
|
if conf.KubernetesCRD != nil {
|
||||||
p.quietAddProvider(conf.KubernetesCRD)
|
p.quietAddProvider(conf.KubernetesCRD)
|
||||||
}
|
}
|
||||||
|
115
pkg/provider/kubernetes/ingress-nginx/annotations.go
Normal file
115
pkg/provider/kubernetes/ingress-nginx/annotations.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
netv1 "k8s.io/api/networking/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ingressConfig struct {
|
||||||
|
AuthType *string `annotation:"nginx.ingress.kubernetes.io/auth-type"`
|
||||||
|
AuthSecret *string `annotation:"nginx.ingress.kubernetes.io/auth-secret"`
|
||||||
|
AuthRealm *string `annotation:"nginx.ingress.kubernetes.io/auth-realm"`
|
||||||
|
AuthSecretType *string `annotation:"nginx.ingress.kubernetes.io/auth-secret-type"`
|
||||||
|
|
||||||
|
AuthURL *string `annotation:"nginx.ingress.kubernetes.io/auth-url"`
|
||||||
|
AuthResponseHeaders *string `annotation:"nginx.ingress.kubernetes.io/auth-response-headers"`
|
||||||
|
|
||||||
|
ForceSSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/force-ssl-redirect"`
|
||||||
|
SSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/ssl-redirect"`
|
||||||
|
|
||||||
|
SSLPassthrough *bool `annotation:"nginx.ingress.kubernetes.io/ssl-passthrough"`
|
||||||
|
|
||||||
|
UseRegex *bool `annotation:"nginx.ingress.kubernetes.io/use-regex"`
|
||||||
|
|
||||||
|
Affinity *string `annotation:"nginx.ingress.kubernetes.io/affinity"`
|
||||||
|
SessionCookieName *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-name"`
|
||||||
|
SessionCookieSecure *bool `annotation:"nginx.ingress.kubernetes.io/session-cookie-secure"`
|
||||||
|
SessionCookiePath *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-path"`
|
||||||
|
SessionCookieDomain *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-domain"`
|
||||||
|
SessionCookieSameSite *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-samesite"`
|
||||||
|
SessionCookieMaxAge *int `annotation:"nginx.ingress.kubernetes.io/session-cookie-max-age"`
|
||||||
|
|
||||||
|
ServiceUpstream *bool `annotation:"nginx.ingress.kubernetes.io/service-upstream"`
|
||||||
|
|
||||||
|
BackendProtocol *string `annotation:"nginx.ingress.kubernetes.io/backend-protocol"`
|
||||||
|
|
||||||
|
ProxySSLSecret *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-secret"`
|
||||||
|
ProxySSLVerify *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-verify"`
|
||||||
|
ProxySSLName *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-name"`
|
||||||
|
ProxySSLServerName *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-server-name"`
|
||||||
|
|
||||||
|
EnableCORS *bool `annotation:"nginx.ingress.kubernetes.io/enable-cors"`
|
||||||
|
EnableCORSAllowCredentials *bool `annotation:"nginx.ingress.kubernetes.io/cors-allow-credentials"`
|
||||||
|
CORSExposeHeaders *[]string `annotation:"nginx.ingress.kubernetes.io/cors-expose-headers"`
|
||||||
|
CORSAllowHeaders *[]string `annotation:"nginx.ingress.kubernetes.io/cors-allow-headers"`
|
||||||
|
CORSAllowMethods *[]string `annotation:"nginx.ingress.kubernetes.io/cors-allow-methods"`
|
||||||
|
CORSAllowOrigin *[]string `annotation:"nginx.ingress.kubernetes.io/cors-allow-origin"`
|
||||||
|
CORSMaxAge *int `annotation:"nginx.ingress.kubernetes.io/cors-max-age"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIngressConfig parses the annotations from an Ingress object into an ingressConfig struct.
|
||||||
|
func parseIngressConfig(ing *netv1.Ingress) (ingressConfig, error) {
|
||||||
|
cfg := ingressConfig{}
|
||||||
|
cfgType := reflect.TypeOf(cfg)
|
||||||
|
cfgValue := reflect.ValueOf(&cfg).Elem()
|
||||||
|
|
||||||
|
for i := range cfgType.NumField() {
|
||||||
|
field := cfgType.Field(i)
|
||||||
|
annotation := field.Tag.Get("annotation")
|
||||||
|
if annotation == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := ing.GetAnnotations()[annotation]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.Type.Elem().Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
cfgValue.Field(i).Set(reflect.ValueOf(&val))
|
||||||
|
case reflect.Bool:
|
||||||
|
parsed, err := strconv.ParseBool(val)
|
||||||
|
if err == nil {
|
||||||
|
cfgValue.Field(i).Set(reflect.ValueOf(&parsed))
|
||||||
|
}
|
||||||
|
case reflect.Int:
|
||||||
|
parsed, err := strconv.Atoi(val)
|
||||||
|
if err == nil {
|
||||||
|
cfgValue.Field(i).Set(reflect.ValueOf(&parsed))
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if field.Type.Elem().Elem().Kind() == reflect.String {
|
||||||
|
// Handle slice of strings
|
||||||
|
var slice []string
|
||||||
|
elements := strings.Split(val, ",")
|
||||||
|
for _, elt := range elements {
|
||||||
|
slice = append(slice, strings.TrimSpace(elt))
|
||||||
|
}
|
||||||
|
cfgValue.Field(i).Set(reflect.ValueOf(&slice))
|
||||||
|
} else {
|
||||||
|
return cfg, errors.New("unsupported slice type in annotations")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return cfg, errors.New("unsupported kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBackendProtocol parses the backend protocol annotation and returns the corresponding protocol string.
|
||||||
|
func parseBackendProtocol(bp string) string {
|
||||||
|
switch strings.ToUpper(bp) {
|
||||||
|
case "HTTPS", "GRPCS":
|
||||||
|
return "https"
|
||||||
|
case "GRPC":
|
||||||
|
return "h2c"
|
||||||
|
default:
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
}
|
76
pkg/provider/kubernetes/ingress-nginx/annotations_test.go
Normal file
76
pkg/provider/kubernetes/ingress-nginx/annotations_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
netv1 "k8s.io/api/networking/v1"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseIngressConfig(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
annotations map[string]string
|
||||||
|
expected ingressConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "all fields set",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-passthrough": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/affinity": "cookie",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-name": "mycookie",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-secure": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-path": "/foo",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-domain": "example.com",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-samesite": "Strict",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-max-age": "3600",
|
||||||
|
"nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
|
||||||
|
"nginx.ingress.kubernetes.io/cors-expose-headers": "foo, bar",
|
||||||
|
},
|
||||||
|
expected: ingressConfig{
|
||||||
|
SSLPassthrough: ptr.To(true),
|
||||||
|
Affinity: ptr.To("cookie"),
|
||||||
|
SessionCookieName: ptr.To("mycookie"),
|
||||||
|
SessionCookieSecure: ptr.To(true),
|
||||||
|
SessionCookiePath: ptr.To("/foo"),
|
||||||
|
SessionCookieDomain: ptr.To("example.com"),
|
||||||
|
SessionCookieSameSite: ptr.To("Strict"),
|
||||||
|
SessionCookieMaxAge: ptr.To(3600),
|
||||||
|
BackendProtocol: ptr.To("HTTPS"),
|
||||||
|
CORSExposeHeaders: ptr.To([]string{"foo", "bar"}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing fields",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-passthrough": "false",
|
||||||
|
},
|
||||||
|
expected: ingressConfig{
|
||||||
|
SSLPassthrough: ptr.To(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid bool and int",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/ssl-passthrough": "notabool",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-max-age (in seconds)": "notanint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var ing netv1.Ingress
|
||||||
|
ing.SetAnnotations(test.annotations)
|
||||||
|
|
||||||
|
cfg, err := parseIngressConfig(&ing)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
384
pkg/provider/kubernetes/ingress-nginx/client.go
Normal file
384
pkg/provider/kubernetes/ingress-nginx/client.go
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
|
traefikversion "github.com/traefik/traefik/v3/pkg/version"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
discoveryv1 "k8s.io/api/discovery/v1"
|
||||||
|
netv1 "k8s.io/api/networking/v1"
|
||||||
|
kerror "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
kinformers "k8s.io/client-go/informers"
|
||||||
|
kclientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
resyncPeriod = 10 * time.Minute
|
||||||
|
defaultTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientWrapper struct {
|
||||||
|
clientset kclientset.Interface
|
||||||
|
clusterScopeFactory kinformers.SharedInformerFactory
|
||||||
|
factoriesKube map[string]kinformers.SharedInformerFactory
|
||||||
|
factoriesSecret map[string]kinformers.SharedInformerFactory
|
||||||
|
factoriesIngress map[string]kinformers.SharedInformerFactory
|
||||||
|
isNamespaceAll bool
|
||||||
|
watchedNamespaces []string
|
||||||
|
|
||||||
|
ignoreIngressClasses bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newInClusterClient returns a new Provider client that is expected to run
|
||||||
|
// inside the cluster.
|
||||||
|
func newInClusterClient(endpoint string) (*clientWrapper, error) {
|
||||||
|
config, err := rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create in-cluster configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint != "" {
|
||||||
|
config.Host = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
return createClientFromConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExternalClusterClientFromFile(file string) (*clientWrapper, error) {
|
||||||
|
configFromFlags, err := clientcmd.BuildConfigFromFlags("", file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return createClientFromConfig(configFromFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newExternalClusterClient returns a new Provider client that may run outside
|
||||||
|
// of the cluster.
|
||||||
|
// The endpoint parameter must not be empty.
|
||||||
|
func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent) (*clientWrapper, error) {
|
||||||
|
if endpoint == "" {
|
||||||
|
return nil, errors.New("endpoint missing for external cluster client")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenData, err := token.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &rest.Config{
|
||||||
|
Host: endpoint,
|
||||||
|
BearerToken: string(tokenData),
|
||||||
|
}
|
||||||
|
|
||||||
|
if caFilePath != "" {
|
||||||
|
caData, err := os.ReadFile(caFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read CA file %s: %w", caFilePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.TLSClientConfig = rest.TLSClientConfig{CAData: caData}
|
||||||
|
}
|
||||||
|
return createClientFromConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClientFromConfig(c *rest.Config) (*clientWrapper, error) {
|
||||||
|
c.UserAgent = fmt.Sprintf(
|
||||||
|
"%s/%s (%s/%s) kubernetes/ingress",
|
||||||
|
filepath.Base(os.Args[0]),
|
||||||
|
traefikversion.Version,
|
||||||
|
runtime.GOOS,
|
||||||
|
runtime.GOARCH,
|
||||||
|
)
|
||||||
|
|
||||||
|
clientset, err := kclientset.NewForConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newClient(clientset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(clientSet kclientset.Interface) *clientWrapper {
|
||||||
|
return &clientWrapper{
|
||||||
|
clientset: clientSet,
|
||||||
|
factoriesSecret: make(map[string]kinformers.SharedInformerFactory),
|
||||||
|
factoriesIngress: make(map[string]kinformers.SharedInformerFactory),
|
||||||
|
factoriesKube: make(map[string]kinformers.SharedInformerFactory),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||||
|
func (c *clientWrapper) WatchAll(ctx context.Context, namespace, namespaceSelector string) (<-chan interface{}, error) {
|
||||||
|
stopCh := ctx.Done()
|
||||||
|
eventCh := make(chan interface{}, 1)
|
||||||
|
eventHandler := &k8s.ResourceEventHandler{Ev: eventCh}
|
||||||
|
|
||||||
|
c.ignoreIngressClasses = false
|
||||||
|
_, err := c.clientset.NetworkingV1().IngressClasses().List(ctx, metav1.ListOptions{Limit: 1})
|
||||||
|
if err != nil {
|
||||||
|
if !kerror.IsNotFound(err) {
|
||||||
|
if kerror.IsForbidden(err) {
|
||||||
|
c.ignoreIngressClasses = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if namespaceSelector != "" {
|
||||||
|
ns, err := c.clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: namespaceSelector})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing namespaces: %w", err)
|
||||||
|
}
|
||||||
|
for _, item := range ns.Items {
|
||||||
|
c.watchedNamespaces = append(c.watchedNamespaces, item.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.isNamespaceAll = namespace == metav1.NamespaceAll
|
||||||
|
c.watchedNamespaces = []string{namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
notOwnedByHelm := func(opts *metav1.ListOptions) {
|
||||||
|
opts.LabelSelector = "owner!=helm"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range c.watchedNamespaces {
|
||||||
|
factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns))
|
||||||
|
|
||||||
|
_, err := factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.factoriesIngress[ns] = factoryIngress
|
||||||
|
|
||||||
|
factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns))
|
||||||
|
_, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.factoriesKube[ns] = factoryKube
|
||||||
|
|
||||||
|
factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm))
|
||||||
|
_, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.factoriesSecret[ns] = factorySecret
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range c.watchedNamespaces {
|
||||||
|
c.factoriesIngress[ns].Start(stopCh)
|
||||||
|
c.factoriesKube[ns].Start(stopCh)
|
||||||
|
c.factoriesSecret[ns].Start(stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range c.watchedNamespaces {
|
||||||
|
for t, ok := range c.factoriesIngress[ns].WaitForCacheSync(stopCh) {
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for t, ok := range c.factoriesKube[ns].WaitForCacheSync(stopCh) {
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for t, ok := range c.factoriesSecret[ns].WaitForCacheSync(stopCh) {
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod)
|
||||||
|
|
||||||
|
if !c.ignoreIngressClasses {
|
||||||
|
_, err = c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.clusterScopeFactory.Start(stopCh)
|
||||||
|
|
||||||
|
for t, ok := range c.clusterScopeFactory.WaitForCacheSync(stopCh) {
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientWrapper) ListIngressClasses() ([]*netv1.IngressClass, error) {
|
||||||
|
if c.ignoreIngressClasses {
|
||||||
|
return []*netv1.IngressClass{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.clusterScopeFactory.Networking().V1().IngressClasses().Lister().List(labels.Everything())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIngresses returns all Ingresses for observed namespaces in the cluster.
|
||||||
|
func (c *clientWrapper) ListIngresses() []*netv1.Ingress {
|
||||||
|
var results []*netv1.Ingress
|
||||||
|
|
||||||
|
for ns, factory := range c.factoriesIngress {
|
||||||
|
// networking
|
||||||
|
listNew, err := factory.Networking().V1().Ingresses().Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Failed to list ingresses in namespace %s", ns)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, listNew...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIngressStatus updates an Ingress with a provided status.
|
||||||
|
func (c *clientWrapper) UpdateIngressStatus(src *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error {
|
||||||
|
if !c.isWatchedNamespace(src.Namespace) {
|
||||||
|
return fmt.Errorf("failed to get ingress %s/%s: namespace is not within watched namespaces", src.Namespace, src.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.With().Str("namespace", ing.Namespace).Str("ingress", ing.Name).Logger()
|
||||||
|
|
||||||
|
if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) {
|
||||||
|
logger.Debug().Msg("Skipping ingress status update")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ingCopy := ing.DeepCopy()
|
||||||
|
ingCopy.Status = netv1.IngressStatus{LoadBalancer: netv1.IngressLoadBalancerStatus{Ingress: ingStatus}}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = c.clientset.NetworkingV1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msg("Updated ingress status")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetService returns the named service from the given namespace.
|
||||||
|
func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, error) {
|
||||||
|
if !c.isWatchedNamespace(namespace) {
|
||||||
|
return nil, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace.
|
||||||
|
func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) {
|
||||||
|
if !c.isWatchedNamespace(namespace) {
|
||||||
|
return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create service label selector requirement: %w", err)
|
||||||
|
}
|
||||||
|
serviceSelector := labels.NewSelector()
|
||||||
|
serviceSelector = serviceSelector.Add(*serviceLabelRequirement)
|
||||||
|
|
||||||
|
return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecret returns the named secret from the given namespace.
|
||||||
|
func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, error) {
|
||||||
|
if !c.isWatchedNamespace(namespace) {
|
||||||
|
return nil, fmt.Errorf("failed to get secret %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.factoriesSecret[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupNamespace returns the lookup namespace key for the given namespace.
|
||||||
|
// When listening on all namespaces, it returns the client-go identifier ("")
|
||||||
|
// for all-namespaces. Otherwise, it returns the given namespace.
|
||||||
|
// The distinction is necessary because we index all informers on the special
|
||||||
|
// identifier iff all-namespaces are requested but receive specific namespace
|
||||||
|
// identifiers from the Kubernetes API, so we have to bridge this gap.
|
||||||
|
func (c *clientWrapper) lookupNamespace(ns string) string {
|
||||||
|
if c.isNamespaceAll {
|
||||||
|
return metav1.NamespaceAll
|
||||||
|
}
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWatchedNamespace checks to ensure that the namespace is being watched before we request
|
||||||
|
// it to ensure we don't panic by requesting an out-of-watch object.
|
||||||
|
func (c *clientWrapper) isWatchedNamespace(ns string) bool {
|
||||||
|
if c.isNamespaceAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(c.watchedNamespaces, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLoadBalancerIngressEquals returns true if the given slices are equal, false otherwise.
|
||||||
|
func isLoadBalancerIngressEquals(aSlice, bSlice []netv1.IngressLoadBalancerIngress) bool {
|
||||||
|
if len(aSlice) != len(bSlice) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
aMap := make(map[string]struct{})
|
||||||
|
for _, aIngress := range aSlice {
|
||||||
|
aMap[aIngress.Hostname+aIngress.IP] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bIngress := range bSlice {
|
||||||
|
if _, exists := aMap[bIngress.Hostname+bIngress.IP]; !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterIngressClass return a slice containing IngressClass matching either the annotation name or the controller.
|
||||||
|
func filterIngressClass(ingressClasses []*netv1.IngressClass, ingressClassByName bool, ingressClass, controllerClass string) []*netv1.IngressClass {
|
||||||
|
var filteredIngressClasses []*netv1.IngressClass
|
||||||
|
for _, ic := range ingressClasses {
|
||||||
|
if ingressClassByName && ic.Name == ingressClass {
|
||||||
|
return append(filteredIngressClasses, ic)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ic.Spec.Controller == controllerClass {
|
||||||
|
filteredIngressClasses = append(filteredIngressClasses, ic)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredIngressClasses
|
||||||
|
}
|
270
pkg/provider/kubernetes/ingress-nginx/client_test.go
Normal file
270
pkg/provider/kubernetes/ingress-nginx/client_test.go
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
discoveryv1 "k8s.io/api/discovery/v1"
|
||||||
|
netv1 "k8s.io/api/networking/v1"
|
||||||
|
kerror "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
kversion "k8s.io/apimachinery/pkg/version"
|
||||||
|
discoveryfake "k8s.io/client-go/discovery/fake"
|
||||||
|
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsLoadBalancerIngressEquals(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
aSlice []netv1.IngressLoadBalancerIngress
|
||||||
|
bSlice []netv1.IngressLoadBalancerIngress
|
||||||
|
expectedEqual bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "both slices are empty",
|
||||||
|
expectedEqual: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "not the same length",
|
||||||
|
bSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
},
|
||||||
|
expectedEqual: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "same ordered content",
|
||||||
|
aSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
},
|
||||||
|
bSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
},
|
||||||
|
expectedEqual: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "same unordered content",
|
||||||
|
aSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
{IP: "192.168.1.2", Hostname: "traefik2"},
|
||||||
|
},
|
||||||
|
bSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.2", Hostname: "traefik2"},
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
},
|
||||||
|
expectedEqual: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "different ordered content",
|
||||||
|
aSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
{IP: "192.168.1.2", Hostname: "traefik2"},
|
||||||
|
},
|
||||||
|
bSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
{IP: "192.168.1.2", Hostname: "traefik"},
|
||||||
|
},
|
||||||
|
expectedEqual: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "different unordered content",
|
||||||
|
aSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
{IP: "192.168.1.2", Hostname: "traefik2"},
|
||||||
|
},
|
||||||
|
bSlice: []netv1.IngressLoadBalancerIngress{
|
||||||
|
{IP: "192.168.1.2", Hostname: "traefik3"},
|
||||||
|
{IP: "192.168.1.1", Hostname: "traefik"},
|
||||||
|
},
|
||||||
|
expectedEqual: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
gotEqual := isLoadBalancerIngressEquals(test.aSlice, test.bSlice)
|
||||||
|
assert.Equal(t, test.expectedEqual, gotEqual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
|
||||||
|
secret := &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "secret",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
helmSecret := &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "helm-secret",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"owner": "helm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient := kubefake.NewClientset(helmSecret, secret)
|
||||||
|
|
||||||
|
discovery, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery)
|
||||||
|
discovery.FakedServerVersion = &kversion.Info{
|
||||||
|
GitVersion: "v1.19",
|
||||||
|
}
|
||||||
|
|
||||||
|
client := newClient(kubeClient)
|
||||||
|
|
||||||
|
eventCh, err := client.WatchAll(t.Context(), "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-eventCh:
|
||||||
|
secret, ok := event.(*corev1.Secret)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.NotEqual(t, "helm-secret", secret.Name)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
assert.Fail(t, "expected to receive event for secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-eventCh:
|
||||||
|
assert.Fail(t, "received more than one event")
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.GetSecret("default", "secret")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.GetSecret("default", "helm-secret")
|
||||||
|
assert.True(t, kerror.IsNotFound(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) {
|
||||||
|
emptyEndpointSlice := &discoveryv1.EndpointSlice{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "empty-endpointslice",
|
||||||
|
Namespace: "test",
|
||||||
|
ResourceVersion: "1244",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"test-annotation": "_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
samplePortName := "testing"
|
||||||
|
samplePortNumber := int32(1337)
|
||||||
|
samplePortProtocol := corev1.ProtocolTCP
|
||||||
|
sampleAddressReady := true
|
||||||
|
filledEndpointSlice := &discoveryv1.EndpointSlice{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "filled-endpointslice",
|
||||||
|
Namespace: "test",
|
||||||
|
ResourceVersion: "1234",
|
||||||
|
},
|
||||||
|
AddressType: discoveryv1.AddressTypeIPv4,
|
||||||
|
Endpoints: []discoveryv1.Endpoint{{
|
||||||
|
Addresses: []string{"10.13.37.1"},
|
||||||
|
Conditions: discoveryv1.EndpointConditions{
|
||||||
|
Ready: &sampleAddressReady,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Ports: []discoveryv1.EndpointPort{{
|
||||||
|
Name: &samplePortName,
|
||||||
|
Port: &samplePortNumber,
|
||||||
|
Protocol: &samplePortProtocol,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient := kubefake.NewClientset(emptyEndpointSlice, filledEndpointSlice)
|
||||||
|
|
||||||
|
discovery, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery)
|
||||||
|
discovery.FakedServerVersion = &kversion.Info{
|
||||||
|
GitVersion: "v1.19",
|
||||||
|
}
|
||||||
|
|
||||||
|
client := newClient(kubeClient)
|
||||||
|
|
||||||
|
eventCh, err := client.WatchAll(t.Context(), "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-eventCh:
|
||||||
|
ep, ok := event.(*discoveryv1.EndpointSlice)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.True(t, ep.Name == "empty-endpointslice" || ep.Name == "filled-endpointslice")
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
assert.Fail(t, "expected to receive event for endpointslices")
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "empty-endpointslice", metav1.GetOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Update endpoint annotation and resource version (apparently not done by fake client itself)
|
||||||
|
// to show an update that should not trigger an update event on our eventCh.
|
||||||
|
// This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election.
|
||||||
|
emptyEndpointSlice.Annotations["test-annotation"] = "___"
|
||||||
|
emptyEndpointSlice.ResourceVersion = "1245"
|
||||||
|
_, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), emptyEndpointSlice, metav1.UpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-eventCh:
|
||||||
|
ep, ok := event.(*discoveryv1.EndpointSlice)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.Fail(t, "didn't expect to receive event for empty endpointslice update", ep.Name)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "filled-endpointslice", metav1.GetOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2"
|
||||||
|
filledEndpointSlice.ResourceVersion = "1235"
|
||||||
|
_, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-eventCh:
|
||||||
|
ep, ok := event.(*discoveryv1.EndpointSlice)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.Equal(t, "filled-endpointslice", ep.Name)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
assert.Fail(t, "expected to receive event for filled endpointslice")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-eventCh:
|
||||||
|
assert.Fail(t, "received more than one event")
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
newPortNumber := int32(42)
|
||||||
|
filledEndpointSlice.Ports[0].Port = &newPortNumber
|
||||||
|
filledEndpointSlice.ResourceVersion = "1236"
|
||||||
|
_, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-eventCh:
|
||||||
|
ep, ok := event.(*discoveryv1.EndpointSlice)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.Equal(t, "filled-endpointslice", ep.Name)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
assert.Fail(t, "expected to receive event for filled endpointslice")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-eventCh:
|
||||||
|
assert.Fail(t, "received more than one event")
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
}
|
69
pkg/provider/kubernetes/ingress-nginx/convert.go
Normal file
69
pkg/provider/kubernetes/ingress-nginx/convert.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
netv1 "k8s.io/api/networking/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type marshaler interface {
|
||||||
|
Marshal() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unmarshaler interface {
|
||||||
|
Unmarshal(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadBalancerIngress interface {
|
||||||
|
corev1.LoadBalancerIngress | netv1.IngressLoadBalancerIngress
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertSlice converts slice of LoadBalancerIngress to slice of LoadBalancerIngress.
|
||||||
|
// O (Bar), I (Foo) => []Bar.
|
||||||
|
func convertSlice[O LoadBalancerIngress, I LoadBalancerIngress](loadBalancerIngresses []I) ([]O, error) {
|
||||||
|
var results []O
|
||||||
|
|
||||||
|
for _, loadBalancerIngress := range loadBalancerIngresses {
|
||||||
|
mar, ok := any(&loadBalancerIngress).(marshaler)
|
||||||
|
if !ok {
|
||||||
|
// All the pointer of types related to the interface LoadBalancerIngress are compatible with the interface marshaler.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
um, err := convert[O](mar)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := any(*um).(O)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert must only be used with unmarshaler and marshaler compatible types.
|
||||||
|
func convert[T any](input marshaler) (*T, error) {
|
||||||
|
data, err := input.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var output T
|
||||||
|
um, ok := any(&output).(unmarshaler)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("the output type doesn't implement unmarshaler interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = um.Unmarshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &output, nil
|
||||||
|
}
|
77
pkg/provider/kubernetes/ingress-nginx/convert_test.go
Normal file
77
pkg/provider/kubernetes/ingress-nginx/convert_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
netv1 "k8s.io/api/networking/v1"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_convertSlice_corev1_to_networkingv1(t *testing.T) {
|
||||||
|
g := []corev1.LoadBalancerIngress{
|
||||||
|
{
|
||||||
|
IP: "132456",
|
||||||
|
Hostname: "foo",
|
||||||
|
Ports: []corev1.PortStatus{
|
||||||
|
{
|
||||||
|
Port: 123,
|
||||||
|
Protocol: "https",
|
||||||
|
Error: ptr.To("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := convertSlice[netv1.IngressLoadBalancerIngress](g)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := []netv1.IngressLoadBalancerIngress{
|
||||||
|
{
|
||||||
|
IP: "132456",
|
||||||
|
Hostname: "foo",
|
||||||
|
Ports: []netv1.IngressPortStatus{
|
||||||
|
{
|
||||||
|
Port: 123,
|
||||||
|
Protocol: "https",
|
||||||
|
Error: ptr.To("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_convert(t *testing.T) {
|
||||||
|
g := &corev1.LoadBalancerIngress{
|
||||||
|
IP: "132456",
|
||||||
|
Hostname: "foo",
|
||||||
|
Ports: []corev1.PortStatus{
|
||||||
|
{
|
||||||
|
Port: 123,
|
||||||
|
Protocol: "https",
|
||||||
|
Error: ptr.To("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := convert[netv1.IngressLoadBalancerIngress](g)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := &netv1.IngressLoadBalancerIngress{
|
||||||
|
IP: "132456",
|
||||||
|
Hostname: "foo",
|
||||||
|
Ports: []netv1.IngressPortStatus{
|
||||||
|
{
|
||||||
|
Port: 123,
|
||||||
|
Protocol: "https",
|
||||||
|
Error: ptr.To("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: IngressClass
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
spec:
|
||||||
|
controller: k8s.io/ingress-nginx
|
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-basicauth
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
# Configuration basic authentication for the Ingress
|
||||||
|
nginx.ingress.kubernetes.io/auth-type: "basic"
|
||||||
|
nginx.ingress.kubernetes.io/auth-secret-type: "auth-file"
|
||||||
|
nginx.ingress.kubernetes.io/auth-secret: "default/basic-auth"
|
||||||
|
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: whoami.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /basicauth
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: basic-auth
|
||||||
|
namespace: default
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
# user:password
|
||||||
|
auth: dXNlcjp7U0hBfVc2cGg1TW01UHo4R2dpVUxiUGd6RzM3bWo5Zz0=
|
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-forwardauth
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/auth-url: "http://whoami.default.svc/"
|
||||||
|
nginx.ingress.kubernetes.io/auth-method: "GET"
|
||||||
|
nginx.ingress.kubernetes.io/auth-response-headers: "X-Foo"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: whoami.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /forwardauth
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-ssl-redirect
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: sslredirect.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- sslredirect.localhost
|
||||||
|
secretName: whoami-tls
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-without-ssl-redirect
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: withoutsslredirect.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- withoutsslredirect.localhost
|
||||||
|
secretName: whoami-tls
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-force-ssl-redirect
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: forcesslredirect.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-ssl-passthrough
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: passthrough.whoami.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami-tls
|
||||||
|
port:
|
||||||
|
number: 443
|
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-default-backend
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
defaultBackend:
|
||||||
|
service:
|
||||||
|
name: whoami-default
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
@ -0,0 +1,108 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-default-backend2
|
||||||
|
namespace: default
|
||||||
|
# annotations:
|
||||||
|
# nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
|
||||||
|
# annotations:
|
||||||
|
## Configuration basic authentication for the Ingress
|
||||||
|
# nginx.ingress.kubernetes.io/auth-type: "basic"
|
||||||
|
# nginx.ingress.kubernetes.io/auth-secret-type: "auth-file"
|
||||||
|
# nginx.ingress.kubernetes.io/auth-secret: "default/basic-auth"
|
||||||
|
# nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
defaultBackend:
|
||||||
|
service:
|
||||||
|
name: whoami-default2
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- host: dd.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 443
|
||||||
|
#
|
||||||
|
# tls:
|
||||||
|
# - hosts:
|
||||||
|
# - dd.localhost
|
||||||
|
# secretName: whoami-tls
|
||||||
|
#
|
||||||
|
#---
|
||||||
|
#kind: Secret
|
||||||
|
#apiVersion: v1
|
||||||
|
#metadata:
|
||||||
|
# name: whoami-tls
|
||||||
|
# namespace: default
|
||||||
|
#
|
||||||
|
#type: opaque
|
||||||
|
#stringData:
|
||||||
|
# tls.crt: |
|
||||||
|
# -----BEGIN CERTIFICATE-----
|
||||||
|
# MIIEXjCCAsagAwIBAgIQAJmtU2qHBlD9D2HZFZLMeDANBgkqhkiG9w0BAQsFADCB
|
||||||
|
# jzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTIwMAYDVQQLDClyb21h
|
||||||
|
# aW5AY29udGFpbm91cy5ob21lIChSb21haW4gVHJpYm90dMOpKTE5MDcGA1UEAwww
|
||||||
|
# bWtjZXJ0IHJvbWFpbkBjb250YWlub3VzLmhvbWUgKFJvbWFpbiBUcmlib3R0w6kp
|
||||||
|
# MB4XDTI1MDYxMDE1NDE0NFoXDTI3MDkxMDE1NDE0NFowXzEnMCUGA1UEChMebWtj
|
||||||
|
# ZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTQwMgYDVQQLDCtyb21haW5ATWFj
|
||||||
|
# Qm9vay1Qcm8ubG9jYWwgKFJvbWFpbiBUcmlib3R0w6kpMIIBIjANBgkqhkiG9w0B
|
||||||
|
# AQEFAAOCAQ8AMIIBCgKCAQEAq3dajz+RgY+VUXvKKtHFFVd+0URcpDRgN+SJOxP/
|
||||||
|
# 1uZG2U57DMvTiVy6zfpYo7QPzyEAUwbRTMMgxZV5oy1JPkGzV5kc08GUT3Lh1Azf
|
||||||
|
# LVPX/K1nA+k7p9+kuMsfkHVABMawRpnWo215T9pjGaTKERA2EaNvrSdq73k6raVn
|
||||||
|
# DnnmvUgWGPvxTetaLu0AVQscGyrTfQNMB8BwC+JEQJKocenJ0ve5l9/yv9543P2G
|
||||||
|
# 6UcOv71lDOBNPyltrc4sXfGC2vB1APbp80BVfkZDiF+8Gr8wGJrkd75Esp/xetFV
|
||||||
|
# yZ6NKO9ZsGZ2E14/qxfvASHGNFNQJafqhnuGbmky8AeaawIDAQABo2UwYzAOBgNV
|
||||||
|
# HQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUjIHl
|
||||||
|
# 1gcu+iVVHCicC14yHQiRojgwGwYDVR0RBBQwEoIQd2hvYW1pLmxvY2FsaG9zdDAN
|
||||||
|
# BgkqhkiG9w0BAQsFAAOCAYEAo/f0ADJwnkOakCHcYCNSqRY/VzRIQSQK3wfDq3bD
|
||||||
|
# 8EDxGrGPYHOIL+u/Up4RO2/9vLEnFpWb30A8z/qZTKKD+rMuU3qTcCJ2tsB3DAIV
|
||||||
|
# T+b2GmJYjURf1gqe/NNXnzZqgkoP+bHx6iNvDr1kmc3pZshayz+FxzNmjgpbKl2G
|
||||||
|
# SgfFLnJDm7hwTC9JFoPyzb586Q0OGQKCJpDMy6pi1MAQl2RWiKyrgo1mhYnSxQmI
|
||||||
|
# qbJbxYlegRRQQPD6YEJcL5lwILVW3TXcGrK+zuMD+xWznDTBg2BxbF2umG8jmXPH
|
||||||
|
# 04gRfjlMNLEYSrNEU8EOa/lXebcxnlz6meFOgfYKmSHxL+kwjTUuppDV/qP9U+VS
|
||||||
|
# /ozJ85VS8iEx1obqZGgqgwcBKMRYzuRnW1XEScGUOK9/cs9mGoXG9uafKb7ekFQc
|
||||||
|
# wU0j0FoUVzc50WWEjCGFU/dS/2HXUXU/Rcf+uULC10ORplwrB5XdXZYxowh7T//U
|
||||||
|
# yh86E4+M0LHyZH0vUwDoBk/1
|
||||||
|
# -----END CERTIFICATE-----
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# tls.key: |
|
||||||
|
# -----BEGIN PRIVATE KEY-----
|
||||||
|
# MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrd1qPP5GBj5VR
|
||||||
|
# e8oq0cUVV37RRFykNGA35Ik7E//W5kbZTnsMy9OJXLrN+lijtA/PIQBTBtFMwyDF
|
||||||
|
# lXmjLUk+QbNXmRzTwZRPcuHUDN8tU9f8rWcD6Tun36S4yx+QdUAExrBGmdajbXlP
|
||||||
|
# 2mMZpMoREDYRo2+tJ2rveTqtpWcOeea9SBYY+/FN61ou7QBVCxwbKtN9A0wHwHAL
|
||||||
|
# 4kRAkqhx6cnS97mX3/K/3njc/YbpRw6/vWUM4E0/KW2tzixd8YLa8HUA9unzQFV+
|
||||||
|
# RkOIX7wavzAYmuR3vkSyn/F60VXJno0o71mwZnYTXj+rF+8BIcY0U1Alp+qGe4Zu
|
||||||
|
# aTLwB5prAgMBAAECggEAVNpLxnf+2c7kZd6MvYPxtA4IhCcAcYI5228NOl87TG3I
|
||||||
|
# weFEo6B6no91IlmxY9HHwQjj0DKfgQ1POnguKcJPbK+2wLLUwTYa3vZLK1TzXMsR
|
||||||
|
# J8noINda3kiei5R5mlNryvFIaqfWwCl8zzeTsy0JkkgjebcXnOjU0o17rFMeHNsH
|
||||||
|
# A3iFWWnHtJkn2OaVtOOgsyjJ9oAnGX0AE4cVp7ZZTerpaYTXzkCphbwRi00IEbCk
|
||||||
|
# 1bn7gPcBQRoxs12GJUUuy/sopQRA51PE//CnV2pkGuDFWBhFBBKYdsHaTUwmTb5P
|
||||||
|
# l6S5CuCtw44NkTPetTe2sn9DpOIlR7PmojQndmKkgQKBgQDJ6/RDueJCBxSYS6bh
|
||||||
|
# 7dTPRphJvntoJHs9Q/NNjKdQhxv0vIIdtRk88Q2qhjjlCzHb1RtD5Jsl+D+TxOdG
|
||||||
|
# wR1/E8+hdbRKv+WACywa38aBPuZSEj89bnyPyQfzs5TtzD5JsdUHT4l5Eudth6Gv
|
||||||
|
# w14dFKria8WiEd7X2GodnlZX/wKBgQDZY1QBNjAHsi7QJJSvbPKwK8RygvdNJEem
|
||||||
|
# FYxhjtHzOfUttjyDXDSGheY3/VzKi2rGgVAHLi+qbvwkURn4qT3xtV5Lpi+BLWHP
|
||||||
|
# Gwepisd9P5TrN0DGQojWjzYatN9MYRzX0JynIB+alabN2bG7kfPPsHikAA7pRxLH
|
||||||
|
# 7EwMBDGdlQKBgBqd9uoCk9e+VTGqL0py7m2QUbzO1jepL3GpBmZ/lwKffMjrHH/M
|
||||||
|
# ApKs9+81mERhEGZ5FgoCFY2Qxti0yQPjqv64XtNaz7RWzWrujhbQzrr0zqmc7Cct
|
||||||
|
# 7E+L4Xd3gbdDCCbwwTMgge+q1UTz7xVbPIm60rfcGwY9MtHjHkHfQGSDAoGBAIA/
|
||||||
|
# CAT6+dTgepuSqSDg7j+eYnOH7etVlutVVQ8M2bFbJNiF5Sc900L1ZX7seryHCUP4
|
||||||
|
# b8T8q2Qpu5iVO/QlrASXkfyhGu9jXYt4D8omtE+gnfMyEoWkJOQncqzIvd9qf0CW
|
||||||
|
# soQqAFsLJG/WmPLmRObm3hUqb6GRq3PEZIzGQJsNAoGBAJEN0ZkrIkNK+Jjd1oNB
|
||||||
|
# AnwgLA0qyAHqJxPig45Nudhb6Jw4ub/hKG9bCrLpcBM57Lue535e2HtQ5Ed22Pim
|
||||||
|
# 0m7bQkvrIQYjflW99RsfkiH5qJsiTy9O92iKgGtJAJ80vTkIggAbsnzOHlZvR0Fr
|
||||||
|
# +GhYvMt0TxpugicUqguSSUZp
|
||||||
|
# -----END PRIVATE KEY-----
|
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-sticky
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/affinity: cookie
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-name: foobar
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-secure: "true"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-path: "/foobar"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-domain: "foo.localhost"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-samesite: "None"
|
||||||
|
nginx.ingress.kubernetes.io/session-cookie-max-age: "42"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: sticky.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-proxy-ssl
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" # HTTP, HTTPS, AUTO_HTTP, GRPC, GRPCS and FCGI
|
||||||
|
nginx.ingress.kubernetes.io/proxy-ssl-secret: "default/ingress-with-proxy-ssl"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-ssl-verify: "on"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-ssl-verify-depth: "1"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-ssl-server-name: "whoami.localhost"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-ssl-name: "whoami.localhost"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: proxy-ssl.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami-tls
|
||||||
|
port:
|
||||||
|
number: 443
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: default
|
||||||
|
name: ingress-with-proxy-ssl
|
||||||
|
|
||||||
|
data:
|
||||||
|
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
|
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-cors
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
|
||||||
|
nginx.ingress.kubernetes.io/cors-expose-headers: "X-Forwarded-For, X-Forwarded-Host"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-headers: "X-Foo"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
|
||||||
|
nginx.ingress.kubernetes.io/cors-max-age: "42"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: cors.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: ingress-with-service-upstream
|
||||||
|
namespace: default
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/service-upstream: "true"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: service-upstream.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
@ -0,0 +1,9 @@
|
|||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: default
|
||||||
|
name: whoami-tls
|
||||||
|
|
||||||
|
data:
|
||||||
|
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
|
||||||
|
tls.key: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
|
80
pkg/provider/kubernetes/ingress-nginx/fixtures/services.yml
Normal file
80
pkg/provider/kubernetes/ingress-nginx/fixtures/services.yml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.10.10.1
|
||||||
|
ports:
|
||||||
|
- name: web2
|
||||||
|
protocol: TCP
|
||||||
|
port: 8000
|
||||||
|
targetPort: web2
|
||||||
|
- name: web
|
||||||
|
protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: web
|
||||||
|
selector:
|
||||||
|
app: whoami
|
||||||
|
task: whoami
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: EndpointSlice
|
||||||
|
apiVersion: discovery.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
kubernetes.io/service-name: whoami
|
||||||
|
|
||||||
|
addressType: IPv4
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
port: 80
|
||||||
|
- name: web2
|
||||||
|
port: 8000
|
||||||
|
endpoints:
|
||||||
|
- addresses:
|
||||||
|
- 10.10.0.1
|
||||||
|
- 10.10.0.2
|
||||||
|
conditions:
|
||||||
|
ready: true
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: whoami-tls
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: websecure
|
||||||
|
protocol: TCP
|
||||||
|
appProtocol: https
|
||||||
|
port: 443
|
||||||
|
targetPort: websecure
|
||||||
|
selector:
|
||||||
|
app: whoami-tls
|
||||||
|
task: whoami
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: EndpointSlice
|
||||||
|
apiVersion: discovery.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: whoami-tls
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
kubernetes.io/service-name: whoami-tls
|
||||||
|
|
||||||
|
addressType: IPv4
|
||||||
|
ports:
|
||||||
|
- name: websecure
|
||||||
|
port: 8443
|
||||||
|
endpoints:
|
||||||
|
- addresses:
|
||||||
|
- 10.10.0.5
|
||||||
|
- 10.10.0.6
|
||||||
|
conditions:
|
||||||
|
ready: true
|
1118
pkg/provider/kubernetes/ingress-nginx/kubernetes.go
Normal file
1118
pkg/provider/kubernetes/ingress-nginx/kubernetes.go
Normal file
File diff suppressed because it is too large
Load Diff
605
pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go
Normal file
605
pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
package ingressnginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/tls"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadIngresses(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
ingressClass string
|
||||||
|
defaultBackendServiceName string
|
||||||
|
defaultBackendServiceNamespace string
|
||||||
|
paths []string
|
||||||
|
expected *dynamic.Configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty, no IngressClass",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"ingresses/01-ingress-with-basicauth.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
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: "Basic Auth",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/01-ingress-with-basicauth.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-basicauth-rule-0-path-0": {
|
||||||
|
Rule: "Host(`whoami.localhost`) && Path(`/basicauth`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Middlewares: []string{"default-ingress-with-basicauth-rule-0-path-0-basic-auth"},
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-ingress-with-basicauth-rule-0-path-0-basic-auth": {
|
||||||
|
BasicAuth: &dynamic.BasicAuth{
|
||||||
|
Users: dynamic.Users{
|
||||||
|
"user:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=",
|
||||||
|
},
|
||||||
|
Realm: "Authentication Required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Forward Auth",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/02-ingress-with-forwardauth.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-forwardauth-rule-0-path-0": {
|
||||||
|
Rule: "Host(`whoami.localhost`) && Path(`/forwardauth`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Middlewares: []string{"default-ingress-with-forwardauth-rule-0-path-0-forward-auth"},
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-ingress-with-forwardauth-rule-0-path-0-forward-auth": {
|
||||||
|
ForwardAuth: &dynamic.ForwardAuth{
|
||||||
|
Address: "http://whoami.default.svc/",
|
||||||
|
AuthResponseHeaders: []string{"X-Foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "SSL Redirect",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"secrets.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/03-ingress-with-ssl-redirect.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-ssl-redirect-rule-0-path-0": {
|
||||||
|
Rule: "Host(`sslredirect.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
TLS: &dynamic.RouterTLSConfig{},
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
"default-ingress-with-ssl-redirect-rule-0-path-0-redirect": {
|
||||||
|
Rule: "Host(`sslredirect.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Middlewares: []string{"default-ingress-with-ssl-redirect-rule-0-path-0-redirect-scheme"},
|
||||||
|
Service: "noop@internal",
|
||||||
|
},
|
||||||
|
"default-ingress-without-ssl-redirect-rule-0-path-0-http": {
|
||||||
|
Rule: "Host(`withoutsslredirect.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
"default-ingress-without-ssl-redirect-rule-0-path-0": {
|
||||||
|
Rule: "Host(`withoutsslredirect.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
TLS: &dynamic.RouterTLSConfig{},
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
"default-ingress-with-force-ssl-redirect-rule-0-path-0": {
|
||||||
|
Rule: "Host(`forcesslredirect.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
"default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect": {
|
||||||
|
Rule: "Host(`forcesslredirect.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Middlewares: []string{"default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect-scheme"},
|
||||||
|
Service: "noop@internal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-ingress-with-ssl-redirect-rule-0-path-0-redirect-scheme": {
|
||||||
|
RedirectScheme: &dynamic.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect-scheme": {
|
||||||
|
RedirectScheme: &dynamic.RedirectScheme{
|
||||||
|
Scheme: "https",
|
||||||
|
Permanent: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Certificates: []*tls.CertAndStores{
|
||||||
|
{
|
||||||
|
Certificate: tls.Certificate{
|
||||||
|
CertFile: "-----BEGIN CERTIFICATE-----",
|
||||||
|
KeyFile: "-----BEGIN CERTIFICATE-----",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "SSL Passthrough",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"secrets.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/04-ingress-with-ssl-passthrough.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"default-ingress-with-ssl-passthrough-passthrough-whoami-localhost": {
|
||||||
|
Rule: "HostSNI(`passthrough.whoami.localhost`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: true,
|
||||||
|
},
|
||||||
|
Service: "default-whoami-tls-443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.TCPService{
|
||||||
|
"default-whoami-tls-443": {
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "10.10.0.5:8443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "10.10.0.6:8443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: "Sticky Sessions",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"secrets.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/06-ingress-with-sticky.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-sticky-rule-0-path-0": {
|
||||||
|
Rule: "Host(`sticky.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
Sticky: &dynamic.Sticky{
|
||||||
|
Cookie: &dynamic.Cookie{
|
||||||
|
Name: "foobar",
|
||||||
|
Domain: "foo.localhost",
|
||||||
|
HTTPOnly: true,
|
||||||
|
MaxAge: 42,
|
||||||
|
Path: ptr.To("/foobar"),
|
||||||
|
SameSite: "none",
|
||||||
|
Secure: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Proxy SSL",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"secrets.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/07-ingress-with-proxy-ssl.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-proxy-ssl-rule-0-path-0": {
|
||||||
|
Rule: "Host(`proxy-ssl.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Service: "default-whoami-tls-443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-tls-443": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "https://10.10.0.5:8443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "https://10.10.0.6:8443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
ServersTransport: "default-ingress-with-proxy-ssl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||||
|
"default-ingress-with-proxy-ssl": {
|
||||||
|
ServerName: "whoami.localhost",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
RootCAs: []types.FileOrContent{"-----BEGIN CERTIFICATE-----"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "CORS",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/08-ingress-with-cors.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-cors-rule-0-path-0": {
|
||||||
|
Rule: "Host(`cors.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Middlewares: []string{"default-ingress-with-cors-rule-0-path-0-cors"},
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-ingress-with-cors-rule-0-path-0-cors": {
|
||||||
|
Headers: &dynamic.Headers{
|
||||||
|
AccessControlAllowCredentials: true,
|
||||||
|
AccessControlAllowHeaders: []string{"X-Foo"},
|
||||||
|
AccessControlAllowMethods: []string{"PUT", "GET", "POST", "OPTIONS"},
|
||||||
|
AccessControlAllowOriginList: []string{"*"},
|
||||||
|
AccessControlExposeHeaders: []string{"X-Forwarded-For", "X-Forwarded-Host"},
|
||||||
|
AccessControlMaxAge: 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Service Upstream",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
"ingressclasses.yml",
|
||||||
|
"ingresses/09-ingress-with-service-upstream.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-ingress-with-service-upstream-rule-0-path-0": {
|
||||||
|
Rule: "Host(`service-upstream.localhost`) && Path(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Service: "default-whoami-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.10.1:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Default Backend",
|
||||||
|
defaultBackendServiceName: "whoami",
|
||||||
|
defaultBackendServiceNamespace: "default",
|
||||||
|
paths: []string{
|
||||||
|
"services.yml",
|
||||||
|
},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-backend": {
|
||||||
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Priority: math.MinInt32,
|
||||||
|
Service: "default-backend",
|
||||||
|
},
|
||||||
|
"default-backend-tls": {
|
||||||
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "default",
|
||||||
|
Priority: math.MinInt32,
|
||||||
|
TLS: &dynamic.RouterTLSConfig{},
|
||||||
|
Service: "default-backend",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-backend": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:8000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:8000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: "wrr",
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: dynamic.DefaultFlushInterval,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
k8sObjects := readResources(t, test.paths)
|
||||||
|
kubeClient := kubefake.NewClientset(k8sObjects...)
|
||||||
|
client := newClient(kubeClient)
|
||||||
|
|
||||||
|
eventCh, err := client.WatchAll(t.Context(), "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if len(k8sObjects) > 0 {
|
||||||
|
// just wait for the first event
|
||||||
|
<-eventCh
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Provider{
|
||||||
|
k8sClient: client,
|
||||||
|
defaultBackendServiceName: test.defaultBackendServiceName,
|
||||||
|
defaultBackendServiceNamespace: test.defaultBackendServiceNamespace,
|
||||||
|
}
|
||||||
|
p.SetDefaults()
|
||||||
|
|
||||||
|
conf := p.loadConfiguration(t.Context())
|
||||||
|
assert.Equal(t, test.expected, conf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readResources(t *testing.T, paths []string) []runtime.Object {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var k8sObjects []runtime.Object
|
||||||
|
for _, path := range paths {
|
||||||
|
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k8sObjects = append(k8sObjects, k8s.MustParseYaml(yamlContent)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return k8sObjects
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user