Compare commits
31 commits
35c53a7bf3
...
5482418b84
Author | SHA1 | Date | |
---|---|---|---|
5482418b84 | |||
|
e9bd2b45ac | ||
|
6e61fe0de1 | ||
|
0e215f9b61 | ||
|
7fdb1ff8af | ||
|
736f37cb58 | ||
|
cff71ee496 | ||
|
f02b223639 | ||
|
d4d23dce72 | ||
|
5e4dc783c7 | ||
|
440cb11250 | ||
|
42920595ad | ||
|
e68e647fd9 | ||
|
8b558646fc | ||
|
f8e45a0b29 | ||
de609cd667 | |||
|
d65de8fe6c | ||
|
5f2c00b438 | ||
|
c2c1c3e09e | ||
|
d8a778b5cd | ||
|
d8cf90dade | ||
|
6a06560318 | ||
|
a4aad5ce5c | ||
|
15973f5503 | ||
|
a4150409c8 | ||
|
aee515b930 | ||
|
b0d19bd466 | ||
|
d99d2f95e6 | ||
|
8d2a2ff08f | ||
|
73e5dbbfe5 | ||
|
ee3e7cbbec |
94 changed files with 4660 additions and 3285 deletions
212
CHANGELOG.md
212
CHANGELOG.md
|
@ -1,3 +1,215 @@
|
||||||
|
## [v3.0.1](https://github.com/traefik/traefik/tree/v3.0.1) (2024-05-22)
|
||||||
|
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0...v3.0.1)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[k8s/ingress]** Fix rule syntax version for all internal routers ([#10689](https://github.com/traefik/traefik/pull/10689) by [HalloTschuess](https://github.com/HalloTschuess))
|
||||||
|
- **[metrics,tracing]** Allow empty configuration for OpenTelemetry metrics and tracing ([#10729](https://github.com/traefik/traefik/pull/10729) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[provider,tls]** Bump tscert dependency to 28a91b69a046 ([#10668](https://github.com/traefik/traefik/pull/10668) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[rules,tcp]** Fix the rule syntax mechanism for TCP ([#10680](https://github.com/traefik/traefik/pull/10680) by [lbenguigui](https://github.com/lbenguigui))
|
||||||
|
- **[tls,server]** Remove deadlines when handling PostgreSQL connections ([#10675](https://github.com/traefik/traefik/pull/10675) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[webui]** Add support for IP White list ([#10740](https://github.com/traefik/traefik/pull/10740) by [davidbaptista](https://github.com/davidbaptista))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[http3]** Add link to the new http3 config in migration ([#10673](https://github.com/traefik/traefik/pull/10673) by [yyewolf](https://github.com/yyewolf))
|
||||||
|
- **[logs]** Fix log.compress value ([#10716](https://github.com/traefik/traefik/pull/10716) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[metrics]** Fix OTel documentation ([#10723](https://github.com/traefik/traefik/pull/10723) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[middleware]** Fix doc consistency forwardauth ([#10724](https://github.com/traefik/traefik/pull/10724) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[middleware]** Remove providers not supported in documentation ([#10725](https://github.com/traefik/traefik/pull/10725) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[rules]** Fix typo in PathRegexp explanation ([#10719](https://github.com/traefik/traefik/pull/10719) by [BreadInvasion](https://github.com/BreadInvasion))
|
||||||
|
- **[rules]** Fix router documentation example ([#10704](https://github.com/traefik/traefik/pull/10704) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
## [v2.11.3](https://github.com/traefik/traefik/tree/v2.11.3) (2024-05-17)
|
||||||
|
[All Commits](https://github.com/traefik/traefik/compare/v2.11.2...v2.11.3)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[server]** Remove deadlines for non-TLS connections ([#10615](https://github.com/traefik/traefik/pull/10615) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[webui]** Display of Content Security Policy values getting out of screen ([#10710](https://github.com/traefik/traefik/pull/10710) by [brandonfl](https://github.com/brandonfl))
|
||||||
|
- **[webui]** Fix provider icon size ([#10621](https://github.com/traefik/traefik/pull/10621) by [framebassman](https://github.com/framebassman))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[k8s/crd]** Fix migration/v2.md ([#10658](https://github.com/traefik/traefik/pull/10658) by [stemar94](https://github.com/stemar94))
|
||||||
|
- **[k8s/gatewayapi]** Fix HTTPRoute use of backendRefs ([#10630](https://github.com/traefik/traefik/pull/10630) by [sakaru](https://github.com/sakaru))
|
||||||
|
- **[k8s/gatewayapi]** Fix HTTPRoute path type ([#10629](https://github.com/traefik/traefik/pull/10629) by [sakaru](https://github.com/sakaru))
|
||||||
|
- **[k8s]** Improve mirroring example on Kubernetes ([#10701](https://github.com/traefik/traefik/pull/10701) by [mloiseleur](https://github.com/mloiseleur))
|
||||||
|
- Consistent entryPoints capitalization in CLI flag usage ([#10650](https://github.com/traefik/traefik/pull/10650) by [jnoordsij](https://github.com/jnoordsij))
|
||||||
|
- Fix unfinished migration sentence for v2.11.2 ([#10633](https://github.com/traefik/traefik/pull/10633) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
|
||||||
|
## [v3.0.0](https://github.com/traefik/traefik/tree/v3.0.0) (2024-04-29)
|
||||||
|
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-beta1...v3.0.0)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
- **[consul]** ConsulCatalog StrictChecks ([#10388](https://github.com/traefik/traefik/pull/10388) by [djenriquez](https://github.com/djenriquez))
|
||||||
|
- **[docker,docker/swarm]** Split Docker provider ([#9652](https://github.com/traefik/traefik/pull/9652) by [ldez](https://github.com/ldez))
|
||||||
|
- **[docker,service]** Adds weight on ServersLoadBalancer ([#10372](https://github.com/traefik/traefik/pull/10372) by [juliens](https://github.com/juliens))
|
||||||
|
- **[ecs]** Add option to keep only healthy ECS tasks ([#8027](https://github.com/traefik/traefik/pull/8027) by [Michampt](https://github.com/Michampt))
|
||||||
|
- **[file]** Reload provider file configuration on SIGHUP ([#9993](https://github.com/traefik/traefik/pull/9993) by [sokoide](https://github.com/sokoide))
|
||||||
|
- **[healthcheck]** Support gRPC healthcheck ([#8583](https://github.com/traefik/traefik/pull/8583) by [jjacque](https://github.com/jjacque))
|
||||||
|
- **[healthcheck]** Add a status option to the service health check ([#9463](https://github.com/traefik/traefik/pull/9463) by [guoard](https://github.com/guoard))
|
||||||
|
- **[http]** Support custom headers when fetching configuration through HTTP ([#9421](https://github.com/traefik/traefik/pull/9421) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[http3]** Moves HTTP/3 outside the experimental section ([#9570](https://github.com/traefik/traefik/pull/9570) by [sdelicata](https://github.com/sdelicata))
|
||||||
|
- **[k8s,hub]** Remove deprecated code ([#9804](https://github.com/traefik/traefik/pull/9804) by [ldez](https://github.com/ldez))
|
||||||
|
- **[k8s,k8s/gatewayapi]** Support for cross-namespace references / GatewayAPI ReferenceGrants ([#10346](https://github.com/traefik/traefik/pull/10346) by [pascal-hofmann](https://github.com/pascal-hofmann))
|
||||||
|
- **[k8s,k8s/gatewayapi]** Support HostSNIRegexp in GatewayAPI TLS routes ([#9486](https://github.com/traefik/traefik/pull/9486) by [ddtmachado](https://github.com/ddtmachado))
|
||||||
|
- **[k8s,k8s/gatewayapi]** Upgrade gateway api to v1.0.0 ([#10205](https://github.com/traefik/traefik/pull/10205) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[k8s/crd,k8s]** Support file path as input param for Kubernetes token value ([#10232](https://github.com/traefik/traefik/pull/10232) by [sssash18](https://github.com/sssash18))
|
||||||
|
- **[k8s/gatewayapi]** Add option to set Gateway status address ([#10582](https://github.com/traefik/traefik/pull/10582) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[k8s/gatewayapi]** Toggle support for experimental channel ([#10435](https://github.com/traefik/traefik/pull/10435) by [SantoDE](https://github.com/SantoDE))
|
||||||
|
- **[k8s/gatewayapi]** Add option to set Gateway status address ([#10582](https://github.com/traefik/traefik/pull/10582) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[k8s/gatewayapi]** Add support for HTTPRequestRedirectFilter in k8s Gateway API ([#9408](https://github.com/traefik/traefik/pull/9408) by [romantomjak](https://github.com/romantomjak))
|
||||||
|
- **[k8s/gatewayapi]** Handle middlewares in filters extension reference ([#10511](https://github.com/traefik/traefik/pull/10511) by [youkoulayley](https://github.com/youkoulayley))
|
||||||
|
- **[k8s/ingress,k8s/crd,k8s,k8s/gatewayapi]** Use runtime.Object in routerTransform ([#10523](https://github.com/traefik/traefik/pull/10523) by [juliens](https://github.com/juliens))
|
||||||
|
- **[k8s/ingress,k8s]** Add option to the Ingress provider to disable IngressClass lookup ([#9281](https://github.com/traefik/traefik/pull/9281) by [jandillenkofer](https://github.com/jandillenkofer))
|
||||||
|
- **[k8s/ingress,k8s]** Remove support of the networking.k8s.io/v1beta1 APIVersion ([#9949](https://github.com/traefik/traefik/pull/9949) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[logs]** Introduce static config hints ([#10351](https://github.com/traefik/traefik/pull/10351) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[logs,performance]** New logger for the Traefik logs ([#9515](https://github.com/traefik/traefik/pull/9515) by [ldez](https://github.com/ldez))
|
||||||
|
- **[logs,plugins]** Retry on plugin API calls ([#9530](https://github.com/traefik/traefik/pull/9530) by [ldez](https://github.com/ldez))
|
||||||
|
- **[logs,provider]** Improve provider logs ([#9562](https://github.com/traefik/traefik/pull/9562) by [ldez](https://github.com/ldez))
|
||||||
|
- **[logs]** Improve test logger assertions ([#9533](https://github.com/traefik/traefik/pull/9533) by [ldez](https://github.com/ldez))
|
||||||
|
- **[marathon]** Remove Marathon provider ([#9614](https://github.com/traefik/traefik/pull/9614) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[metrics,tracing,accesslogs]** Remove observability for internal resources ([#9633](https://github.com/traefik/traefik/pull/9633) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[metrics,tracing]** Upgrade opentelemetry dependencies ([#10472](https://github.com/traefik/traefik/pull/10472) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[metrics]** Add support for sending DogStatsD metrics over Unix Socket ([#10199](https://github.com/traefik/traefik/pull/10199) by [liamvdv](https://github.com/liamvdv))
|
||||||
|
- **[metrics]** Remove InfluxDB v1 metrics middleware ([#9612](https://github.com/traefik/traefik/pull/9612) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[metrics]** Upgrade OpenTelemetry dependencies ([#10181](https://github.com/traefik/traefik/pull/10181) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[metrics]** Support gRPC and gRPC-Web protocol in metrics ([#9483](https://github.com/traefik/traefik/pull/9483) by [longit644](https://github.com/longit644))
|
||||||
|
- **[middleware,accesslogs]** Log TLS client subject ([#9285](https://github.com/traefik/traefik/pull/9285) by [xmessi](https://github.com/xmessi))
|
||||||
|
- **[middleware,metrics,tracing,otel]** Add OpenTelemetry tracing and metrics support ([#8999](https://github.com/traefik/traefik/pull/8999) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[middleware]** Disable Content-Type auto-detection by default ([#9546](https://github.com/traefik/traefik/pull/9546) by [sdelicata](https://github.com/sdelicata))
|
||||||
|
- **[middleware]** Add gRPC-Web middleware ([#9451](https://github.com/traefik/traefik/pull/9451) by [juliens](https://github.com/juliens))
|
||||||
|
- **[middleware]** Add support for Brotli ([#9387](https://github.com/traefik/traefik/pull/9387) by [glinton](https://github.com/glinton))
|
||||||
|
- **[middleware]** Renaming IPWhiteList to IPAllowList ([#9457](https://github.com/traefik/traefik/pull/9457) by [wxmbugu](https://github.com/wxmbugu))
|
||||||
|
- **[middleware,authentication,tracing]** Add captured headers options for tracing ([#10457](https://github.com/traefik/traefik/pull/10457) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[middleware,authentication]** Add forwardAuth.addAuthCookiesToResponse ([#8924](https://github.com/traefik/traefik/pull/8924) by [tgunsch](https://github.com/tgunsch))
|
||||||
|
- **[middleware,metrics]** Semconv OTLP stable HTTP metrics ([#10421](https://github.com/traefik/traefik/pull/10421) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[middleware]** Feat re introduce IpWhitelist middleware as deprecated ([#10341](https://github.com/traefik/traefik/pull/10341) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[middleware]** Disable br compression when no Accept-Encoding header is present ([#10178](https://github.com/traefik/traefik/pull/10178) by [robin-moser](https://github.com/robin-moser))
|
||||||
|
- **[middleware]** Implements the includedContentTypes option for the compress middleware ([#10207](https://github.com/traefik/traefik/pull/10207) by [rjsocha](https://github.com/rjsocha))
|
||||||
|
- **[middleware]** Add `rejectStatusCode` option to `IPAllowList` middleware ([#10130](https://github.com/traefik/traefik/pull/10130) by [jfly](https://github.com/jfly))
|
||||||
|
- **[middleware]** Merge v2.11 into v3.0 ([#10426](https://github.com/traefik/traefik/pull/10426) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[middleware]** Add ResponseCode to CircuitBreaker ([#10147](https://github.com/traefik/traefik/pull/10147) by [fahhem](https://github.com/fahhem))
|
||||||
|
- **[nomad]** Allow empty services ([#10375](https://github.com/traefik/traefik/pull/10375) by [chrispruitt](https://github.com/chrispruitt))
|
||||||
|
- **[nomad]** Support multiple namespaces in the Nomad Provider ([#9332](https://github.com/traefik/traefik/pull/9332) by [0teh](https://github.com/0teh))
|
||||||
|
- **[plugins]** Add http-wasm plugin support to Traefik ([#10189](https://github.com/traefik/traefik/pull/10189) by [zetaab](https://github.com/zetaab))
|
||||||
|
- **[plugins]** Upgrade http-wasm host to v0.6.0 to support clients using v0.4.0 ([#10475](https://github.com/traefik/traefik/pull/10475) by [jcchavezs](https://github.com/jcchavezs))
|
||||||
|
- **[rancher]** Remove Rancher v1 provider ([#9613](https://github.com/traefik/traefik/pull/9613) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[rules]** Bring back v2 rule matchers ([#10339](https://github.com/traefik/traefik/pull/10339) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[rules]** Remove containous/mux from HTTP muxer ([#9558](https://github.com/traefik/traefik/pull/9558) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[rules]** Update routing syntax ([#9531](https://github.com/traefik/traefik/pull/9531) by [skwair](https://github.com/skwair))
|
||||||
|
- **[server]** Add SO_REUSEPORT support for EntryPoints ([#9834](https://github.com/traefik/traefik/pull/9834) by [aofei](https://github.com/aofei))
|
||||||
|
- **[server]** Rework servers load-balancer to use the WRR ([#9431](https://github.com/traefik/traefik/pull/9431) by [juliens](https://github.com/juliens))
|
||||||
|
- **[server]** Allow default entrypoints definition ([#9100](https://github.com/traefik/traefik/pull/9100) by [applejag](https://github.com/applejag))
|
||||||
|
- **[sticky-session]** Support setting sticky cookie max age ([#10176](https://github.com/traefik/traefik/pull/10176) by [Patrick0308](https://github.com/Patrick0308))
|
||||||
|
- **[tls,tcp,service]** Add TCP Servers Transports support ([#9465](https://github.com/traefik/traefik/pull/9465) by [sdelicata](https://github.com/sdelicata))
|
||||||
|
- **[tls,service]** Support SPIFFE mTLS between Traefik and Backend servers ([#9394](https://github.com/traefik/traefik/pull/9394) by [jlevesy](https://github.com/jlevesy))
|
||||||
|
- **[tls]** Add Tailscale certificate resolver ([#9237](https://github.com/traefik/traefik/pull/9237) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[tls]** Support SNI routing with Postgres STARTTLS connections ([#9377](https://github.com/traefik/traefik/pull/9377) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[tracing,otel]** Migrate to opentelemetry ([#10223](https://github.com/traefik/traefik/pull/10223) by [zetaab](https://github.com/zetaab))
|
||||||
|
- **[tracing]** Support OTEL_PROPAGATORS to configure tracing propagation ([#10465](https://github.com/traefik/traefik/pull/10465) by [youkoulayley](https://github.com/youkoulayley))
|
||||||
|
- **[webui,middleware,k8s/gatewayapi]** Support RequestHeaderModifier filter ([#10521](https://github.com/traefik/traefik/pull/10521) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[webui]** Added router priority to webui's list and detail page ([#9004](https://github.com/traefik/traefik/pull/9004) by [bendre90](https://github.com/bendre90))
|
||||||
|
- Reintroduce dropped v2 dynamic config ([#10355](https://github.com/traefik/traefik/pull/10355) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Remove deprecated options ([#9527](https://github.com/traefik/traefik/pull/9527) by [sdelicata](https://github.com/sdelicata))
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[consul,tls]** Enable TLS for Consul Connect TCP services ([#10140](https://github.com/traefik/traefik/pull/10140) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[docker]** Fix struct names in comment ([#10503](https://github.com/traefik/traefik/pull/10503) by [hishope](https://github.com/hishope))
|
||||||
|
- **[k8s/crd,k8s]** Adds the missing circuit-breaker response code for CRD ([#10625](https://github.com/traefik/traefik/pull/10625) by [ldez](https://github.com/ldez))
|
||||||
|
- **[k8s/crd,k8s]** Delete warning in Kubernetes CRD provider about the supported version ([#10414](https://github.com/traefik/traefik/pull/10414) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[logs]** Avoid cumulative send anonymous usage log ([#10579](https://github.com/traefik/traefik/pull/10579) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[logs]** Change traefik cmd error log to error level ([#9569](https://github.com/traefik/traefik/pull/9569) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[logs]** Fix log level ([#9545](https://github.com/traefik/traefik/pull/9545) by [ldez](https://github.com/ldez))
|
||||||
|
- **[metrics]** Fix OpenTelemetry metrics ([#9962](https://github.com/traefik/traefik/pull/9962) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[metrics]** Fix OpenTelemetry service name ([#9619](https://github.com/traefik/traefik/pull/9619) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[metrics]** Fix open connections metric ([#9656](https://github.com/traefik/traefik/pull/9656) by [mpl](https://github.com/mpl))
|
||||||
|
- **[metrics]** Remove config reload failure metrics ([#9660](https://github.com/traefik/traefik/pull/9660) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[metrics]** Fix OpenTelemetry unit tests ([#10380](https://github.com/traefik/traefik/pull/10380) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[metrics]** Fix ServerUp metric ([#9534](https://github.com/traefik/traefik/pull/9534) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware,authentication,metrics,tracing]** Align OpenTelemetry tracing and metrics configurations ([#10404](https://github.com/traefik/traefik/pull/10404) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[middleware]** Fix brotli response status code when compression is disabled ([#10396](https://github.com/traefik/traefik/pull/10396) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[middleware]** Allow short healthcheck interval with long timeout ([#9832](https://github.com/traefik/traefik/pull/9832) by [kevinmcconnell](https://github.com/kevinmcconnell))
|
||||||
|
- **[middleware]** Fix GrpcWeb middleware to clear ContentLength after translating to normal gRPC message ([#9782](https://github.com/traefik/traefik/pull/9782) by [CleverUnderDog](https://github.com/CleverUnderDog))
|
||||||
|
- **[provider,tls]** Bump tscert dependency to 28a91b69a046 ([#10668](https://github.com/traefik/traefik/pull/10668) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[rules]** Rework Host and HostRegexp matchers ([#9559](https://github.com/traefik/traefik/pull/9559) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[rules]** Support regexp in path/pathprefix in matcher v2 ([#10546](https://github.com/traefik/traefik/pull/10546) by [youkoulayley](https://github.com/youkoulayley))
|
||||||
|
- **[sticky-session,server]** Set sameSite field for wrr load balancer sticky cookie ([#10066](https://github.com/traefik/traefik/pull/10066) by [sunyakun](https://github.com/sunyakun))
|
||||||
|
- **[tcp]** Don't log EOF or timeout errors while peeking first bytes in Postgres StartTLS hook ([#9663](https://github.com/traefik/traefik/pull/9663) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[tls,server]** Compute priority for https forwarder TLS routes ([#10288](https://github.com/traefik/traefik/pull/10288) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[tls,service]** Enforce default servers transport SPIFFE config ([#9444](https://github.com/traefik/traefik/pull/9444) by [jlevesy](https://github.com/jlevesy))
|
||||||
|
- **[webui]** Detect dashboard assets content types ([#9622](https://github.com/traefik/traefik/pull/9622) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[webui]** Add missing Docker Swarm logo ([#10529](https://github.com/traefik/traefik/pull/10529) by [ldez](https://github.com/ldez))
|
||||||
|
- **[webui]** fix: detect dashboard content types ([#9594](https://github.com/traefik/traefik/pull/9594) by [ldez](https://github.com/ldez))
|
||||||
|
- Fix a regression on flags using spaces between key and value ([#10445](https://github.com/traefik/traefik/pull/10445) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[docker/swarm]** Remove documentation of old swarm options ([#10001](https://github.com/traefik/traefik/pull/10001) by [ldez](https://github.com/ldez))
|
||||||
|
- **[docker/swarm]** Fix minor typo in swarm example ([#10071](https://github.com/traefik/traefik/pull/10071) by [kaznovac](https://github.com/kaznovac))
|
||||||
|
- **[k8s,k8s/gatewayapi]** Add ReferenceGrants to Gateway API Traefik controller RBAC ([#10462](https://github.com/traefik/traefik/pull/10462) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[k8s]** Update Kubernetes version for v3 Helm chart ([#10637](https://github.com/traefik/traefik/pull/10637) by [jnoordsij](https://github.com/jnoordsij))
|
||||||
|
- **[k8s]** Improve Kubernetes support documentation ([#9974](https://github.com/traefik/traefik/pull/9974) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[k8s]** Fix invalid version in docs about Gateway API on Traefik v3 ([#10474](https://github.com/traefik/traefik/pull/10474) by [mloiseleur](https://github.com/mloiseleur))
|
||||||
|
- **[rules]** Improve ruleSyntax option documentation ([#10441](https://github.com/traefik/traefik/pull/10441) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Prepare release v3.0.0 ([#10666](https://github.com/traefik/traefik/pull/10666) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Prepare release v3.0.0-rc2 ([#10514](https://github.com/traefik/traefik/pull/10514) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Fix typo in migration docs ([#10478](https://github.com/traefik/traefik/pull/10478) by [Eisberge](https://github.com/Eisberge))
|
||||||
|
- Prepare release v3.0.0 rc3 ([#10520](https://github.com/traefik/traefik/pull/10520) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Fix typo in dialer_test.go ([#10552](https://github.com/traefik/traefik/pull/10552) by [eltociear](https://github.com/eltociear))
|
||||||
|
- Fix typo and improve explanation on internal resources ([#10563](https://github.com/traefik/traefik/pull/10563) by [mloiseleur](https://github.com/mloiseleur))
|
||||||
|
- Prepare release v3.0.0-rc1 ([#10429](https://github.com/traefik/traefik/pull/10429) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Update version comment in quick-start.md ([#10383](https://github.com/traefik/traefik/pull/10383) by [matthieuwerner](https://github.com/matthieuwerner))
|
||||||
|
- Improve migration guide ([#10319](https://github.com/traefik/traefik/pull/10319) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Prepare release v3.0.0 beta5 ([#10273](https://github.com/traefik/traefik/pull/10273) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Prepare release v3.0.0-beta4 ([#10165](https://github.com/traefik/traefik/pull/10165) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Prepare release v3.0.0-rc4 ([#10588](https://github.com/traefik/traefik/pull/10588) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Fix bad anchor on documentation ([#10041](https://github.com/traefik/traefik/pull/10041) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Prepare release v3.0.0-rc5 ([#10605](https://github.com/traefik/traefik/pull/10605) by [ldez](https://github.com/ldez))
|
||||||
|
- Fix migration guide heading ([#9989](https://github.com/traefik/traefik/pull/9989) by [ldez](https://github.com/ldez))
|
||||||
|
- Prepare release v3.0.0-beta3 ([#9978](https://github.com/traefik/traefik/pull/9978) by [ldez](https://github.com/ldez))
|
||||||
|
- Fix some typos in comments ([#10626](https://github.com/traefik/traefik/pull/10626) by [hidewrong](https://github.com/hidewrong))
|
||||||
|
- Adjust quick start ([#9790](https://github.com/traefik/traefik/pull/9790) by [svx](https://github.com/svx))
|
||||||
|
- Mention PathPrefix matcher changes in V3 Migration Guide ([#9727](https://github.com/traefik/traefik/pull/9727) by [aofei](https://github.com/aofei))
|
||||||
|
- Fix yaml indentation in the HTTP3 example ([#9724](https://github.com/traefik/traefik/pull/9724) by [benwaffle](https://github.com/benwaffle))
|
||||||
|
- Add OpenTelemetry in observability overview ([#9654](https://github.com/traefik/traefik/pull/9654) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- Prepare release v3.0.0-beta2 ([#9587](https://github.com/traefik/traefik/pull/9587) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- Prepare release v3.0.0-beta1 ([#9577](https://github.com/traefik/traefik/pull/9577) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
|
||||||
|
**Misc:**
|
||||||
|
- Merge current v2.11 into v3.0 ([#10651](https://github.com/traefik/traefik/pull/10651) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge current v2.11 into v3.0 ([#10632](https://github.com/traefik/traefik/pull/10632) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Merge current v2.11 into v3.0 ([#10604](https://github.com/traefik/traefik/pull/10604) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.11 into v3.0 ([#10587](https://github.com/traefik/traefik/pull/10587) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Merge current v2.11 into v3.0 ([#10566](https://github.com/traefik/traefik/pull/10566) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge current v2.11 into v3.0 ([#10564](https://github.com/traefik/traefik/pull/10564) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.11 into v3.0 ([#10519](https://github.com/traefik/traefik/pull/10519) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Merge v2.11 into v3.0 ([#10513](https://github.com/traefik/traefik/pull/10513) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge v2.11 into v3.0 ([#10417](https://github.com/traefik/traefik/pull/10417) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge current v2.11 into v3.0 ([#10382](https://github.com/traefik/traefik/pull/10382) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge back v2.11 into v3.0 ([#10377](https://github.com/traefik/traefik/pull/10377) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge back v2.11 into v3.0 ([#10353](https://github.com/traefik/traefik/pull/10353) by [youkoulayley](https://github.com/youkoulayley))
|
||||||
|
- Merge current v2.11 into v3.0 ([#10328](https://github.com/traefik/traefik/pull/10328) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge current v2.10 into v3.0 ([#10272](https://github.com/traefik/traefik/pull/10272) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Merge current v2.10 into v3.0 ([#10164](https://github.com/traefik/traefik/pull/10164) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge current v2.10 into v3.0 ([#10038](https://github.com/traefik/traefik/pull/10038) by [mmatur](https://github.com/mmatur))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9977](https://github.com/traefik/traefik/pull/9977) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9931](https://github.com/traefik/traefik/pull/9931) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9896](https://github.com/traefik/traefik/pull/9896) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9867](https://github.com/traefik/traefik/pull/9867) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9850](https://github.com/traefik/traefik/pull/9850) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9845](https://github.com/traefik/traefik/pull/9845) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9803](https://github.com/traefik/traefik/pull/9803) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.10 into v3.0 ([#9793](https://github.com/traefik/traefik/pull/9793) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.9 into v3.0 ([#9722](https://github.com/traefik/traefik/pull/9722) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Merge branch v2.9 into v3.0 ([#9650](https://github.com/traefik/traefik/pull/9650) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- Merge branch v2.9 into v3.0 ([#9632](https://github.com/traefik/traefik/pull/9632) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Merge current v2.9 into master ([#9576](https://github.com/traefik/traefik/pull/9576) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- Merge branch v2.9 into master ([#9554](https://github.com/traefik/traefik/pull/9554) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.9 into master ([#9536](https://github.com/traefik/traefik/pull/9536) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.9 into master ([#9532](https://github.com/traefik/traefik/pull/9532) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.9 into master ([#9482](https://github.com/traefik/traefik/pull/9482) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Merge branch v2.9 into master ([#9464](https://github.com/traefik/traefik/pull/9464) by [ldez](https://github.com/ldez))
|
||||||
|
- Merge branch v2.9 into master ([#9449](https://github.com/traefik/traefik/pull/9449) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Merge branch v2.9 into master ([#9419](https://github.com/traefik/traefik/pull/9419) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- Merge branch v2.9 into master ([#9351](https://github.com/traefik/traefik/pull/9351) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
|
||||||
## [v3.0.0-rc5](https://github.com/traefik/traefik/tree/v3.0.0-rc4) (2024-04-11)
|
## [v3.0.0-rc5](https://github.com/traefik/traefik/tree/v3.0.0-rc4) (2024-04-11)
|
||||||
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-rc4...v3.0.0-rc5)
|
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-rc4...v3.0.0-rc5)
|
||||||
|
|
||||||
|
|
BIN
docs/content/assets/img/middleware/ipwhitelist.png
Normal file
BIN
docs/content/assets/img/middleware/ipwhitelist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -44,7 +44,7 @@ Traefik can be installed in Kubernetes using the Helm chart from <https://github
|
||||||
|
|
||||||
Ensure that the following requirements are met:
|
Ensure that the following requirements are met:
|
||||||
|
|
||||||
* Kubernetes 1.16+
|
* Kubernetes 1.22+
|
||||||
* Helm version 3.9+ is [installed](https://helm.sh/docs/intro/install/)
|
* Helm version 3.9+ is [installed](https://helm.sh/docs/intro/install/)
|
||||||
|
|
||||||
Add Traefik Labs chart repository to Helm:
|
Add Traefik Labs chart repository to Helm:
|
||||||
|
|
|
@ -300,7 +300,7 @@ labels:
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
```yaml tab="Kubernetes"
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
name: test-auth
|
name: test-auth
|
||||||
|
@ -316,13 +316,6 @@ spec:
|
||||||
- "traefik.http.middlewares.test-auth.forwardauth.addAuthCookiesToResponse=Session-Cookie,State-Cookie"
|
- "traefik.http.middlewares.test-auth.forwardauth.addAuthCookiesToResponse=Session-Cookie,State-Cookie"
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
|
||||||
[http.middlewares]
|
|
||||||
[http.middlewares.test-auth.forwardAuth]
|
|
||||||
address = "https://example.com/auth"
|
|
||||||
addAuthCookiesToResponse = ["Session-Cookie", "State-Cookie"]
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
http:
|
http:
|
||||||
middlewares:
|
middlewares:
|
||||||
|
@ -334,6 +327,13 @@ http:
|
||||||
- "State-Cookie"
|
- "State-Cookie"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-auth.forwardAuth]
|
||||||
|
address = "https://example.com/auth"
|
||||||
|
addAuthCookiesToResponse = ["Session-Cookie", "State-Cookie"]
|
||||||
|
```
|
||||||
|
|
||||||
### `tls`
|
### `tls`
|
||||||
|
|
||||||
_Optional_
|
_Optional_
|
||||||
|
|
|
@ -35,18 +35,6 @@ spec:
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
||||||
```
|
```
|
||||||
|
|
||||||
```json tab="Marathon"
|
|
||||||
"labels": {
|
|
||||||
"traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange": "127.0.0.1/32,192.168.1.7"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
|
||||||
# Accepts request from defined IP
|
|
||||||
labels:
|
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
# Accepts request from defined IP
|
# Accepts request from defined IP
|
||||||
http:
|
http:
|
||||||
|
@ -125,20 +113,6 @@ spec:
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth=2"
|
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth=2"
|
||||||
```
|
```
|
||||||
|
|
||||||
```json tab="Marathon"
|
|
||||||
"labels": {
|
|
||||||
"traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange": "127.0.0.1/32, 192.168.1.7",
|
|
||||||
"traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth": "2"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
|
||||||
# Whitelisting Based on `X-Forwarded-For` with `depth=2`
|
|
||||||
labels:
|
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
|
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.depth=2"
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
# Allowlisting Based on `X-Forwarded-For` with `depth=2`
|
# Allowlisting Based on `X-Forwarded-For` with `depth=2`
|
||||||
http:
|
http:
|
||||||
|
@ -207,20 +181,6 @@ spec:
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
||||||
```
|
```
|
||||||
|
|
||||||
```json tab="Marathon"
|
|
||||||
"labels": {
|
|
||||||
"traefik.http.middlewares.test-ipallowlist.ipallowlist.sourceRange=127.0.0.1/32, 192.168.1.0/24"
|
|
||||||
"traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
|
||||||
# Exclude from `X-Forwarded-For`
|
|
||||||
labels:
|
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourceRange=127.0.0.1/32, 192.168.1.0/24"
|
|
||||||
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
# Exclude from `X-Forwarded-For`
|
# Exclude from `X-Forwarded-For`
|
||||||
http:
|
http:
|
||||||
|
|
|
@ -147,6 +147,7 @@ It is now unsupported and would prevent Traefik to start.
|
||||||
##### Remediation
|
##### Remediation
|
||||||
|
|
||||||
The `http3` option should be removed from the static configuration experimental section.
|
The `http3` option should be removed from the static configuration experimental section.
|
||||||
|
To configure `http3`, please checkout the [entrypoint configuration documentation](https://doc.traefik.io/traefik/v3.0/routing/entrypoints/#http3_1).
|
||||||
|
|
||||||
### Consul provider
|
### Consul provider
|
||||||
|
|
||||||
|
|
|
@ -513,7 +513,7 @@ In `v2.10`, the Kubernetes CRDs API Group `traefik.containo.us` is deprecated, a
|
||||||
As the Kubernetes CRD provider still works with both API Versions (`traefik.io/v1alpha1` and `traefik.containo.us/v1alpha1`),
|
As the Kubernetes CRD provider still works with both API Versions (`traefik.io/v1alpha1` and `traefik.containo.us/v1alpha1`),
|
||||||
it means that for the same kind, namespace and name, the provider will only keep the `traefik.io/v1alpha1` resource.
|
it means that for the same kind, namespace and name, the provider will only keep the `traefik.io/v1alpha1` resource.
|
||||||
|
|
||||||
In addition, the Kubernetes CRDs API Version `traefik.io/v1alpha1` will not be supported in Traefik v3 itself.
|
In addition, the Kubernetes CRDs API Version `traefik.containo.us/v1alpha1` will not be supported in Traefik v3 itself.
|
||||||
|
|
||||||
Please note that it is a requirement to update the CRDs and the RBAC in the cluster before upgrading Traefik.
|
Please note that it is a requirement to update the CRDs and the RBAC in the cluster before upgrading Traefik.
|
||||||
To do so, please apply the required [CRDs](https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml) and [RBAC](https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml) manifests for v2.10:
|
To do so, please apply the required [CRDs](https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml) and [RBAC](https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml) manifests for v2.10:
|
||||||
|
|
|
@ -169,14 +169,14 @@ The default is not to perform compression.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
log:
|
log:
|
||||||
compress: 3
|
compress: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[log]
|
[log]
|
||||||
compress = 3
|
compress = true
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--log.compress=3
|
--log.compress=true
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,7 +5,7 @@ description: "Traefik Proxy supports these metrics backend systems: Datadog, Inf
|
||||||
|
|
||||||
# Metrics
|
# Metrics
|
||||||
|
|
||||||
Traefik supports these metrics backends:
|
Traefik provides metrics in the [OpenTelemetry](./opentelemetry.md) format as well as the following vendor specific backends:
|
||||||
|
|
||||||
- [Datadog](./datadog.md)
|
- [Datadog](./datadog.md)
|
||||||
- [InfluxDB2](./influxdb2.md)
|
- [InfluxDB2](./influxdb2.md)
|
||||||
|
@ -46,6 +46,13 @@ addInternals = true
|
||||||
| Open connections | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. |
|
| Open connections | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. |
|
||||||
| TLS certificates not after | Gauge | | The expiration date of certificates. |
|
| TLS certificates not after | Gauge | | The expiration date of certificates. |
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_config_reloads_total
|
||||||
|
traefik_config_last_reload_success
|
||||||
|
traefik_open_connections
|
||||||
|
traefik_tls_certs_not_after
|
||||||
|
```
|
||||||
|
|
||||||
```prom tab="Prometheus"
|
```prom tab="Prometheus"
|
||||||
traefik_config_reloads_total
|
traefik_config_reloads_total
|
||||||
traefik_config_last_reload_success
|
traefik_config_last_reload_success
|
||||||
|
@ -75,13 +82,6 @@ traefik.tls.certs.notAfterTimestamp
|
||||||
{prefix}.tls.certs.notAfterTimestamp
|
{prefix}.tls.certs.notAfterTimestamp
|
||||||
```
|
```
|
||||||
|
|
||||||
```opentelemetry tab="OpenTelemetry"
|
|
||||||
traefik_config_reloads_total
|
|
||||||
traefik_config_last_reload_success
|
|
||||||
traefik_open_connections
|
|
||||||
traefik_tls_certs_not_after
|
|
||||||
```
|
|
||||||
|
|
||||||
### Labels
|
### Labels
|
||||||
|
|
||||||
Here is a comprehensive list of labels that are provided by the global metrics:
|
Here is a comprehensive list of labels that are provided by the global metrics:
|
||||||
|
@ -91,201 +91,9 @@ Here is a comprehensive list of labels that are provided by the global metrics:
|
||||||
| `entrypoint` | Entrypoint that handled the connection | "example_entrypoint" |
|
| `entrypoint` | Entrypoint that handled the connection | "example_entrypoint" |
|
||||||
| `protocol` | Connection protocol | "TCP" |
|
| `protocol` | Connection protocol | "TCP" |
|
||||||
|
|
||||||
## HTTP Metrics
|
## OpenTelemetry Semantic Conventions
|
||||||
|
|
||||||
### EntryPoint Metrics
|
Traefik Proxy follows [official OpenTelemetry semantic conventions v1.23.1](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-metrics.md).
|
||||||
|
|
||||||
| Metric | Type | [Labels](#labels) | Description |
|
|
||||||
|-----------------------|-----------|--------------------------------------------|---------------------------------------------------------------------|
|
|
||||||
| Requests total | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. |
|
|
||||||
| Requests TLS total | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. |
|
|
||||||
| Request duration | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. |
|
|
||||||
| Requests bytes total | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. |
|
|
||||||
| Responses bytes total | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. |
|
|
||||||
|
|
||||||
```prom tab="Prometheus"
|
|
||||||
traefik_entrypoint_requests_total
|
|
||||||
traefik_entrypoint_requests_tls_total
|
|
||||||
traefik_entrypoint_request_duration_seconds
|
|
||||||
traefik_entrypoint_requests_bytes_total
|
|
||||||
traefik_entrypoint_responses_bytes_total
|
|
||||||
```
|
|
||||||
|
|
||||||
```dd tab="Datadog"
|
|
||||||
entrypoint.request.total
|
|
||||||
entrypoint.request.tls.total
|
|
||||||
entrypoint.request.duration
|
|
||||||
entrypoint.requests.bytes.total
|
|
||||||
entrypoint.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```influxdb tab="InfluxDB2"
|
|
||||||
traefik.entrypoint.requests.total
|
|
||||||
traefik.entrypoint.requests.tls.total
|
|
||||||
traefik.entrypoint.request.duration
|
|
||||||
traefik.entrypoint.requests.bytes.total
|
|
||||||
traefik.entrypoint.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```statsd tab="StatsD"
|
|
||||||
# Default prefix: "traefik"
|
|
||||||
{prefix}.entrypoint.request.total
|
|
||||||
{prefix}.entrypoint.request.tls.total
|
|
||||||
{prefix}.entrypoint.request.duration
|
|
||||||
{prefix}.entrypoint.requests.bytes.total
|
|
||||||
{prefix}.entrypoint.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```opentelemetry tab="OpenTelemetry"
|
|
||||||
traefik_entrypoint_requests_total
|
|
||||||
traefik_entrypoint_requests_tls_total
|
|
||||||
traefik_entrypoint_request_duration_seconds
|
|
||||||
traefik_entrypoint_requests_bytes_total
|
|
||||||
traefik_entrypoint_responses_bytes_total
|
|
||||||
```
|
|
||||||
|
|
||||||
### Router Metrics
|
|
||||||
|
|
||||||
| Metric | Type | [Labels](#labels) | Description |
|
|
||||||
|-----------------------|-----------|---------------------------------------------------|----------------------------------------------------------------|
|
|
||||||
| Requests total | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. |
|
|
||||||
| Requests TLS total | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. |
|
|
||||||
| Request duration | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. |
|
|
||||||
| Requests bytes total | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. |
|
|
||||||
| Responses bytes total | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. |
|
|
||||||
|
|
||||||
```prom tab="Prometheus"
|
|
||||||
traefik_router_requests_total
|
|
||||||
traefik_router_requests_tls_total
|
|
||||||
traefik_router_request_duration_seconds
|
|
||||||
traefik_router_requests_bytes_total
|
|
||||||
traefik_router_responses_bytes_total
|
|
||||||
```
|
|
||||||
|
|
||||||
```dd tab="Datadog"
|
|
||||||
router.request.total
|
|
||||||
router.request.tls.total
|
|
||||||
router.request.duration
|
|
||||||
router.requests.bytes.total
|
|
||||||
router.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```influxdb tab="InfluxDB2"
|
|
||||||
traefik.router.requests.total
|
|
||||||
traefik.router.requests.tls.total
|
|
||||||
traefik.router.request.duration
|
|
||||||
traefik.router.requests.bytes.total
|
|
||||||
traefik.router.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```statsd tab="StatsD"
|
|
||||||
# Default prefix: "traefik"
|
|
||||||
{prefix}.router.request.total
|
|
||||||
{prefix}.router.request.tls.total
|
|
||||||
{prefix}.router.request.duration
|
|
||||||
{prefix}.router.requests.bytes.total
|
|
||||||
{prefix}.router.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```opentelemetry tab="OpenTelemetry"
|
|
||||||
traefik_router_requests_total
|
|
||||||
traefik_router_requests_tls_total
|
|
||||||
traefik_router_request_duration_seconds
|
|
||||||
traefik_router_requests_bytes_total
|
|
||||||
traefik_router_responses_bytes_total
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Metrics
|
|
||||||
|
|
||||||
| Metric | Type | Labels | Description |
|
|
||||||
|-----------------------|-----------|-----------------------------------------|-------------------------------------------------------------|
|
|
||||||
| Requests total | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. |
|
|
||||||
| Requests TLS total | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. |
|
|
||||||
| Request duration | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. |
|
|
||||||
| Retries total | Count | `service` | The count of requests retries on a service. |
|
|
||||||
| Server UP | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. |
|
|
||||||
| Requests bytes total | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. |
|
|
||||||
| Responses bytes total | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. |
|
|
||||||
|
|
||||||
```prom tab="Prometheus"
|
|
||||||
traefik_service_requests_total
|
|
||||||
traefik_service_requests_tls_total
|
|
||||||
traefik_service_request_duration_seconds
|
|
||||||
traefik_service_retries_total
|
|
||||||
traefik_service_server_up
|
|
||||||
traefik_service_requests_bytes_total
|
|
||||||
traefik_service_responses_bytes_total
|
|
||||||
```
|
|
||||||
|
|
||||||
```dd tab="Datadog"
|
|
||||||
service.request.total
|
|
||||||
router.service.tls.total
|
|
||||||
service.request.duration
|
|
||||||
service.retries.total
|
|
||||||
service.server.up
|
|
||||||
service.requests.bytes.total
|
|
||||||
service.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```influxdb tab="InfluxDB2"
|
|
||||||
traefik.service.requests.total
|
|
||||||
traefik.service.requests.tls.total
|
|
||||||
traefik.service.request.duration
|
|
||||||
traefik.service.retries.total
|
|
||||||
traefik.service.server.up
|
|
||||||
traefik.service.requests.bytes.total
|
|
||||||
traefik.service.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```statsd tab="StatsD"
|
|
||||||
# Default prefix: "traefik"
|
|
||||||
{prefix}.service.request.total
|
|
||||||
{prefix}.service.request.tls.total
|
|
||||||
{prefix}.service.request.duration
|
|
||||||
{prefix}.service.retries.total
|
|
||||||
{prefix}.service.server.up
|
|
||||||
{prefix}.service.requests.bytes.total
|
|
||||||
{prefix}.service.responses.bytes.total
|
|
||||||
```
|
|
||||||
|
|
||||||
```opentelemetry tab="OpenTelemetry"
|
|
||||||
traefik_service_requests_total
|
|
||||||
traefik_service_requests_tls_total
|
|
||||||
traefik_service_request_duration_seconds
|
|
||||||
traefik_service_retries_total
|
|
||||||
traefik_service_server_up
|
|
||||||
traefik_service_requests_bytes_total
|
|
||||||
traefik_service_responses_bytes_total
|
|
||||||
```
|
|
||||||
|
|
||||||
### Labels
|
|
||||||
|
|
||||||
Here is a comprehensive list of labels that are provided by the metrics:
|
|
||||||
|
|
||||||
| Label | Description | example |
|
|
||||||
|---------------|---------------------------------------|----------------------------|
|
|
||||||
| `cn` | Certificate Common Name | "example.com" |
|
|
||||||
| `code` | Request code | "200" |
|
|
||||||
| `entrypoint` | Entrypoint that handled the request | "example_entrypoint" |
|
|
||||||
| `method` | Request Method | "GET" |
|
|
||||||
| `protocol` | Request protocol | "http" |
|
|
||||||
| `router` | Router that handled the request | "example_router" |
|
|
||||||
| `sans` | Certificate Subject Alternative NameS | "example.com" |
|
|
||||||
| `serial` | Certificate Serial Number | "123..." |
|
|
||||||
| `service` | Service that handled the request | "example_service@provider" |
|
|
||||||
| `tls_cipher` | TLS cipher used for the request | "TLS_FALLBACK_SCSV" |
|
|
||||||
| `tls_version` | TLS version used for the request | "1.0" |
|
|
||||||
| `url` | Service server url | "http://example.com" |
|
|
||||||
|
|
||||||
!!! info "`method` label value"
|
|
||||||
|
|
||||||
If the HTTP method verb on a request is not one defined in the set of common methods for [`HTTP/1.1`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
|
|
||||||
or the [`PRI`](https://datatracker.ietf.org/doc/html/rfc7540#section-11.6) verb (for `HTTP/2`),
|
|
||||||
then the value for the method label becomes `EXTENSION_METHOD`.
|
|
||||||
|
|
||||||
## Semantic Conventions for HTTP Metrics
|
|
||||||
|
|
||||||
Traefik Proxy follows [official OTLP semantic conventions v1.23.1](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.1/docs/http/http-metrics.md).
|
|
||||||
|
|
||||||
### HTTP Server
|
### HTTP Server
|
||||||
|
|
||||||
|
@ -328,3 +136,197 @@ Here is a comprehensive list of labels that are provided by the metrics:
|
||||||
| `server.address` | Name of the local HTTP server that received the request | "example.com" |
|
| `server.address` | Name of the local HTTP server that received the request | "example.com" |
|
||||||
| `server.port` | Port of the local HTTP server that received the request | "80" |
|
| `server.port` | Port of the local HTTP server that received the request | "80" |
|
||||||
| `url.scheme` | The URI scheme component identifying the used protocol | "http" |
|
| `url.scheme` | The URI scheme component identifying the used protocol | "http" |
|
||||||
|
|
||||||
|
## HTTP Metrics
|
||||||
|
|
||||||
|
On top of the official OpenTelemetry semantic conventions, Traefik provides its own metrics to monitor the incoming traffic.
|
||||||
|
|
||||||
|
### EntryPoint Metrics
|
||||||
|
|
||||||
|
| Metric | Type | [Labels](#labels) | Description |
|
||||||
|
|-----------------------|-----------|--------------------------------------------|---------------------------------------------------------------------|
|
||||||
|
| Requests total | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. |
|
||||||
|
| Requests TLS total | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. |
|
||||||
|
| Request duration | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. |
|
||||||
|
| Requests bytes total | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. |
|
||||||
|
| Responses bytes total | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. |
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_entrypoint_requests_total
|
||||||
|
traefik_entrypoint_requests_tls_total
|
||||||
|
traefik_entrypoint_request_duration_seconds
|
||||||
|
traefik_entrypoint_requests_bytes_total
|
||||||
|
traefik_entrypoint_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
|
```prom tab="Prometheus"
|
||||||
|
traefik_entrypoint_requests_total
|
||||||
|
traefik_entrypoint_requests_tls_total
|
||||||
|
traefik_entrypoint_request_duration_seconds
|
||||||
|
traefik_entrypoint_requests_bytes_total
|
||||||
|
traefik_entrypoint_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
|
```dd tab="Datadog"
|
||||||
|
entrypoint.request.total
|
||||||
|
entrypoint.request.tls.total
|
||||||
|
entrypoint.request.duration
|
||||||
|
entrypoint.requests.bytes.total
|
||||||
|
entrypoint.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
```influxdb tab="InfluxDB2"
|
||||||
|
traefik.entrypoint.requests.total
|
||||||
|
traefik.entrypoint.requests.tls.total
|
||||||
|
traefik.entrypoint.request.duration
|
||||||
|
traefik.entrypoint.requests.bytes.total
|
||||||
|
traefik.entrypoint.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
```statsd tab="StatsD"
|
||||||
|
# Default prefix: "traefik"
|
||||||
|
{prefix}.entrypoint.request.total
|
||||||
|
{prefix}.entrypoint.request.tls.total
|
||||||
|
{prefix}.entrypoint.request.duration
|
||||||
|
{prefix}.entrypoint.requests.bytes.total
|
||||||
|
{prefix}.entrypoint.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
### Router Metrics
|
||||||
|
|
||||||
|
| Metric | Type | [Labels](#labels) | Description |
|
||||||
|
|-----------------------|-----------|---------------------------------------------------|----------------------------------------------------------------|
|
||||||
|
| Requests total | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. |
|
||||||
|
| Requests TLS total | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. |
|
||||||
|
| Request duration | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. |
|
||||||
|
| Requests bytes total | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. |
|
||||||
|
| Responses bytes total | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. |
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_router_requests_total
|
||||||
|
traefik_router_requests_tls_total
|
||||||
|
traefik_router_request_duration_seconds
|
||||||
|
traefik_router_requests_bytes_total
|
||||||
|
traefik_router_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
|
```prom tab="Prometheus"
|
||||||
|
traefik_router_requests_total
|
||||||
|
traefik_router_requests_tls_total
|
||||||
|
traefik_router_request_duration_seconds
|
||||||
|
traefik_router_requests_bytes_total
|
||||||
|
traefik_router_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
|
```dd tab="Datadog"
|
||||||
|
router.request.total
|
||||||
|
router.request.tls.total
|
||||||
|
router.request.duration
|
||||||
|
router.requests.bytes.total
|
||||||
|
router.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
```influxdb tab="InfluxDB2"
|
||||||
|
traefik.router.requests.total
|
||||||
|
traefik.router.requests.tls.total
|
||||||
|
traefik.router.request.duration
|
||||||
|
traefik.router.requests.bytes.total
|
||||||
|
traefik.router.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
```statsd tab="StatsD"
|
||||||
|
# Default prefix: "traefik"
|
||||||
|
{prefix}.router.request.total
|
||||||
|
{prefix}.router.request.tls.total
|
||||||
|
{prefix}.router.request.duration
|
||||||
|
{prefix}.router.requests.bytes.total
|
||||||
|
{prefix}.router.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Metrics
|
||||||
|
|
||||||
|
| Metric | Type | Labels | Description |
|
||||||
|
|-----------------------|-----------|-----------------------------------------|-------------------------------------------------------------|
|
||||||
|
| Requests total | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. |
|
||||||
|
| Requests TLS total | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. |
|
||||||
|
| Request duration | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. |
|
||||||
|
| Retries total | Count | `service` | The count of requests retries on a service. |
|
||||||
|
| Server UP | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. |
|
||||||
|
| Requests bytes total | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. |
|
||||||
|
| Responses bytes total | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. |
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_service_requests_total
|
||||||
|
traefik_service_requests_tls_total
|
||||||
|
traefik_service_request_duration_seconds
|
||||||
|
traefik_service_retries_total
|
||||||
|
traefik_service_server_up
|
||||||
|
traefik_service_requests_bytes_total
|
||||||
|
traefik_service_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
|
```prom tab="Prometheus"
|
||||||
|
traefik_service_requests_total
|
||||||
|
traefik_service_requests_tls_total
|
||||||
|
traefik_service_request_duration_seconds
|
||||||
|
traefik_service_retries_total
|
||||||
|
traefik_service_server_up
|
||||||
|
traefik_service_requests_bytes_total
|
||||||
|
traefik_service_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
|
```dd tab="Datadog"
|
||||||
|
service.request.total
|
||||||
|
router.service.tls.total
|
||||||
|
service.request.duration
|
||||||
|
service.retries.total
|
||||||
|
service.server.up
|
||||||
|
service.requests.bytes.total
|
||||||
|
service.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
```influxdb tab="InfluxDB2"
|
||||||
|
traefik.service.requests.total
|
||||||
|
traefik.service.requests.tls.total
|
||||||
|
traefik.service.request.duration
|
||||||
|
traefik.service.retries.total
|
||||||
|
traefik.service.server.up
|
||||||
|
traefik.service.requests.bytes.total
|
||||||
|
traefik.service.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
```statsd tab="StatsD"
|
||||||
|
# Default prefix: "traefik"
|
||||||
|
{prefix}.service.request.total
|
||||||
|
{prefix}.service.request.tls.total
|
||||||
|
{prefix}.service.request.duration
|
||||||
|
{prefix}.service.retries.total
|
||||||
|
{prefix}.service.server.up
|
||||||
|
{prefix}.service.requests.bytes.total
|
||||||
|
{prefix}.service.responses.bytes.total
|
||||||
|
```
|
||||||
|
|
||||||
|
### Labels
|
||||||
|
|
||||||
|
Here is a comprehensive list of labels that are provided by the metrics:
|
||||||
|
|
||||||
|
| Label | Description | example |
|
||||||
|
|---------------|---------------------------------------|----------------------------|
|
||||||
|
| `cn` | Certificate Common Name | "example.com" |
|
||||||
|
| `code` | Request code | "200" |
|
||||||
|
| `entrypoint` | Entrypoint that handled the request | "example_entrypoint" |
|
||||||
|
| `method` | Request Method | "GET" |
|
||||||
|
| `protocol` | Request protocol | "http" |
|
||||||
|
| `router` | Router that handled the request | "example_router" |
|
||||||
|
| `sans` | Certificate Subject Alternative NameS | "example.com" |
|
||||||
|
| `serial` | Certificate Serial Number | "123..." |
|
||||||
|
| `service` | Service that handled the request | "example_service@provider" |
|
||||||
|
| `tls_cipher` | TLS cipher used for the request | "TLS_FALLBACK_SCSV" |
|
||||||
|
| `tls_version` | TLS version used for the request | "1.0" |
|
||||||
|
| `url` | Service server url | "http://example.com" |
|
||||||
|
|
||||||
|
!!! info "`method` label value"
|
||||||
|
|
||||||
|
If the HTTP method verb on a request is not one defined in the set of common methods for [`HTTP/1.1`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
|
||||||
|
or the [`PRI`](https://datatracker.ietf.org/doc/html/rfc7540#section-11.6) verb (for `HTTP/2`),
|
||||||
|
then the value for the method label becomes `EXTENSION_METHOD`.
|
||||||
|
|
|
@ -29,7 +29,7 @@ Read the [Access Logs documentation](./access-logs.md) to learn how to configure
|
||||||
Traefik offers a metrics feature that provides valuable insights about the performance and usage.
|
Traefik offers a metrics feature that provides valuable insights about the performance and usage.
|
||||||
These metrics include the number of requests received, the requests duration, and more.
|
These metrics include the number of requests received, the requests duration, and more.
|
||||||
|
|
||||||
Traefik supports these metrics systems: Prometheus, Datadog, InfluxDB 2.X, and StatsD.
|
On top of supporting metrics in the OpenTelemetry format, Traefik supports the following vendor specific metrics systems: Prometheus, Datadog, InfluxDB 2.X, and StatsD.
|
||||||
|
|
||||||
Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it.
|
Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it.
|
||||||
|
|
||||||
|
@ -37,6 +37,6 @@ Read the [Metrics documentation](./metrics/overview.md) to learn how to configur
|
||||||
|
|
||||||
The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure.
|
The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure.
|
||||||
|
|
||||||
Traefik supports these tracing with OpenTelemetry.
|
Traefik provides tracing information in the OpenTelemery format.
|
||||||
|
|
||||||
Read the [Tracing documentation](./tracing/overview.md) to learn how to configure it.
|
Read the [Tracing documentation](./tracing/overview.md) to learn how to configure it.
|
||||||
|
|
|
@ -337,6 +337,30 @@ providers:
|
||||||
--providers.kubernetescrd.allowexternalnameservices=true
|
--providers.kubernetescrd.allowexternalnameservices=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `nativeLBByDefault`
|
||||||
|
|
||||||
|
_Optional, Default: false_
|
||||||
|
|
||||||
|
Defines whether to use Native Kubernetes load-balancing mode by default.
|
||||||
|
For more information, please check out the IngressRoute `nativeLB` option [documentation](../routing/providers/kubernetes-crd.md#load-balancing).
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
providers:
|
||||||
|
kubernetesCRD:
|
||||||
|
nativeLBByDefault: true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[providers.kubernetesCRD]
|
||||||
|
nativeLBByDefault = true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--providers.kubernetescrd.nativeLBByDefault=true
|
||||||
|
```
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
For additional information, refer to the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.
|
For additional information, refer to the [full example](../user-guides/crd-acme/index.md) with Let's Encrypt.
|
||||||
|
|
|
@ -467,6 +467,30 @@ providers:
|
||||||
--providers.kubernetesingress.allowexternalnameservices=true
|
--providers.kubernetesingress.allowexternalnameservices=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `nativeLBByDefault`
|
||||||
|
|
||||||
|
_Optional, Default: false_
|
||||||
|
|
||||||
|
Defines whether to use Native Kubernetes load-balancing mode by default.
|
||||||
|
For more information, please check out the `traefik.ingress.kubernetes.io/service.nativelb` [service annotation documentation](../routing/providers/kubernetes-ingress.md#on-service).
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
providers:
|
||||||
|
kubernetesIngress:
|
||||||
|
nativeLBByDefault: true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[providers.kubernetesIngress]
|
||||||
|
nativeLBByDefault = true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--providers.kubernetesingress.nativeLBByDefault=true
|
||||||
|
```
|
||||||
|
|
||||||
### Further
|
### Further
|
||||||
|
|
||||||
To learn more about the various aspects of the Ingress specification that Traefik supports,
|
To learn more about the various aspects of the Ingress specification that Traefik supports,
|
||||||
|
|
|
@ -339,6 +339,9 @@ Enable metrics on services. (Default: ```true```)
|
||||||
`--metrics.otlp.explicitboundaries`:
|
`--metrics.otlp.explicitboundaries`:
|
||||||
Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.075000, 0.100000, 0.250000, 0.500000, 0.750000, 1.000000, 2.500000, 5.000000, 7.500000, 10.000000```)
|
Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.075000, 0.100000, 0.250000, 0.500000, 0.750000, 1.000000, 2.500000, 5.000000, 7.500000, 10.000000```)
|
||||||
|
|
||||||
|
`--metrics.otlp.grpc`:
|
||||||
|
gRPC configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`--metrics.otlp.grpc.endpoint`:
|
`--metrics.otlp.grpc.endpoint`:
|
||||||
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
||||||
|
|
||||||
|
@ -360,6 +363,9 @@ TLS insecure skip verify (Default: ```false```)
|
||||||
`--metrics.otlp.grpc.tls.key`:
|
`--metrics.otlp.grpc.tls.key`:
|
||||||
TLS key
|
TLS key
|
||||||
|
|
||||||
|
`--metrics.otlp.http`:
|
||||||
|
HTTP configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`--metrics.otlp.http.endpoint`:
|
`--metrics.otlp.http.endpoint`:
|
||||||
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
||||||
|
|
||||||
|
@ -714,6 +720,9 @@ Kubernetes label selector to use.
|
||||||
`--providers.kubernetescrd.namespaces`:
|
`--providers.kubernetescrd.namespaces`:
|
||||||
Kubernetes namespaces.
|
Kubernetes namespaces.
|
||||||
|
|
||||||
|
`--providers.kubernetescrd.nativelbbydefault`:
|
||||||
|
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
|
||||||
|
|
||||||
`--providers.kubernetescrd.throttleduration`:
|
`--providers.kubernetescrd.throttleduration`:
|
||||||
Ingress refresh throttle duration (Default: ```0```)
|
Ingress refresh throttle duration (Default: ```0```)
|
||||||
|
|
||||||
|
@ -795,6 +804,9 @@ Kubernetes Ingress label selector to use.
|
||||||
`--providers.kubernetesingress.namespaces`:
|
`--providers.kubernetesingress.namespaces`:
|
||||||
Kubernetes namespaces.
|
Kubernetes namespaces.
|
||||||
|
|
||||||
|
`--providers.kubernetesingress.nativelbbydefault`:
|
||||||
|
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
|
||||||
|
|
||||||
`--providers.kubernetesingress.throttleduration`:
|
`--providers.kubernetesingress.throttleduration`:
|
||||||
Ingress refresh throttle duration (Default: ```0```)
|
Ingress refresh throttle duration (Default: ```0```)
|
||||||
|
|
||||||
|
@ -1050,6 +1062,9 @@ Defines additional attributes (key:value) on all spans.
|
||||||
`--tracing.otlp`:
|
`--tracing.otlp`:
|
||||||
Settings for OpenTelemetry. (Default: ```false```)
|
Settings for OpenTelemetry. (Default: ```false```)
|
||||||
|
|
||||||
|
`--tracing.otlp.grpc`:
|
||||||
|
gRPC configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`--tracing.otlp.grpc.endpoint`:
|
`--tracing.otlp.grpc.endpoint`:
|
||||||
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
||||||
|
|
||||||
|
@ -1071,6 +1086,9 @@ TLS insecure skip verify (Default: ```false```)
|
||||||
`--tracing.otlp.grpc.tls.key`:
|
`--tracing.otlp.grpc.tls.key`:
|
||||||
TLS key
|
TLS key
|
||||||
|
|
||||||
|
`--tracing.otlp.http`:
|
||||||
|
HTTP configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`--tracing.otlp.http.endpoint`:
|
`--tracing.otlp.http.endpoint`:
|
||||||
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
||||||
|
|
||||||
|
|
|
@ -339,6 +339,9 @@ Enable metrics on services. (Default: ```true```)
|
||||||
`TRAEFIK_METRICS_OTLP_EXPLICITBOUNDARIES`:
|
`TRAEFIK_METRICS_OTLP_EXPLICITBOUNDARIES`:
|
||||||
Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.075000, 0.100000, 0.250000, 0.500000, 0.750000, 1.000000, 2.500000, 5.000000, 7.500000, 10.000000```)
|
Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.075000, 0.100000, 0.250000, 0.500000, 0.750000, 1.000000, 2.500000, 5.000000, 7.500000, 10.000000```)
|
||||||
|
|
||||||
|
`TRAEFIK_METRICS_OTLP_GRPC`:
|
||||||
|
gRPC configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_METRICS_OTLP_GRPC_ENDPOINT`:
|
`TRAEFIK_METRICS_OTLP_GRPC_ENDPOINT`:
|
||||||
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
||||||
|
|
||||||
|
@ -360,6 +363,9 @@ TLS insecure skip verify (Default: ```false```)
|
||||||
`TRAEFIK_METRICS_OTLP_GRPC_TLS_KEY`:
|
`TRAEFIK_METRICS_OTLP_GRPC_TLS_KEY`:
|
||||||
TLS key
|
TLS key
|
||||||
|
|
||||||
|
`TRAEFIK_METRICS_OTLP_HTTP`:
|
||||||
|
HTTP configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_METRICS_OTLP_HTTP_ENDPOINT`:
|
`TRAEFIK_METRICS_OTLP_HTTP_ENDPOINT`:
|
||||||
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
||||||
|
|
||||||
|
@ -714,6 +720,9 @@ Kubernetes label selector to use.
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_NAMESPACES`:
|
`TRAEFIK_PROVIDERS_KUBERNETESCRD_NAMESPACES`:
|
||||||
Kubernetes namespaces.
|
Kubernetes namespaces.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESCRD_NATIVELBBYDEFAULT`:
|
||||||
|
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESCRD_THROTTLEDURATION`:
|
`TRAEFIK_PROVIDERS_KUBERNETESCRD_THROTTLEDURATION`:
|
||||||
Ingress refresh throttle duration (Default: ```0```)
|
Ingress refresh throttle duration (Default: ```0```)
|
||||||
|
|
||||||
|
@ -795,6 +804,9 @@ Kubernetes Ingress label selector to use.
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NAMESPACES`:
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NAMESPACES`:
|
||||||
Kubernetes namespaces.
|
Kubernetes namespaces.
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NATIVELBBYDEFAULT`:
|
||||||
|
Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_THROTTLEDURATION`:
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_THROTTLEDURATION`:
|
||||||
Ingress refresh throttle duration (Default: ```0```)
|
Ingress refresh throttle duration (Default: ```0```)
|
||||||
|
|
||||||
|
@ -1050,6 +1062,9 @@ Defines additional attributes (key:value) on all spans.
|
||||||
`TRAEFIK_TRACING_OTLP`:
|
`TRAEFIK_TRACING_OTLP`:
|
||||||
Settings for OpenTelemetry. (Default: ```false```)
|
Settings for OpenTelemetry. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_TRACING_OTLP_GRPC`:
|
||||||
|
gRPC configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_TRACING_OTLP_GRPC_ENDPOINT`:
|
`TRAEFIK_TRACING_OTLP_GRPC_ENDPOINT`:
|
||||||
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```)
|
||||||
|
|
||||||
|
@ -1071,6 +1086,9 @@ TLS insecure skip verify (Default: ```false```)
|
||||||
`TRAEFIK_TRACING_OTLP_GRPC_TLS_KEY`:
|
`TRAEFIK_TRACING_OTLP_GRPC_TLS_KEY`:
|
||||||
TLS key
|
TLS key
|
||||||
|
|
||||||
|
`TRAEFIK_TRACING_OTLP_HTTP`:
|
||||||
|
HTTP configuration for the OpenTelemetry collector. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_TRACING_OTLP_HTTP_ENDPOINT`:
|
`TRAEFIK_TRACING_OTLP_HTTP_ENDPOINT`:
|
||||||
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```)
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
allowEmptyServices = true
|
allowEmptyServices = true
|
||||||
allowExternalNameServices = true
|
allowExternalNameServices = true
|
||||||
disableIngressClassLookup = true
|
disableIngressClassLookup = true
|
||||||
|
nativeLBByDefault = true
|
||||||
[providers.kubernetesIngress.ingressEndpoint]
|
[providers.kubernetesIngress.ingressEndpoint]
|
||||||
ip = "foobar"
|
ip = "foobar"
|
||||||
hostname = "foobar"
|
hostname = "foobar"
|
||||||
|
@ -139,6 +140,7 @@
|
||||||
ingressClass = "foobar"
|
ingressClass = "foobar"
|
||||||
throttleDuration = "42s"
|
throttleDuration = "42s"
|
||||||
allowEmptyServices = true
|
allowEmptyServices = true
|
||||||
|
nativeLBByDefault = true
|
||||||
[providers.kubernetesGateway]
|
[providers.kubernetesGateway]
|
||||||
endpoint = "foobar"
|
endpoint = "foobar"
|
||||||
token = "foobar"
|
token = "foobar"
|
||||||
|
|
|
@ -141,6 +141,7 @@ providers:
|
||||||
allowEmptyServices: true
|
allowEmptyServices: true
|
||||||
allowExternalNameServices: true
|
allowExternalNameServices: true
|
||||||
disableIngressClassLookup: true
|
disableIngressClassLookup: true
|
||||||
|
nativeLBByDefault: true
|
||||||
kubernetesCRD:
|
kubernetesCRD:
|
||||||
endpoint: foobar
|
endpoint: foobar
|
||||||
token: foobar
|
token: foobar
|
||||||
|
@ -154,6 +155,7 @@ providers:
|
||||||
ingressClass: foobar
|
ingressClass: foobar
|
||||||
throttleDuration: 42s
|
throttleDuration: 42s
|
||||||
allowEmptyServices: true
|
allowEmptyServices: true
|
||||||
|
nativeLBByDefault: true
|
||||||
kubernetesGateway:
|
kubernetesGateway:
|
||||||
endpoint: foobar
|
endpoint: foobar
|
||||||
token: foobar
|
token: foobar
|
||||||
|
|
|
@ -901,15 +901,15 @@ More information in the dedicated [mirroring](../services/index.md#mirroring-ser
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
mirroring:
|
mirroring:
|
||||||
name: svc1
|
name: svc1 # svc1 receives 100% of the traffic
|
||||||
port: 80
|
port: 80
|
||||||
mirrors:
|
mirrors:
|
||||||
- name: svc2
|
- name: svc2 # svc2 receives a copy of 20% of this traffic
|
||||||
port: 80
|
port: 80
|
||||||
percent: 20
|
percent: 20
|
||||||
- name: svc3
|
- name: svc3 # svc3 receives a copy of 15% of this traffic
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
percent: 20
|
percent: 15
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Mirroring Traefik Service"
|
```yaml tab="Mirroring Traefik Service"
|
||||||
|
@ -922,15 +922,15 @@ More information in the dedicated [mirroring](../services/index.md#mirroring-ser
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
mirroring:
|
mirroring:
|
||||||
name: wrr1
|
name: wrr1 # wrr1 receives 100% of the traffic
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
mirrors:
|
mirrors:
|
||||||
- name: svc2
|
- name: svc2 # svc2 receives a copy of 20% of this traffic
|
||||||
port: 80
|
port: 80
|
||||||
percent: 20
|
percent: 20
|
||||||
- name: svc3
|
- name: svc3 # svc3 receives a copy of 10% of this traffic
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
percent: 20
|
percent: 10
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="K8s Service"
|
```yaml tab="K8s Service"
|
||||||
|
|
|
@ -273,7 +273,7 @@ Kubernetes cluster before creating `HTTPRoute` objects.
|
||||||
| [6] | `rules` | A list of HTTP matchers, filters and actions. |
|
| [6] | `rules` | A list of HTTP matchers, filters and actions. |
|
||||||
| [7] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. |
|
| [7] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. |
|
||||||
| [8] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. |
|
| [8] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. |
|
||||||
| [9] | `type` | Type of match against the path Value (supported types: `Exact`, `PathPrefix`). |
|
| [9] | `type` | Type of match against the path Value (supported types: `Exact`, `PathPrefix`, and `RegularExpression`). |
|
||||||
| [10] | `value` | The value of the HTTP path to match against. |
|
| [10] | `value` | The value of the HTTP path to match against. |
|
||||||
| [11] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
|
| [11] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
|
||||||
| [12] | `name` | Name of the HTTP header to be matched. |
|
| [12] | `name` | Name of the HTTP header to be matched. |
|
||||||
|
|
|
@ -368,7 +368,7 @@ Path are always starting with a `/`, except for `PathRegexp`.
|
||||||
[case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity):
|
[case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
HostRegexp(`(?i)^/products`)
|
PathRegexp(`(?i)^/products`)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Query and QueryRegexp
|
#### Query and QueryRegexp
|
||||||
|
@ -827,7 +827,7 @@ http:
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info "Multiple Hosts in a Rule"
|
!!! info "Multiple Hosts in a Rule"
|
||||||
The rule ```Host(`test1.example.com`,`test2.example.com`)``` will request a certificate with the main domain `test1.example.com` and SAN `test2.example.com`.
|
The rule ```Host(`test1.example.com`) || Host(`test2.example.com`)``` will request a certificate with the main domain `test1.example.com` and SAN `test2.example.com`.
|
||||||
|
|
||||||
#### `domains`
|
#### `domains`
|
||||||
|
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -55,7 +55,7 @@ require (
|
||||||
github.com/spiffe/go-spiffe/v2 v2.1.1
|
github.com/spiffe/go-spiffe/v2 v2.1.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
|
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046
|
||||||
github.com/testcontainers/testcontainers-go v0.30.0
|
github.com/testcontainers/testcontainers-go v0.30.0
|
||||||
github.com/testcontainers/testcontainers-go/modules/k3s v0.30.0
|
github.com/testcontainers/testcontainers-go/modules/k3s v0.30.0
|
||||||
github.com/tetratelabs/wazero v1.5.0
|
github.com/tetratelabs/wazero v1.5.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -80,6 +80,7 @@ github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||||
|
@ -1072,8 +1073,8 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
|
||||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g=
|
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g=
|
||||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 h1:xwMw7LFhV9dbvot9A7NLClP9udqbjrQlIwWMH8e7uiQ=
|
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 h1:8rUlviSVOEe7TMk7W0gIPrW8MqEzYfZHpsNWSf8s2vg=
|
||||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0=
|
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
||||||
|
@ -1410,7 +1411,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
|
@ -199,21 +199,10 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
RunTest: *k8sConformanceRunTest,
|
RunTest: *k8sConformanceRunTest,
|
||||||
// Until the feature are all supported, following tests are skipped.
|
// Until the feature are all supported, following tests are skipped.
|
||||||
SkipTests: []string{
|
SkipTests: []string{
|
||||||
tests.GatewayClassObservedGenerationBump.ShortName,
|
|
||||||
tests.GatewayWithAttachedRoutes.ShortName,
|
|
||||||
tests.GatewayModifyListeners.ShortName,
|
|
||||||
tests.GatewayInvalidTLSConfiguration.ShortName,
|
|
||||||
tests.HTTPRouteHostnameIntersection.ShortName,
|
|
||||||
tests.HTTPRouteListenerHostnameMatching.ShortName,
|
tests.HTTPRouteListenerHostnameMatching.ShortName,
|
||||||
tests.HTTPRouteInvalidNonExistentBackendRef.ShortName,
|
|
||||||
tests.HTTPRouteInvalidReferenceGrant.ShortName,
|
|
||||||
tests.HTTPRouteInvalidCrossNamespaceParentRef.ShortName,
|
tests.HTTPRouteInvalidCrossNamespaceParentRef.ShortName,
|
||||||
tests.HTTPRouteInvalidParentRefNotMatchingSectionName.ShortName,
|
|
||||||
tests.HTTPRouteInvalidCrossNamespaceBackendRef.ShortName,
|
|
||||||
tests.HTTPRouteMatchingAcrossRoutes.ShortName,
|
tests.HTTPRouteMatchingAcrossRoutes.ShortName,
|
||||||
tests.HTTPRoutePartiallyInvalidViaInvalidReferenceGrant.ShortName,
|
tests.HTTPRoutePartiallyInvalidViaInvalidReferenceGrant.ShortName,
|
||||||
tests.HTTPRouteRedirectHostAndStatus.ShortName,
|
|
||||||
tests.HTTPRouteInvalidBackendRefUnknownKind.ShortName,
|
|
||||||
tests.HTTPRoutePathMatchOrder.ShortName,
|
tests.HTTPRoutePathMatchOrder.ShortName,
|
||||||
tests.HTTPRouteHeaderMatching.ShortName,
|
tests.HTTPRouteHeaderMatching.ShortName,
|
||||||
tests.HTTPRouteReferenceGrant.ShortName,
|
tests.HTTPRouteReferenceGrant.ShortName,
|
||||||
|
|
2
integration/testdata/rawdata-consul.json
vendored
2
integration/testdata/rawdata-consul.json
vendored
|
@ -53,6 +53,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
2
integration/testdata/rawdata-etcd.json
vendored
2
integration/testdata/rawdata-etcd.json
vendored
|
@ -53,6 +53,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
2
integration/testdata/rawdata-gateway.json
vendored
2
integration/testdata/rawdata-gateway.json
vendored
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
2
integration/testdata/rawdata-ingress.json
vendored
2
integration/testdata/rawdata-ingress.json
vendored
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
2
integration/testdata/rawdata-redis.json
vendored
2
integration/testdata/rawdata-redis.json
vendored
|
@ -53,6 +53,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
2
integration/testdata/rawdata-zk.json
vendored
2
integration/testdata/rawdata-zk.json
vendored
|
@ -53,6 +53,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806,
|
"priority": 9223372036854775806,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -69,6 +70,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805,
|
"priority": 9223372036854775805,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
|
|
@ -128,6 +128,10 @@ type WeightedRoundRobin struct {
|
||||||
type WRRService struct {
|
type WRRService struct {
|
||||||
Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"`
|
Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty" export:"true"`
|
||||||
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" export:"true"`
|
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty" export:"true"`
|
||||||
|
|
||||||
|
// Status defines an HTTP status code that should be returned when calling the service.
|
||||||
|
// This is required by the Gateway API implementation which expects specific HTTP status to be returned.
|
||||||
|
Status *int `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults Default values for a WRRService.
|
// SetDefaults Default values for a WRRService.
|
||||||
|
|
|
@ -2189,6 +2189,11 @@ func (in *WRRService) DeepCopyInto(out *WRRService) {
|
||||||
*out = new(int)
|
*out = new(int)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.Status != nil {
|
||||||
|
in, out := &in.Status, &out.Status
|
||||||
|
*out = new(int)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: global-native-lb
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- foo
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`foo.com`)
|
||||||
|
services:
|
||||||
|
- name: native-svc-tcp
|
||||||
|
port: 8000
|
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRouteUDP
|
||||||
|
metadata:
|
||||||
|
name: global-native-lb
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- foo
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- services:
|
||||||
|
- name: native-svc-udp
|
||||||
|
port: 8000
|
|
@ -0,0 +1,16 @@
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: global-native-lb
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- foo
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- match: Host(`foo.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: native-svc
|
||||||
|
port: 80
|
|
@ -60,6 +60,7 @@ type Provider struct {
|
||||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||||
AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||||
|
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
|
||||||
|
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
|
|
||||||
|
@ -135,7 +136,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.AllowExternalNameServices {
|
if p.AllowExternalNameServices {
|
||||||
logger.Warn().Msg("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
logger.Info().Msg("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.GoCtx(func(ctxPool context.Context) {
|
pool.GoCtx(func(ctxPool context.Context) {
|
||||||
|
|
|
@ -55,6 +55,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||||
allowCrossNamespace: p.AllowCrossNamespace,
|
allowCrossNamespace: p.AllowCrossNamespace,
|
||||||
allowExternalNameServices: p.AllowExternalNameServices,
|
allowExternalNameServices: p.AllowExternalNameServices,
|
||||||
allowEmptyServices: p.AllowEmptyServices,
|
allowEmptyServices: p.AllowEmptyServices,
|
||||||
|
NativeLBByDefault: p.NativeLBByDefault,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range ingressRoute.Spec.Routes {
|
for _, route := range ingressRoute.Spec.Routes {
|
||||||
|
@ -202,6 +203,7 @@ type configBuilder struct {
|
||||||
allowCrossNamespace bool
|
allowCrossNamespace bool
|
||||||
allowExternalNameServices bool
|
allowExternalNameServices bool
|
||||||
allowEmptyServices bool
|
allowEmptyServices bool
|
||||||
|
NativeLBByDefault bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
||||||
|
@ -377,20 +379,6 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.NativeLB {
|
|
||||||
address, err := getNativeServiceAddress(*service, *svcPort)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, address)}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var servers []dynamic.Server
|
var servers []dynamic.Server
|
||||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||||
if !c.allowExternalNameServices {
|
if !c.allowExternalNameServices {
|
||||||
|
@ -409,6 +397,24 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nativeLB := c.NativeLBByDefault
|
||||||
|
if svc.NativeLB != nil {
|
||||||
|
nativeLB = *svc.NativeLB
|
||||||
|
}
|
||||||
|
if nativeLB {
|
||||||
|
address, err := getNativeServiceAddress(*service, *svcPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []dynamic.Server{{URL: fmt.Sprintf("%s://%s", protocol, address)}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
|
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
|
||||||
nodes, nodesExists, nodesErr := c.client.GetNodes()
|
nodes, nodesExists, nodesErr := c.client.GetNodes()
|
||||||
if nodesErr != nil {
|
if nodesErr != nil {
|
||||||
|
|
|
@ -237,15 +237,6 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.NativeLB {
|
|
||||||
address, err := getNativeServiceAddress(*service, *svcPort)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []dynamic.TCPServer{{Address: address}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var servers []dynamic.TCPServer
|
var servers []dynamic.TCPServer
|
||||||
|
|
||||||
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
|
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
|
||||||
|
@ -280,6 +271,19 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc traefikv1
|
||||||
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
|
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
nativeLB := p.NativeLBByDefault
|
||||||
|
if svc.NativeLB != nil {
|
||||||
|
nativeLB = *svc.NativeLB
|
||||||
|
}
|
||||||
|
if nativeLB {
|
||||||
|
address, err := getNativeServiceAddress(*service, *svcPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []dynamic.TCPServer{{Address: address}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||||
if endpointsErr != nil {
|
if endpointsErr != nil {
|
||||||
return nil, endpointsErr
|
return nil, endpointsErr
|
||||||
|
|
|
@ -7361,3 +7361,339 @@ func (p *extensionBuilderRegistryMock) RegisterBackendFuncs(group, kind string,
|
||||||
|
|
||||||
p.groupKindBackendFuncs[group][kind] = builderFunc
|
p.groupKindBackendFuncs[group][kind] = builderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGlobalNativeLB(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
ingressClass string
|
||||||
|
paths []string
|
||||||
|
NativeLBByDefault bool
|
||||||
|
expected *dynamic.Configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty",
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP with global native Service LB",
|
||||||
|
paths: []string{"services.yml", "with_global_native_service_lb.yml"},
|
||||||
|
NativeLBByDefault: true,
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-global-native-lb-6f97418635c7e18853da": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-global-native-lb-6f97418635c7e18853da",
|
||||||
|
Rule: "Host(`foo.com`)",
|
||||||
|
Priority: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-global-native-lb-6f97418635c7e18853da": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HTTP with native Service LB in ingressroute",
|
||||||
|
paths: []string{"services.yml", "with_native_service_lb.yml"},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-test-route-6f97418635c7e18853da": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-test-route-6f97418635c7e18853da",
|
||||||
|
Rule: "Host(`foo.com`)",
|
||||||
|
Priority: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-test-route-6f97418635c7e18853da": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "TCP with global native Service LB",
|
||||||
|
paths: []string{"tcp/services.yml", "tcp/with_global_native_service_lb.yml"},
|
||||||
|
NativeLBByDefault: true,
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"default-global-native-lb-fdd3e9338e47a45efefc": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-global-native-lb-fdd3e9338e47a45efefc",
|
||||||
|
Rule: "HostSNI(`foo.com`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{
|
||||||
|
"default-global-native-lb-fdd3e9338e47a45efefc": {
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "10.10.0.1:8000",
|
||||||
|
Port: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "TCP with native Service LB in ingressroute",
|
||||||
|
paths: []string{"tcp/services.yml", "tcp/with_native_service_lb.yml"},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"default-test.route-fdd3e9338e47a45efefc": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||||
|
Rule: "HostSNI(`foo.com`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{
|
||||||
|
"default-test.route-fdd3e9338e47a45efefc": {
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "10.10.0.1:8000",
|
||||||
|
Port: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "UDP with native Service LB in ingressroute",
|
||||||
|
paths: []string{"udp/services.yml", "udp/with_native_service_lb.yml"},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{
|
||||||
|
"default-test.route-0": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-test.route-0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.UDPService{
|
||||||
|
"default-test.route-0": {
|
||||||
|
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.UDPServer{
|
||||||
|
{
|
||||||
|
Address: "10.10.0.1:8000",
|
||||||
|
Port: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "UDP with global native Service LB",
|
||||||
|
paths: []string{"udp/services.yml", "udp/with_global_native_service_lb.yml"},
|
||||||
|
NativeLBByDefault: true,
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{
|
||||||
|
"default-global-native-lb-0": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-global-native-lb-0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.UDPService{
|
||||||
|
"default-global-native-lb-0": {
|
||||||
|
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.UDPServer{
|
||||||
|
{
|
||||||
|
Address: "10.10.0.1:8000",
|
||||||
|
Port: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var k8sObjects []runtime.Object
|
||||||
|
var crdObjects []runtime.Object
|
||||||
|
for _, path := range test.paths {
|
||||||
|
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
objects := k8s.MustParseYaml(yamlContent)
|
||||||
|
for _, obj := range objects {
|
||||||
|
switch o := obj.(type) {
|
||||||
|
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||||
|
k8sObjects = append(k8sObjects, o)
|
||||||
|
case *traefikv1alpha1.IngressRoute:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
case *traefikv1alpha1.IngressRouteTCP:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
case *traefikv1alpha1.IngressRouteUDP:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
case *traefikv1alpha1.Middleware:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
case *traefikv1alpha1.TraefikService:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
case *traefikv1alpha1.TLSOption:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
case *traefikv1alpha1.TLSStore:
|
||||||
|
crdObjects = append(crdObjects, o)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||||
|
crdClient := traefikcrdfake.NewSimpleClientset(crdObjects...)
|
||||||
|
|
||||||
|
client := newClientImpl(kubeClient, crdClient)
|
||||||
|
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
|
||||||
|
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if k8sObjects != nil || crdObjects != nil {
|
||||||
|
// just wait for the first event
|
||||||
|
<-eventCh
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Provider{NativeLBByDefault: test.NativeLBByDefault}
|
||||||
|
|
||||||
|
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||||
|
assert.Equal(t, test.expected, conf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -121,15 +121,6 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.NativeLB {
|
|
||||||
address, err := getNativeServiceAddress(*service, *svcPort)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []dynamic.UDPServer{{Address: address}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var servers []dynamic.UDPServer
|
var servers []dynamic.UDPServer
|
||||||
|
|
||||||
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
|
if service.Spec.Type == corev1.ServiceTypeNodePort && svc.NodePortLB {
|
||||||
|
@ -164,6 +155,19 @@ func (p *Provider) loadUDPServers(client Client, namespace string, svc traefikv1
|
||||||
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
|
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
nativeLB := p.NativeLBByDefault
|
||||||
|
if svc.NativeLB != nil {
|
||||||
|
nativeLB = *svc.NativeLB
|
||||||
|
}
|
||||||
|
if nativeLB {
|
||||||
|
address, err := getNativeServiceAddress(*service, *svcPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []dynamic.UDPServer{{Address: address}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||||
if endpointsErr != nil {
|
if endpointsErr != nil {
|
||||||
return nil, endpointsErr
|
return nil, endpointsErr
|
||||||
|
|
|
@ -125,7 +125,7 @@ type LoadBalancerSpec struct {
|
||||||
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
|
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
|
||||||
// The Kubernetes Service itself does load-balance to the pods.
|
// The Kubernetes Service itself does load-balance to the pods.
|
||||||
// By default, NativeLB is false.
|
// By default, NativeLB is false.
|
||||||
NativeLB bool `json:"nativeLB,omitempty"`
|
NativeLB *bool `json:"nativeLB,omitempty"`
|
||||||
// NodePortLB controls, when creating the load-balancer,
|
// NodePortLB controls, when creating the load-balancer,
|
||||||
// whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort.
|
// whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort.
|
||||||
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
||||||
|
|
|
@ -92,7 +92,7 @@ type ServiceTCP struct {
|
||||||
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
|
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
|
||||||
// The Kubernetes Service itself does load-balance to the pods.
|
// The Kubernetes Service itself does load-balance to the pods.
|
||||||
// By default, NativeLB is false.
|
// By default, NativeLB is false.
|
||||||
NativeLB bool `json:"nativeLB,omitempty"`
|
NativeLB *bool `json:"nativeLB,omitempty"`
|
||||||
// NodePortLB controls, when creating the load-balancer,
|
// NodePortLB controls, when creating the load-balancer,
|
||||||
// whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort.
|
// whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort.
|
||||||
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
||||||
|
|
|
@ -37,7 +37,7 @@ type ServiceUDP struct {
|
||||||
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
|
// whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP.
|
||||||
// The Kubernetes Service itself does load-balance to the pods.
|
// The Kubernetes Service itself does load-balance to the pods.
|
||||||
// By default, NativeLB is false.
|
// By default, NativeLB is false.
|
||||||
NativeLB bool `json:"nativeLB,omitempty"`
|
NativeLB *bool `json:"nativeLB,omitempty"`
|
||||||
// NodePortLB controls, when creating the load-balancer,
|
// NodePortLB controls, when creating the load-balancer,
|
||||||
// whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort.
|
// whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort.
|
||||||
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
// It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
|
||||||
|
|
|
@ -577,6 +577,11 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
|
||||||
*out = new(int)
|
*out = new(int)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.NativeLB != nil {
|
||||||
|
in, out := &in.NativeLB, &out.NativeLB
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1333,6 +1338,11 @@ func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
|
||||||
*out = new(dynamic.ProxyProtocol)
|
*out = new(dynamic.ProxyProtocol)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.NativeLB != nil {
|
||||||
|
in, out := &in.NativeLB, &out.NativeLB
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1355,6 +1365,11 @@ func (in *ServiceUDP) DeepCopyInto(out *ServiceUDP) {
|
||||||
*out = new(int)
|
*out = new(int)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.NativeLB != nil {
|
||||||
|
in, out := &in.NativeLB, &out.NativeLB
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,11 @@ type resourceEventHandler struct {
|
||||||
ev chan<- interface{}
|
ev chan<- interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reh *resourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) {
|
func (reh *resourceEventHandler) OnAdd(obj interface{}, _ bool) {
|
||||||
eventHandlerFunc(reh.ev, obj)
|
eventHandlerFunc(reh.ev, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
|
func (reh *resourceEventHandler) OnUpdate(_, newObj interface{}) {
|
||||||
eventHandlerFunc(reh.ev, newObj)
|
eventHandlerFunc(reh.ev, newObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,19 +49,21 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
||||||
// The stores can then be accessed via the Get* functions.
|
// The stores can then be accessed via the Get* functions.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||||
GetGatewayClasses() ([]*gatev1.GatewayClass, error)
|
|
||||||
UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error
|
UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error
|
||||||
UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error
|
UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error
|
||||||
UpdateHTTPRouteStatus(ctx context.Context, gateway *gatev1.Gateway, nsName ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error
|
UpdateHTTPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error
|
||||||
GetGateways() []*gatev1.Gateway
|
UpdateTCPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TCPRouteStatus) error
|
||||||
GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error)
|
UpdateTLSRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TLSRouteStatus) error
|
||||||
GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error)
|
ListGatewayClasses() ([]*gatev1.GatewayClass, error)
|
||||||
GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error)
|
ListGateways() []*gatev1.Gateway
|
||||||
GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error)
|
ListHTTPRoutes() ([]*gatev1.HTTPRoute, error)
|
||||||
|
ListTCPRoutes() ([]*gatev1alpha2.TCPRoute, error)
|
||||||
|
ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error)
|
||||||
|
ListNamespaces(selector labels.Selector) ([]string, error)
|
||||||
|
ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error)
|
||||||
GetService(namespace, name string) (*corev1.Service, bool, error)
|
GetService(namespace, name string) (*corev1.Service, bool, error)
|
||||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||||
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
||||||
GetNamespaces(selector labels.Selector) ([]string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientWrapper struct {
|
type clientWrapper struct {
|
||||||
|
@ -280,7 +282,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||||
return eventCh, nil
|
return eventCh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetNamespaces(selector labels.Selector) ([]string, error) {
|
func (c *clientWrapper) ListNamespaces(selector labels.Selector) ([]string, error) {
|
||||||
ns, err := c.factoryNamespace.Core().V1().Namespaces().Lister().List(selector)
|
ns, err := c.factoryNamespace.Core().V1().Namespaces().Lister().List(selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -297,22 +299,12 @@ func (c *clientWrapper) GetNamespaces(selector labels.Selector) ([]string, error
|
||||||
return namespaces, nil
|
return namespaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) {
|
func (c *clientWrapper) ListHTTPRoutes() ([]*gatev1.HTTPRoute, error) {
|
||||||
var httpRoutes []*gatev1.HTTPRoute
|
var httpRoutes []*gatev1.HTTPRoute
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range c.watchedNamespaces {
|
||||||
if !c.isWatchedNamespace(namespace) {
|
|
||||||
log.Warn().Msgf("Failed to get HTTPRoutes: %q is not within watched namespaces", namespace)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(labels.Everything())
|
routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1().HTTPRoutes().Lister().HTTPRoutes(namespace).List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("listing HTTP routes in namespace %s", namespace)
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) == 0 {
|
|
||||||
log.Debug().Msgf("No HTTPRoutes found in namespace %q", namespace)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpRoutes = append(httpRoutes, routes...)
|
httpRoutes = append(httpRoutes, routes...)
|
||||||
|
@ -321,53 +313,35 @@ func (c *clientWrapper) GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute,
|
||||||
return httpRoutes, nil
|
return httpRoutes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error) {
|
func (c *clientWrapper) ListTCPRoutes() ([]*gatev1alpha2.TCPRoute, error) {
|
||||||
var tcpRoutes []*gatev1alpha2.TCPRoute
|
var tcpRoutes []*gatev1alpha2.TCPRoute
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range c.watchedNamespaces {
|
||||||
if !c.isWatchedNamespace(namespace) {
|
|
||||||
log.Warn().Msgf("Failed to get TCPRoutes: %q is not within watched namespaces", namespace)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha2().TCPRoutes().Lister().TCPRoutes(namespace).List(labels.Everything())
|
routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha2().TCPRoutes().Lister().TCPRoutes(namespace).List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("listing TCP routes in namespace %s", namespace)
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) == 0 {
|
|
||||||
log.Debug().Msgf("No TCPRoutes found in namespace %q", namespace)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpRoutes = append(tcpRoutes, routes...)
|
tcpRoutes = append(tcpRoutes, routes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcpRoutes, nil
|
return tcpRoutes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error) {
|
func (c *clientWrapper) ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error) {
|
||||||
var tlsRoutes []*gatev1alpha2.TLSRoute
|
var tlsRoutes []*gatev1alpha2.TLSRoute
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range c.watchedNamespaces {
|
||||||
if !c.isWatchedNamespace(namespace) {
|
|
||||||
log.Warn().Msgf("Failed to get TLSRoutes: %q is not within watched namespaces", namespace)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha2().TLSRoutes().Lister().TLSRoutes(namespace).List(labels.Everything())
|
routes, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha2().TLSRoutes().Lister().TLSRoutes(namespace).List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("listing TLS routes in namespace %s", namespace)
|
||||||
}
|
|
||||||
|
|
||||||
if len(routes) == 0 {
|
|
||||||
log.Debug().Msgf("No TLSRoutes found in namespace %q", namespace)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsRoutes = append(tlsRoutes, routes...)
|
tlsRoutes = append(tlsRoutes, routes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tlsRoutes, nil
|
return tlsRoutes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
|
func (c *clientWrapper) ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
|
||||||
if !c.isWatchedNamespace(namespace) {
|
if !c.isWatchedNamespace(namespace) {
|
||||||
log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace)
|
log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace)
|
||||||
|
|
||||||
|
@ -382,7 +356,7 @@ func (c *clientWrapper) GetReferenceGrants(namespace string) ([]*gatev1beta1.Ref
|
||||||
return referenceGrants, nil
|
return referenceGrants, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetGateways() []*gatev1.Gateway {
|
func (c *clientWrapper) ListGateways() []*gatev1.Gateway {
|
||||||
var result []*gatev1.Gateway
|
var result []*gatev1.Gateway
|
||||||
|
|
||||||
for ns, factory := range c.factoriesGateway {
|
for ns, factory := range c.factoriesGateway {
|
||||||
|
@ -397,7 +371,7 @@ func (c *clientWrapper) GetGateways() []*gatev1.Gateway {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) GetGatewayClasses() ([]*gatev1.GatewayClass, error) {
|
func (c *clientWrapper) ListGatewayClasses() ([]*gatev1.GatewayClass, error) {
|
||||||
return c.factoryGatewayClass.Gateway().V1().GatewayClasses().Lister().List(labels.Everything())
|
return c.factoryGatewayClass.Gateway().V1().GatewayClasses().Lister().List(labels.Everything())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,7 +411,7 @@ func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStat
|
||||||
return fmt.Errorf("cannot update Gateway status %s/%s: namespace is not within watched namespaces", gateway.Namespace, gateway.Name)
|
return fmt.Errorf("cannot update Gateway status %s/%s: namespace is not within watched namespaces", gateway.Namespace, gateway.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusEquals(gateway.Status, gatewayStatus) {
|
if gatewayStatusEquals(gateway.Status, gatewayStatus) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,89 +429,106 @@ func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStat
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, gateway *gatev1.Gateway, nsName ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error {
|
func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error {
|
||||||
if !c.isWatchedNamespace(nsName.Namespace) {
|
if !c.isWatchedNamespace(route.Namespace) {
|
||||||
return fmt.Errorf("updating HTTPRoute status %s/%s: namespace is not within watched namespaces", nsName.Namespace, nsName.Name)
|
return fmt.Errorf("updating HTTPRoute status %s/%s: namespace is not within watched namespaces", route.Namespace, route.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
route, err := c.factoriesGateway[c.lookupNamespace(nsName.Namespace)].Gateway().V1().HTTPRoutes().Lister().HTTPRoutes(nsName.Namespace).Get(nsName.Name)
|
currentRoute, err := c.factoriesGateway[c.lookupNamespace(route.Namespace)].Gateway().V1().HTTPRoutes().Lister().HTTPRoutes(route.Namespace).Get(route.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting HTTPRoute %s/%s: %w", nsName.Namespace, nsName.Name, err)
|
return fmt.Errorf("getting HTTPRoute %s/%s: %w", route.Namespace, route.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var statuses []gatev1.RouteParentStatus
|
// TODO: keep statuses for gateways managed by other Traefik instances.
|
||||||
for _, status := range route.Status.Parents {
|
var parentStatuses []gatev1.RouteParentStatus
|
||||||
if status.ControllerName != controllerName {
|
for _, currentParentStatus := range currentRoute.Status.Parents {
|
||||||
statuses = append(statuses, status)
|
if currentParentStatus.ControllerName != controllerName {
|
||||||
continue
|
parentStatuses = append(parentStatuses, currentParentStatus)
|
||||||
}
|
|
||||||
if status.ParentRef.Namespace != nil && string(*status.ParentRef.Namespace) != gateway.Namespace {
|
|
||||||
statuses = append(statuses, status)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if string(status.ParentRef.Name) != gateway.Name {
|
|
||||||
statuses = append(statuses, status)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statuses = append(statuses, status.Parents...)
|
|
||||||
|
|
||||||
route = route.DeepCopy()
|
parentStatuses = append(parentStatuses, status.Parents...)
|
||||||
route.Status = gatev1.HTTPRouteStatus{
|
|
||||||
|
currentRoute = currentRoute.DeepCopy()
|
||||||
|
currentRoute.Status = gatev1.HTTPRouteStatus{
|
||||||
RouteStatus: gatev1.RouteStatus{
|
RouteStatus: gatev1.RouteStatus{
|
||||||
Parents: statuses,
|
Parents: parentStatuses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.csGateway.GatewayV1().HTTPRoutes(nsName.Namespace).UpdateStatus(ctx, route, metav1.UpdateOptions{}); err != nil {
|
if _, err := c.csGateway.GatewayV1().HTTPRoutes(route.Namespace).UpdateStatus(ctx, currentRoute, metav1.UpdateOptions{}); err != nil {
|
||||||
return fmt.Errorf("updating HTTPRoute %s/%s status: %w", nsName.Namespace, nsName.Name, err)
|
return fmt.Errorf("updating HTTPRoute %s/%s status: %w", route.Namespace, route.Name, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusEquals(oldStatus, newStatus gatev1.GatewayStatus) bool {
|
func (c *clientWrapper) UpdateTCPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TCPRouteStatus) error {
|
||||||
if len(oldStatus.Listeners) != len(newStatus.Listeners) {
|
if !c.isWatchedNamespace(route.Namespace) {
|
||||||
return false
|
return fmt.Errorf("updating TCPRoute status %s/%s: namespace is not within watched namespaces", route.Namespace, route.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !conditionsEquals(oldStatus.Conditions, newStatus.Conditions) {
|
currentRoute, err := c.factoriesGateway[c.lookupNamespace(route.Namespace)].Gateway().V1alpha2().TCPRoutes().Lister().TCPRoutes(route.Namespace).Get(route.Name)
|
||||||
return false
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting TCPRoute %s/%s: %w", route.Namespace, route.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
listenerMatches := 0
|
// TODO: keep statuses for gateways managed by other Traefik instances.
|
||||||
for _, newListener := range newStatus.Listeners {
|
var parentStatuses []gatev1alpha2.RouteParentStatus
|
||||||
for _, oldListener := range oldStatus.Listeners {
|
for _, currentParentStatus := range currentRoute.Status.Parents {
|
||||||
if newListener.Name == oldListener.Name {
|
if currentParentStatus.ControllerName != controllerName {
|
||||||
if !conditionsEquals(newListener.Conditions, oldListener.Conditions) {
|
parentStatuses = append(parentStatuses, currentParentStatus)
|
||||||
return false
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
listenerMatches++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return listenerMatches == len(oldStatus.Listeners)
|
parentStatuses = append(parentStatuses, status.Parents...)
|
||||||
|
|
||||||
|
currentRoute = currentRoute.DeepCopy()
|
||||||
|
currentRoute.Status = gatev1alpha2.TCPRouteStatus{
|
||||||
|
RouteStatus: gatev1.RouteStatus{
|
||||||
|
Parents: parentStatuses,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.csGateway.GatewayV1alpha2().TCPRoutes(route.Namespace).UpdateStatus(ctx, currentRoute, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("updating TCPRoute %s/%s status: %w", route.Namespace, route.Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func conditionsEquals(conditionsA, conditionsB []metav1.Condition) bool {
|
func (c *clientWrapper) UpdateTLSRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TLSRouteStatus) error {
|
||||||
if len(conditionsA) != len(conditionsB) {
|
if !c.isWatchedNamespace(route.Namespace) {
|
||||||
return false
|
return fmt.Errorf("updating TLSRoute status %s/%s: namespace is not within watched namespaces", route.Namespace, route.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
conditionMatches := 0
|
currentRoute, err := c.factoriesGateway[c.lookupNamespace(route.Namespace)].Gateway().V1alpha2().TLSRoutes().Lister().TLSRoutes(route.Namespace).Get(route.Name)
|
||||||
for _, conditionA := range conditionsA {
|
if err != nil {
|
||||||
for _, conditionB := range conditionsB {
|
return fmt.Errorf("getting TLSRoute %s/%s: %w", route.Namespace, route.Name, err)
|
||||||
if conditionA.Type == conditionB.Type {
|
|
||||||
if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message || conditionA.ObservedGeneration != conditionB.ObservedGeneration {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
conditionMatches++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: keep statuses for gateways managed by other Traefik instances.
|
||||||
|
var parentStatuses []gatev1alpha2.RouteParentStatus
|
||||||
|
for _, currentParentStatus := range currentRoute.Status.Parents {
|
||||||
|
if currentParentStatus.ControllerName != controllerName {
|
||||||
|
parentStatuses = append(parentStatuses, currentParentStatus)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return conditionMatches == len(conditionsA)
|
parentStatuses = append(parentStatuses, status.Parents...)
|
||||||
|
|
||||||
|
currentRoute = currentRoute.DeepCopy()
|
||||||
|
currentRoute.Status = gatev1alpha2.TLSRouteStatus{
|
||||||
|
RouteStatus: gatev1.RouteStatus{
|
||||||
|
Parents: parentStatuses,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.csGateway.GatewayV1alpha2().TLSRoutes(route.Namespace).UpdateStatus(ctx, currentRoute, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("updating TLSRoute %s/%s status: %w", route.Namespace, route.Name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetService returns the named service from the given namespace.
|
// GetService returns the named service from the given namespace.
|
||||||
|
@ -582,11 +573,21 @@ func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool,
|
||||||
// The distinction is necessary because we index all informers on the special
|
// The distinction is necessary because we index all informers on the special
|
||||||
// identifier iff all-namespaces are requested but receive specific namespace
|
// identifier iff all-namespaces are requested but receive specific namespace
|
||||||
// identifiers from the Kubernetes API, so we have to bridge this gap.
|
// identifiers from the Kubernetes API, so we have to bridge this gap.
|
||||||
func (c *clientWrapper) lookupNamespace(ns string) string {
|
func (c *clientWrapper) lookupNamespace(namespace string) string {
|
||||||
if c.isNamespaceAll {
|
if c.isNamespaceAll {
|
||||||
return metav1.NamespaceAll
|
return metav1.NamespaceAll
|
||||||
}
|
}
|
||||||
return ns
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(namespace string) bool {
|
||||||
|
if c.isNamespaceAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(c.watchedNamespaces, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventHandlerFunc will pass the obj on to the events channel or drop it.
|
// eventHandlerFunc will pass the obj on to the events channel or drop it.
|
||||||
|
@ -608,12 +609,51 @@ func translateNotFoundError(err error) (bool, error) {
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// isWatchedNamespace checks to ensure that the namespace is being watched before we request
|
func gatewayStatusEquals(statusA, statusB gatev1.GatewayStatus) bool {
|
||||||
// it to ensure we don't panic by requesting an out-of-watch object.
|
if len(statusA.Listeners) != len(statusB.Listeners) {
|
||||||
func (c *clientWrapper) isWatchedNamespace(ns string) bool {
|
return false
|
||||||
if c.isNamespaceAll {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return slices.Contains(c.watchedNamespaces, ns)
|
if !conditionsEquals(statusA.Conditions, statusB.Conditions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerMatches := 0
|
||||||
|
for _, newListener := range statusB.Listeners {
|
||||||
|
for _, oldListener := range statusA.Listeners {
|
||||||
|
if newListener.Name == oldListener.Name {
|
||||||
|
if !conditionsEquals(newListener.Conditions, oldListener.Conditions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if newListener.AttachedRoutes != oldListener.AttachedRoutes {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerMatches++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return listenerMatches == len(statusA.Listeners)
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionsEquals(conditionsA, conditionsB []metav1.Condition) bool {
|
||||||
|
if len(conditionsA) != len(conditionsB) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionMatches := 0
|
||||||
|
for _, conditionA := range conditionsA {
|
||||||
|
for _, conditionB := range conditionsB {
|
||||||
|
if conditionA.Type == conditionB.Type {
|
||||||
|
if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message || conditionA.ObservedGeneration != conditionB.ObservedGeneration {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
conditionMatches++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditionMatches == len(conditionsA)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatusEquals(t *testing.T) {
|
func Test_gatewayStatusEquals(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
statusA gatev1.GatewayStatus
|
statusA gatev1.GatewayStatus
|
||||||
|
@ -230,13 +230,45 @@ func TestStatusEquals(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Gateway listeners with same conditions but different number of attached routes",
|
||||||
|
statusA: gatev1.GatewayStatus{
|
||||||
|
Listeners: []gatev1.ListenerStatus{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
AttachedRoutes: 1,
|
||||||
|
Conditions: []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: "foobar",
|
||||||
|
Reason: "foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
statusB: gatev1.GatewayStatus{
|
||||||
|
Listeners: []gatev1.ListenerStatus{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
AttachedRoutes: 2,
|
||||||
|
Conditions: []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: "foobar",
|
||||||
|
Reason: "foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
result := statusEquals(test.statusA, test.statusB)
|
result := gatewayStatusEquals(test.statusA, test.statusB)
|
||||||
|
|
||||||
assert.Equal(t, test.expected, result)
|
assert.Equal(t, test.expected, result)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
---
|
|
||||||
kind: GatewayClass
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: my-gateway-class
|
|
||||||
spec:
|
|
||||||
controllerName: traefik.io/gateway-controller
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: Gateway
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: my-gateway
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
gatewayClassName: my-gateway-class
|
|
||||||
listeners: # Use GatewayClass defaults for listener definition.
|
|
||||||
- name: http
|
|
||||||
protocol: HTTP
|
|
||||||
port: 80
|
|
||||||
allowedRoutes:
|
|
||||||
namespaces:
|
|
||||||
from: Same
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: HTTPRoute
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: http-app-1
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: my-gateway
|
|
||||||
kind: Gateway
|
|
||||||
group: gateway.networking.k8s.io
|
|
||||||
hostnames:
|
|
||||||
- "foo.com"
|
|
||||||
rules:
|
|
||||||
- matches:
|
|
||||||
- path:
|
|
||||||
type: PathPrefix
|
|
||||||
value: /bar
|
|
||||||
backendRefs:
|
|
||||||
- name: whoami
|
|
||||||
port: 80
|
|
||||||
weight: 1
|
|
|
@ -68,3 +68,14 @@ spec:
|
||||||
weight: 1
|
weight: 1
|
||||||
kind: Service
|
kind: Service
|
||||||
group: ""
|
group: ""
|
||||||
|
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: RegularExpression
|
||||||
|
value: "^/buzz/[0-9]+$"
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
---
|
|
||||||
kind: GatewayClass
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: my-gateway-class
|
|
||||||
spec:
|
|
||||||
controllerName: traefik.io/gateway-controller
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: Gateway
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: my-gateway
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
gatewayClassName: my-gateway-class
|
|
||||||
listeners: # Use GatewayClass defaults for listener definition.
|
|
||||||
- name: tls
|
|
||||||
protocol: TLS
|
|
||||||
port: 9001
|
|
||||||
tls:
|
|
||||||
mode: Passthrough
|
|
||||||
allowedRoutes:
|
|
||||||
kinds:
|
|
||||||
- kind: TLSRoute
|
|
||||||
group: gateway.networking.k8s.io
|
|
||||||
namespaces:
|
|
||||||
from: Same
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: TLSRoute
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
||||||
metadata:
|
|
||||||
name: tls-app-1
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: my-gateway
|
|
||||||
kind: Gateway
|
|
||||||
group: gateway.networking.k8s.io
|
|
||||||
hostnames:
|
|
||||||
- "*.foo.*.bar"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: whoamitcp
|
|
||||||
port: 9000
|
|
||||||
weight: 1
|
|
||||||
kind: Service
|
|
||||||
group: ""
|
|
575
pkg/provider/kubernetes/gateway/httproute.go
Normal file
575
pkg/provider/kubernetes/gateway/httproute.go
Normal file
|
@ -0,0 +1,575 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
ktypes "k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||||
|
routes, err := client.ListHTTPRoutes()
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("Unable to list HTTPRoutes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
logger := log.Ctx(ctx).With().
|
||||||
|
Str("http_route", route.Name).
|
||||||
|
Str("namespace", route.Namespace).
|
||||||
|
Logger()
|
||||||
|
|
||||||
|
var parentStatuses []gatev1.RouteParentStatus
|
||||||
|
for _, parentRef := range route.Spec.ParentRefs {
|
||||||
|
parentStatus := &gatev1.RouteParentStatus{
|
||||||
|
ParentRef: parentRef,
|
||||||
|
ControllerName: controllerName,
|
||||||
|
Conditions: []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionAccepted),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonAccepted),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var attachedListeners bool
|
||||||
|
notAcceptedReason := gatev1.RouteReasonNoMatchingParent
|
||||||
|
for _, listener := range gatewayListeners {
|
||||||
|
if !matchListener(listener, route.Namespace, parentRef) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRoute(listener, route.Namespace, kindHTTPRoute) {
|
||||||
|
notAcceptedReason = gatev1.RouteReasonNotAllowedByListeners
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hostnames, ok := findMatchingHostnames(listener.Hostname, route.Spec.Hostnames)
|
||||||
|
if !ok {
|
||||||
|
notAcceptedReason = gatev1.RouteReasonNoMatchingListenerHostname
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.Status.AttachedRoutes++
|
||||||
|
|
||||||
|
// TODO should we build the conf if the listener is not attached
|
||||||
|
// only consider the route attached if the listener is in an "attached" state.
|
||||||
|
if listener.Attached {
|
||||||
|
attachedListeners = true
|
||||||
|
}
|
||||||
|
resolveConditions := p.loadHTTPRoute(logger.WithContext(ctx), client, listener, route, hostnames, conf)
|
||||||
|
|
||||||
|
// TODO: handle more accurately route conditions (in case of multiple listener matching).
|
||||||
|
for _, condition := range resolveConditions {
|
||||||
|
parentStatus.Conditions = appendCondition(parentStatus.Conditions, condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachedListeners {
|
||||||
|
parentStatus.Conditions = []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionAccepted),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(notAcceptedReason),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentStatuses = append(parentStatuses, *parentStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
status := gatev1.HTTPRouteStatus{
|
||||||
|
RouteStatus: gatev1.RouteStatus{
|
||||||
|
Parents: parentStatuses,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.UpdateHTTPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, status); err != nil {
|
||||||
|
logger.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Unable to update HTTPRoute status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname, conf *dynamic.Configuration) []metav1.Condition {
|
||||||
|
routeConditions := []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hostRule := hostRule(hostnames)
|
||||||
|
|
||||||
|
for _, routeRule := range route.Spec.Rules {
|
||||||
|
router := dynamic.Router{
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Rule: routerRule(routeRule, hostRule),
|
||||||
|
EntryPoints: []string{listener.EPName},
|
||||||
|
}
|
||||||
|
if listener.Protocol == gatev1.HTTPSProtocolType {
|
||||||
|
router.TLS = &dynamic.RouterTLSConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
|
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
||||||
|
routerKey := makeRouterKey(router.Rule, makeID(route.Namespace, routerName))
|
||||||
|
|
||||||
|
var wrr dynamic.WeightedRoundRobin
|
||||||
|
wrrName := provider.Normalize(routerKey + "-wrr")
|
||||||
|
|
||||||
|
middlewares, err := p.loadMiddlewares(listener.Protocol, route.Namespace, routerKey, routeRule.Filters)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Unable to load HTTPRoute filters")
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||||
|
Name: "invalid-httproute-filter",
|
||||||
|
Status: ptr.To(500),
|
||||||
|
Weight: ptr.To(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
conf.HTTP.Services[wrrName] = &dynamic.Service{Weighted: &wrr}
|
||||||
|
router.Service = wrrName
|
||||||
|
} else {
|
||||||
|
for name, middleware := range middlewares {
|
||||||
|
// If the middleware config is nil in the return of the loadMiddlewares function,
|
||||||
|
// it means that we just need a reference to that middleware.
|
||||||
|
if middleware != nil {
|
||||||
|
conf.HTTP.Middlewares[name] = middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Middlewares = append(router.Middlewares, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traefik internal service can be used only if there is only one BackendRef service reference.
|
||||||
|
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef) {
|
||||||
|
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||||
|
} else {
|
||||||
|
for _, backendRef := range routeRule.BackendRefs {
|
||||||
|
name, svc, errCondition := p.loadHTTPService(client, route, backendRef)
|
||||||
|
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||||
|
if errCondition != nil {
|
||||||
|
routeConditions = appendCondition(routeConditions, *errCondition)
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||||
|
Name: name,
|
||||||
|
Status: ptr.To(500),
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc != nil {
|
||||||
|
conf.HTTP.Services[name] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||||
|
Name: name,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.HTTP.Services[wrrName] = &dynamic.Service{Weighted: &wrr}
|
||||||
|
router.Service = wrrName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rt := &router
|
||||||
|
p.applyRouterTransform(ctx, rt, route)
|
||||||
|
|
||||||
|
routerKey = provider.Normalize(routerKey)
|
||||||
|
conf.HTTP.Routers[routerKey] = rt
|
||||||
|
}
|
||||||
|
|
||||||
|
return routeConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadHTTPService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
|
||||||
|
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
|
||||||
|
func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
||||||
|
group := groupCore
|
||||||
|
if backendRef.Group != nil && *backendRef.Group != "" {
|
||||||
|
group = string(*backendRef.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := ptr.Deref(backendRef.Kind, "Service")
|
||||||
|
namespace := ptr.Deref(backendRef.Namespace, gatev1.Namespace(route.Namespace))
|
||||||
|
namespaceStr := string(namespace)
|
||||||
|
serviceName := provider.Normalize(makeID(namespaceStr, string(backendRef.Name)))
|
||||||
|
|
||||||
|
// TODO support cross namespace through ReferenceGrant.
|
||||||
|
if namespaceStr != route.Namespace {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonRefNotPermitted),
|
||||||
|
Message: fmt.Sprintf("Cannot load HTTPBackendRef %s/%s/%s/%s namespace not allowed", group, kind, namespace, backendRef.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if group != groupCore || kind != "Service" {
|
||||||
|
name, service, err := p.loadHTTPBackendRef(namespaceStr, backendRef)
|
||||||
|
if err != nil {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonInvalidKind),
|
||||||
|
Message: fmt.Sprintf("Cannot load HTTPBackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
port := ptr.Deref(backendRef.Port, gatev1.PortNumber(0))
|
||||||
|
if port == 0 {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonUnsupportedProtocol),
|
||||||
|
Message: fmt.Sprintf("Cannot load HTTPBackendRef %s/%s/%s/%s port is required", group, kind, namespace, backendRef.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
portStr := strconv.FormatInt(int64(port), 10)
|
||||||
|
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||||
|
|
||||||
|
lb, err := loadHTTPServers(client, namespaceStr, backendRef)
|
||||||
|
if err != nil {
|
||||||
|
return serviceName, nil, &metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonBackendNotFound),
|
||||||
|
Message: fmt.Sprintf("Cannot load HTTPBackendRef %s/%s/%s/%s: %s", group, kind, namespace, backendRef.Name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceName, &dynamic.Service{LoadBalancer: lb}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, error) {
|
||||||
|
// Support for cross-provider references (e.g: api@internal).
|
||||||
|
// This provides the same behavior as for IngressRoutes.
|
||||||
|
if *backendRef.Kind == "TraefikService" && strings.Contains(string(backendRef.Name), "@") {
|
||||||
|
return string(backendRef.Name), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
backendFunc, ok := p.groupKindBackendFuncs[string(*backendRef.Group)][string(*backendRef.Kind)]
|
||||||
|
if !ok {
|
||||||
|
return "", nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
|
}
|
||||||
|
if backendFunc == nil {
|
||||||
|
return "", nil, fmt.Errorf("undefined backendFunc for HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return backendFunc(string(backendRef.Name), namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadMiddlewares(listenerProtocol gatev1.ProtocolType, namespace, prefix string, filters []gatev1.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) {
|
||||||
|
middlewares := make(map[string]*dynamic.Middleware)
|
||||||
|
|
||||||
|
for i, filter := range filters {
|
||||||
|
switch filter.Type {
|
||||||
|
case gatev1.HTTPRouteFilterRequestRedirect:
|
||||||
|
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||||
|
middlewares[middlewareName] = createRedirectRegexMiddleware(listenerProtocol, filter.RequestRedirect)
|
||||||
|
|
||||||
|
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
||||||
|
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||||
|
middlewares[middlewareName] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||||
|
|
||||||
|
case gatev1.HTTPRouteFilterExtensionRef:
|
||||||
|
name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("loading ExtensionRef filter %s: %w", filter.Type, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
middlewares[name] = middleware
|
||||||
|
|
||||||
|
default:
|
||||||
|
// As per the spec: https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
|
||||||
|
// In all cases where incompatible or unsupported filters are
|
||||||
|
// specified, implementations MUST add a warning condition to
|
||||||
|
// status.
|
||||||
|
return nil, fmt.Errorf("unsupported filter %s", filter.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return middlewares, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRef *gatev1.LocalObjectReference) (string, *dynamic.Middleware, error) {
|
||||||
|
if extensionRef == nil {
|
||||||
|
return "", nil, errors.New("filter extension ref undefined")
|
||||||
|
}
|
||||||
|
|
||||||
|
filterFunc, ok := p.groupKindFilterFuncs[string(extensionRef.Group)][string(extensionRef.Kind)]
|
||||||
|
if !ok {
|
||||||
|
return "", nil, fmt.Errorf("unsupported filter extension ref %s/%s/%s", extensionRef.Group, extensionRef.Kind, extensionRef.Name)
|
||||||
|
}
|
||||||
|
if filterFunc == nil {
|
||||||
|
return "", nil, fmt.Errorf("undefined filterFunc for filter extension ref %s/%s/%s", extensionRef.Group, extensionRef.Kind, extensionRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterFunc(string(extensionRef.Name), namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO support cross namespace through ReferencePolicy.
|
||||||
|
func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
||||||
|
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting service: %w", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.New("service not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var portSpec corev1.ServicePort
|
||||||
|
var match bool
|
||||||
|
|
||||||
|
for _, p := range service.Spec.Ports {
|
||||||
|
if backendRef.Port == nil || p.Port == int32(*backendRef.Port) {
|
||||||
|
portSpec = p
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return nil, errors.New("service port not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints, endpointsExists, err := client.GetEndpoints(namespace, string(backendRef.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting endpoints: %w", err)
|
||||||
|
}
|
||||||
|
if !endpointsExists {
|
||||||
|
return nil, errors.New("endpoints not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(endpoints.Subsets) == 0 {
|
||||||
|
return nil, errors.New("subset not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
lb := &dynamic.ServersLoadBalancer{}
|
||||||
|
lb.SetDefaults()
|
||||||
|
|
||||||
|
var port int32
|
||||||
|
var portStr string
|
||||||
|
for _, subset := range endpoints.Subsets {
|
||||||
|
for _, p := range subset.Ports {
|
||||||
|
if portSpec.Name == p.Name {
|
||||||
|
port = p.Port
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
return nil, errors.New("cannot define a port")
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := getProtocol(portSpec)
|
||||||
|
|
||||||
|
portStr = strconv.FormatInt(int64(port), 10)
|
||||||
|
for _, addr := range subset.Addresses {
|
||||||
|
lb.Servers = append(lb.Servers, dynamic.Server{
|
||||||
|
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(addr.IP, portStr)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostRule(hostnames []gatev1.Hostname) string {
|
||||||
|
var rules []string
|
||||||
|
|
||||||
|
for _, hostname := range hostnames {
|
||||||
|
host := string(hostname)
|
||||||
|
|
||||||
|
wildcard := strings.Count(host, "*")
|
||||||
|
if wildcard == 0 {
|
||||||
|
rules = append(rules, fmt.Sprintf("Host(`%s`)", host))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-z0-9-\.]+\.`, 1)
|
||||||
|
rules = append(rules, fmt.Sprintf("HostRegexp(`^%s$`)", host))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(rules) {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case 1:
|
||||||
|
return rules[0]
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("(%s)", strings.Join(rules, " || "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func routerRule(routeRule gatev1.HTTPRouteRule, hostRule string) string {
|
||||||
|
var rule string
|
||||||
|
var matchesRules []string
|
||||||
|
|
||||||
|
for _, match := range routeRule.Matches {
|
||||||
|
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
|
Value: ptr.To("/"),
|
||||||
|
})
|
||||||
|
pathType := ptr.Deref(path.Type, gatev1.PathMatchPathPrefix)
|
||||||
|
pathValue := ptr.Deref(path.Value, "/")
|
||||||
|
|
||||||
|
var matchRules []string
|
||||||
|
switch pathType {
|
||||||
|
case gatev1.PathMatchExact:
|
||||||
|
matchRules = append(matchRules, fmt.Sprintf("Path(`%s`)", pathValue))
|
||||||
|
case gatev1.PathMatchPathPrefix:
|
||||||
|
matchRules = append(matchRules, buildPathMatchPathPrefixRule(pathValue))
|
||||||
|
case gatev1.PathMatchRegularExpression:
|
||||||
|
matchRules = append(matchRules, fmt.Sprintf("PathRegexp(`%s`)", pathValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
matchRules = append(matchRules, headerRules(match.Headers)...)
|
||||||
|
matchesRules = append(matchesRules, strings.Join(matchRules, " && "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no matches are specified, the default is a prefix
|
||||||
|
// path match on "/", which has the effect of matching every
|
||||||
|
// HTTP request.
|
||||||
|
if len(routeRule.Matches) == 0 {
|
||||||
|
matchesRules = append(matchesRules, "PathPrefix(`/`)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostRule != "" {
|
||||||
|
if len(matchesRules) == 0 {
|
||||||
|
return hostRule
|
||||||
|
}
|
||||||
|
rule += hostRule + " && "
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matchesRules) == 1 {
|
||||||
|
return rule + matchesRules[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule) == 0 {
|
||||||
|
return strings.Join(matchesRules, " || ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule + "(" + strings.Join(matchesRules, " || ") + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerRules(headers []gatev1.HTTPHeaderMatch) []string {
|
||||||
|
var headerRules []string
|
||||||
|
for _, header := range headers {
|
||||||
|
typ := ptr.Deref(header.Type, gatev1.HeaderMatchExact)
|
||||||
|
switch typ {
|
||||||
|
case gatev1.HeaderMatchExact:
|
||||||
|
headerRules = append(headerRules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||||
|
case gatev1.HeaderMatchRegularExpression:
|
||||||
|
headerRules = append(headerRules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headerRules
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPathMatchPathPrefixRule(path string) string {
|
||||||
|
if path == "/" {
|
||||||
|
return "PathPrefix(`/`)"
|
||||||
|
}
|
||||||
|
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRequestHeaderModifier does not enforce/check the configuration,
|
||||||
|
// as the spec indicates that either the webhook or CEL (since v1.0 GA Release) should enforce that.
|
||||||
|
func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middleware {
|
||||||
|
sets := map[string]string{}
|
||||||
|
for _, header := range filter.Set {
|
||||||
|
sets[string(header.Name)] = header.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
adds := map[string]string{}
|
||||||
|
for _, header := range filter.Add {
|
||||||
|
adds[string(header.Name)] = header.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dynamic.Middleware{
|
||||||
|
RequestHeaderModifier: &dynamic.RequestHeaderModifier{
|
||||||
|
Set: sets,
|
||||||
|
Add: adds,
|
||||||
|
Remove: filter.Remove,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRedirectRegexMiddleware(listenerProtocol gatev1.ProtocolType, filter *gatev1.HTTPRequestRedirectFilter) *dynamic.Middleware {
|
||||||
|
// The spec allows for an empty string in which case we should use the
|
||||||
|
// scheme of the request which in this case is the listener scheme.
|
||||||
|
filterScheme := ptr.Deref(filter.Scheme, strings.ToLower(string(listenerProtocol)))
|
||||||
|
statusCode := ptr.Deref(filter.StatusCode, http.StatusFound)
|
||||||
|
|
||||||
|
port := "${port}"
|
||||||
|
if filter.Port != nil {
|
||||||
|
port = fmt.Sprintf(":%d", *filter.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := "${hostname}"
|
||||||
|
if filter.Hostname != nil && *filter.Hostname != "" {
|
||||||
|
hostname = string(*filter.Hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dynamic.Middleware{
|
||||||
|
RedirectRegex: &dynamic.RedirectRegex{
|
||||||
|
Regex: `^[a-z]+:\/\/(?P<userInfo>.+@)?(?P<hostname>\[[\w:\.]+\]|[\w\._-]+)(?P<port>:\d+)?\/(?P<path>.*)`,
|
||||||
|
Replacement: fmt.Sprintf("%s://${userinfo}%s%s/${path}", filterScheme, hostname, port),
|
||||||
|
Permanent: statusCode == http.StatusMovedPermanently,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProtocol(portSpec corev1.ServicePort) string {
|
||||||
|
protocol := "http"
|
||||||
|
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol
|
||||||
|
}
|
281
pkg/provider/kubernetes/gateway/httproute_test.go
Normal file
281
pkg/provider/kubernetes/gateway/httproute_test.go
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_hostRule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
hostnames []gatev1.Hostname
|
||||||
|
expectedRule string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty rule and matches",
|
||||||
|
expectedRule: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One Host",
|
||||||
|
hostnames: []gatev1.Hostname{
|
||||||
|
"Foo",
|
||||||
|
},
|
||||||
|
expectedRule: "Host(`Foo`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Multiple Hosts",
|
||||||
|
hostnames: []gatev1.Hostname{
|
||||||
|
"Foo",
|
||||||
|
"Bar",
|
||||||
|
"Bir",
|
||||||
|
},
|
||||||
|
expectedRule: "(Host(`Foo`) || Host(`Bar`) || Host(`Bir`))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Several Host and wildcard",
|
||||||
|
hostnames: []gatev1.Hostname{
|
||||||
|
"*.bar.foo",
|
||||||
|
"bar.foo",
|
||||||
|
"foo.foo",
|
||||||
|
},
|
||||||
|
expectedRule: "(HostRegexp(`^[a-z0-9-\\.]+\\.bar\\.foo$`) || Host(`bar.foo`) || Host(`foo.foo`))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Host with wildcard",
|
||||||
|
hostnames: []gatev1.Hostname{
|
||||||
|
"*.bar.foo",
|
||||||
|
},
|
||||||
|
expectedRule: "HostRegexp(`^[a-z0-9-\\.]+\\.bar\\.foo$`)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rule := hostRule(test.hostnames)
|
||||||
|
assert.Equal(t, test.expectedRule, rule)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_routerRule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
routeRule gatev1.HTTPRouteRule
|
||||||
|
hostRule string
|
||||||
|
expectedRule string
|
||||||
|
expectedError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty rule and matches",
|
||||||
|
expectedRule: "PathPrefix(`/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One Host rule without matches",
|
||||||
|
hostRule: "Host(`foo.com`)",
|
||||||
|
expectedRule: "Host(`foo.com`) && PathPrefix(`/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
|
Value: ptr.To("/"),
|
||||||
|
}),
|
||||||
|
Headers: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "PathPrefix(`/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch Type",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
|
Value: ptr.To("/"),
|
||||||
|
}),
|
||||||
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
|
{Name: "foo", Value: "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "PathPrefix(`/`) && Header(`foo`,`bar`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{Path: nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "PathPrefix(`/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One HTTPRouteMatch with nil HTTPPathMatch Type",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: nil,
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "(Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One HTTPRouteMatch with nil HTTPPathMatch Values",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Path(`/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One Path in matches",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Path(`/foo/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One Path in matches and another empty",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Path(`/foo/`) || PathPrefix(`/`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Path OR Header rules",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||||
|
Name: "my-header",
|
||||||
|
Value: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Path(`/foo/`) || PathPrefix(`/`) && Header(`my-header`,`foo`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Path && Header rules",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||||
|
Name: "my-header",
|
||||||
|
Value: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Host && Path && Header rules",
|
||||||
|
hostRule: "Host(`foo.com`)",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||||
|
Name: "my-header",
|
||||||
|
Value: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Host && (Path || Header) rules",
|
||||||
|
hostRule: "Host(`foo.com`)",
|
||||||
|
routeRule: gatev1.HTTPRouteRule{
|
||||||
|
Matches: []gatev1.HTTPRouteMatch{
|
||||||
|
{
|
||||||
|
Path: &gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
|
Value: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||||
|
Name: "my-header",
|
||||||
|
Value: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || PathPrefix(`/`) && Header(`my-header`,`foo`))",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rule := routerRule(test.routeRule, test.hostRule)
|
||||||
|
assert.Equal(t, test.expectedRule, rule)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
296
pkg/provider/kubernetes/gateway/tcproute.go
Normal file
296
pkg/provider/kubernetes/gateway/tcproute.go
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
ktypes "k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) loadTCPRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||||
|
logger := log.Ctx(ctx)
|
||||||
|
routes, err := client.ListTCPRoutes()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msgf("Get TCPRoutes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
logger := log.Ctx(ctx).With().Str("tcproute", route.Name).Str("namespace", route.Namespace).Logger()
|
||||||
|
|
||||||
|
var parentStatuses []gatev1alpha2.RouteParentStatus
|
||||||
|
for _, parentRef := range route.Spec.ParentRefs {
|
||||||
|
parentStatus := &gatev1alpha2.RouteParentStatus{
|
||||||
|
ParentRef: parentRef,
|
||||||
|
ControllerName: controllerName,
|
||||||
|
Conditions: []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionAccepted),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonAccepted),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var attachedListeners bool
|
||||||
|
for _, listener := range gatewayListeners {
|
||||||
|
if !matchListener(listener, route.Namespace, parentRef) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRoute(listener, route.Namespace, kindTCPRoute) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.Status.AttachedRoutes++
|
||||||
|
attachedListeners = true
|
||||||
|
|
||||||
|
resolveConditions := p.loadTCPRoute(client, listener, route, conf)
|
||||||
|
|
||||||
|
// TODO: handle more accurately route conditions (in case of multiple listener matching).
|
||||||
|
for _, condition := range resolveConditions {
|
||||||
|
parentStatus.Conditions = appendCondition(parentStatus.Conditions, condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachedListeners {
|
||||||
|
parentStatus.Conditions = []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionAccepted),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonNoMatchingParent),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentStatuses = append(parentStatuses, *parentStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
routeStatus := gatev1alpha2.TCPRouteStatus{
|
||||||
|
RouteStatus: gatev1alpha2.RouteStatus{
|
||||||
|
Parents: parentStatuses,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.UpdateTCPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, routeStatus); err != nil {
|
||||||
|
logger.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Unable to update TCPRoute status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *gatev1alpha2.TCPRoute, conf *dynamic.Configuration) []metav1.Condition {
|
||||||
|
routeConditions := []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
router := dynamic.TCPRouter{
|
||||||
|
Rule: "HostSNI(`*`)",
|
||||||
|
EntryPoints: []string{listener.EPName},
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
}
|
||||||
|
|
||||||
|
if listener.Protocol == gatev1.TLSProtocolType && listener.TLS != nil {
|
||||||
|
// TODO support let's encrypt
|
||||||
|
router.TLS = &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
|
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
||||||
|
routerKey := provider.Normalize(makeRouterKey("", makeID(route.Namespace, routerName)))
|
||||||
|
|
||||||
|
var ruleServiceNames []string
|
||||||
|
for i, rule := range route.Spec.Rules {
|
||||||
|
if rule.BackendRefs == nil {
|
||||||
|
// Should not happen due to validation.
|
||||||
|
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tcproute_types.go#L76
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wrrService, subServices, err := loadTCPServices(client, route.Namespace, rule.BackendRefs)
|
||||||
|
if err != nil {
|
||||||
|
routeConditions = appendCondition(routeConditions, metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonBackendNotFound),
|
||||||
|
Message: fmt.Sprintf("Cannot load TCPRoute service %s/%s: %v", route.Namespace, route.Name, err),
|
||||||
|
})
|
||||||
|
return routeConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcName, svc := range subServices {
|
||||||
|
conf.TCP.Services[svcName] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i)
|
||||||
|
conf.TCP.Services[serviceName] = wrrService
|
||||||
|
|
||||||
|
ruleServiceNames = append(ruleServiceNames, serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ruleServiceNames) == 1 {
|
||||||
|
router.Service = ruleServiceNames[0]
|
||||||
|
conf.TCP.Routers[routerKey] = &router
|
||||||
|
return routeConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
routeServiceKey := routerKey + "-wrr"
|
||||||
|
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
||||||
|
|
||||||
|
for _, name := range ruleServiceNames {
|
||||||
|
service := dynamic.TCPWRRService{Name: name}
|
||||||
|
service.SetDefaults()
|
||||||
|
|
||||||
|
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TCP.Services[routeServiceKey] = routeService
|
||||||
|
|
||||||
|
router.Service = routeServiceKey
|
||||||
|
conf.TCP.Routers[routerKey] = &router
|
||||||
|
|
||||||
|
return routeConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTCPServices is generating a WRR service, even when there is only one target.
|
||||||
|
func loadTCPServices(client Client, namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) {
|
||||||
|
services := map[string]*dynamic.TCPService{}
|
||||||
|
|
||||||
|
wrrSvc := &dynamic.TCPService{
|
||||||
|
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||||
|
Services: []dynamic.TCPWRRService{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, backendRef := range backendRefs {
|
||||||
|
if backendRef.Group == nil || backendRef.Kind == nil {
|
||||||
|
// Should not happen as this is validated by kubernetes
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isInternalService(backendRef) {
|
||||||
|
return nil, nil, fmt.Errorf("traefik internal service %s is not allowed in a WRR loadbalancer", backendRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
weight := int(ptr.Deref(backendRef.Weight, 1))
|
||||||
|
|
||||||
|
if isTraefikService(backendRef) {
|
||||||
|
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: string(backendRef.Name), Weight: &weight})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
|
||||||
|
return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||||
|
}
|
||||||
|
|
||||||
|
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, nil, errors.New("service not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(service.Spec.Ports) > 1 && backendRef.Port == nil {
|
||||||
|
// If the port is unspecified and the backend is a Service
|
||||||
|
// object consisting of multiple port definitions, the route
|
||||||
|
// must be dropped from the Gateway. The controller should
|
||||||
|
// raise the "ResolvedRefs" condition on the Gateway with the
|
||||||
|
// "DroppedRoutes" reason. The gateway status for this route
|
||||||
|
// should be updated with a condition that describes the error
|
||||||
|
// more specifically.
|
||||||
|
log.Error().Msg("A multiple ports Kubernetes Service cannot be used if unspecified backendRef.Port")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var portSpec corev1.ServicePort
|
||||||
|
var match bool
|
||||||
|
|
||||||
|
for _, p := range service.Spec.Ports {
|
||||||
|
if backendRef.Port == nil || p.Port == int32(*backendRef.Port) {
|
||||||
|
portSpec = p
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
return nil, nil, errors.New("service port not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, string(backendRef.Name))
|
||||||
|
if endpointsErr != nil {
|
||||||
|
return nil, nil, endpointsErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if !endpointsExists {
|
||||||
|
return nil, nil, errors.New("endpoints not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(endpoints.Subsets) == 0 {
|
||||||
|
return nil, nil, errors.New("subset not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var port int32
|
||||||
|
var portStr string
|
||||||
|
for _, subset := range endpoints.Subsets {
|
||||||
|
for _, p := range subset.Ports {
|
||||||
|
if portSpec.Name == p.Name {
|
||||||
|
port = p.Port
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
return nil, nil, errors.New("cannot define a port")
|
||||||
|
}
|
||||||
|
|
||||||
|
portStr = strconv.FormatInt(int64(port), 10)
|
||||||
|
for _, addr := range subset.Addresses {
|
||||||
|
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.TCPServer{
|
||||||
|
Address: net.JoinHostPort(addr.IP, portStr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := provider.Normalize(makeID(service.Namespace, service.Name) + "-" + portStr)
|
||||||
|
services[serviceName] = &svc
|
||||||
|
|
||||||
|
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wrrSvc.Weighted.Services) == 0 {
|
||||||
|
return nil, nil, errors.New("no service has been created")
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrrSvc, services, nil
|
||||||
|
}
|
210
pkg/provider/kubernetes/gateway/tlsroute.go
Normal file
210
pkg/provider/kubernetes/gateway/tlsroute.go
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/provider"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
ktypes "k8s.io/apimachinery/pkg/types"
|
||||||
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) loadTLSRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||||
|
logger := log.Ctx(ctx)
|
||||||
|
routes, err := client.ListTLSRoutes()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msgf("Get TLSRoutes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
logger := log.Ctx(ctx).With().Str("tlsroute", route.Name).Str("namespace", route.Namespace).Logger()
|
||||||
|
|
||||||
|
var parentStatuses []gatev1alpha2.RouteParentStatus
|
||||||
|
for _, parentRef := range route.Spec.ParentRefs {
|
||||||
|
parentStatus := &gatev1alpha2.RouteParentStatus{
|
||||||
|
ParentRef: parentRef,
|
||||||
|
ControllerName: controllerName,
|
||||||
|
Conditions: []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionAccepted),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonAccepted),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var attachedListeners bool
|
||||||
|
for _, listener := range gatewayListeners {
|
||||||
|
if !matchListener(listener, route.Namespace, parentRef) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRoute(listener, route.Namespace, kindTLSRoute) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hostnames, ok := findMatchingHostnames(listener.Hostname, route.Spec.Hostnames)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.Status.AttachedRoutes++
|
||||||
|
attachedListeners = true
|
||||||
|
|
||||||
|
resolveConditions := p.loadTLSRoute(client, listener, route, hostnames, conf)
|
||||||
|
|
||||||
|
// TODO: handle more accurately route conditions (in case of multiple listener matching).
|
||||||
|
for _, condition := range resolveConditions {
|
||||||
|
parentStatus.Conditions = appendCondition(parentStatus.Conditions, condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachedListeners {
|
||||||
|
parentStatus.Conditions = []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionAccepted),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonNoMatchingParent),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentStatuses = append(parentStatuses, *parentStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
routeStatus := gatev1alpha2.TLSRouteStatus{
|
||||||
|
RouteStatus: gatev1alpha2.RouteStatus{
|
||||||
|
Parents: parentStatuses,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.UpdateTLSRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, routeStatus); err != nil {
|
||||||
|
logger.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Unable to update TLSRoute status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *gatev1alpha2.TLSRoute, hostnames []gatev1.Hostname, conf *dynamic.Configuration) []metav1.Condition {
|
||||||
|
routeConditions := []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
router := dynamic.TCPRouter{
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Rule: hostSNIRule(hostnames),
|
||||||
|
EntryPoints: []string{listener.EPName},
|
||||||
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: listener.TLS.Mode != nil && *listener.TLS.Mode == gatev1.TLSModePassthrough,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
|
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
||||||
|
routerKey := provider.Normalize(makeRouterKey(router.Rule, makeID(route.Namespace, routerName)))
|
||||||
|
|
||||||
|
var ruleServiceNames []string
|
||||||
|
for i, routeRule := range route.Spec.Rules {
|
||||||
|
if len(routeRule.BackendRefs) == 0 {
|
||||||
|
// Should not happen due to validation.
|
||||||
|
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tlsroute_types.go#L120
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs)
|
||||||
|
if err != nil {
|
||||||
|
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
|
||||||
|
routeConditions = appendCondition(routeConditions, metav1.Condition{
|
||||||
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
Status: metav1.ConditionFalse,
|
||||||
|
ObservedGeneration: route.Generation,
|
||||||
|
LastTransitionTime: metav1.Now(),
|
||||||
|
Reason: string(gatev1.RouteReasonBackendNotFound),
|
||||||
|
Message: fmt.Sprintf("Cannot load TLSRoute service %s/%s: %v", route.Namespace, route.Name, err),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for svcName, svc := range subServices {
|
||||||
|
conf.TCP.Services[svcName] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i)
|
||||||
|
conf.TCP.Services[serviceName] = wrrService
|
||||||
|
|
||||||
|
ruleServiceNames = append(ruleServiceNames, serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ruleServiceNames) == 1 {
|
||||||
|
router.Service = ruleServiceNames[0]
|
||||||
|
conf.TCP.Routers[routerKey] = &router
|
||||||
|
|
||||||
|
return routeConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
routeServiceKey := routerKey + "-wrr"
|
||||||
|
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
||||||
|
|
||||||
|
for _, name := range ruleServiceNames {
|
||||||
|
service := dynamic.TCPWRRService{Name: name}
|
||||||
|
service.SetDefaults()
|
||||||
|
|
||||||
|
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TCP.Services[routeServiceKey] = routeService
|
||||||
|
|
||||||
|
router.Service = routeServiceKey
|
||||||
|
conf.TCP.Routers[routerKey] = &router
|
||||||
|
|
||||||
|
return routeConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostSNIRule(hostnames []gatev1.Hostname) string {
|
||||||
|
rules := make([]string, 0, len(hostnames))
|
||||||
|
uniqHostnames := map[gatev1.Hostname]struct{}{}
|
||||||
|
|
||||||
|
for _, hostname := range hostnames {
|
||||||
|
if len(hostname) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := uniqHostnames[hostname]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
host := string(hostname)
|
||||||
|
uniqHostnames[hostname] = struct{}{}
|
||||||
|
|
||||||
|
wildcard := strings.Count(host, "*")
|
||||||
|
if wildcard == 0 {
|
||||||
|
rules = append(rules, fmt.Sprintf("HostSNI(`%s`)", host))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-z0-9-\.]+\.`, 1)
|
||||||
|
rules = append(rules, fmt.Sprintf("HostSNIRegexp(`^%s$`)", host))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hostnames) == 0 || len(rules) == 0 {
|
||||||
|
return "HostSNI(`*`)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(rules, " || ")
|
||||||
|
}
|
66
pkg/provider/kubernetes/gateway/tlsroute_test.go
Normal file
66
pkg/provider/kubernetes/gateway/tlsroute_test.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_hostSNIRule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
hostnames []gatev1.Hostname
|
||||||
|
expectedRule string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty",
|
||||||
|
expectedRule: "HostSNI(`*`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Empty hostname",
|
||||||
|
hostnames: []gatev1.Hostname{""},
|
||||||
|
expectedRule: "HostSNI(`*`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Supported wildcard",
|
||||||
|
hostnames: []gatev1.Hostname{"*.foo"},
|
||||||
|
expectedRule: "HostSNIRegexp(`^[a-z0-9-\\.]+\\.foo$`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Some empty hostnames",
|
||||||
|
hostnames: []gatev1.Hostname{"foo", "", "bar"},
|
||||||
|
expectedRule: "HostSNI(`foo`) || HostSNI(`bar`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Valid hostname",
|
||||||
|
hostnames: []gatev1.Hostname{"foo"},
|
||||||
|
expectedRule: "HostSNI(`foo`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Multiple valid hostnames",
|
||||||
|
hostnames: []gatev1.Hostname{"foo", "bar"},
|
||||||
|
expectedRule: "HostSNI(`foo`) || HostSNI(`bar`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Multiple valid hostnames with wildcard",
|
||||||
|
hostnames: []gatev1.Hostname{"bar.foo", "foo.foo", "*.foo"},
|
||||||
|
expectedRule: "HostSNI(`bar.foo`) || HostSNI(`foo.foo`) || HostSNIRegexp(`^[a-z0-9-\\.]+\\.foo$`)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Multiple overlapping hostnames",
|
||||||
|
hostnames: []gatev1.Hostname{"foo", "bar", "foo", "baz"},
|
||||||
|
expectedRule: "HostSNI(`foo`) || HostSNI(`bar`) || HostSNI(`baz`)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rule := hostSNIRule(test.hostnames)
|
||||||
|
assert.Equal(t, test.expectedRule, rule)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ type ServiceIng struct {
|
||||||
ServersTransport string `json:"serversTransport,omitempty"`
|
ServersTransport string `json:"serversTransport,omitempty"`
|
||||||
PassHostHeader *bool `json:"passHostHeader"`
|
PassHostHeader *bool `json:"passHostHeader"`
|
||||||
Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"`
|
Sticky *dynamic.Sticky `json:"sticky,omitempty" label:"allowEmpty"`
|
||||||
NativeLB bool `json:"nativeLB,omitempty"`
|
NativeLB *bool `json:"nativeLB,omitempty"`
|
||||||
NodePortLB bool `json:"nodePortLB,omitempty"`
|
NodePortLB bool `json:"nodePortLB,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ func Test_parseServiceConfig(t *testing.T) {
|
||||||
ServersScheme: "protocol",
|
ServersScheme: "protocol",
|
||||||
ServersTransport: "foobar@file",
|
ServersTransport: "foobar@file",
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
NativeLB: true,
|
NativeLB: Bool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
kind: Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: global-native-lb
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: traefik.tchouk
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: service1
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
pathType: Prefix
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: service1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
clusterIP: 10.0.0.1
|
||||||
|
type: ClusterIP
|
||||||
|
externalName: traefik.wtf
|
|
@ -52,6 +52,7 @@ type Provider struct {
|
||||||
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||||
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
|
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
|
||||||
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"`
|
DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"`
|
||||||
|
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
|
||||||
|
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.AllowExternalNameServices {
|
if p.AllowExternalNameServices {
|
||||||
logger.Warn().Msg("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
logger.Info().Msg("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.GoCtx(func(ctxPool context.Context) {
|
pool.GoCtx(func(ctxPool context.Context) {
|
||||||
|
@ -277,6 +278,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
|
|
||||||
rt := &dynamic.Router{
|
rt := &dynamic.Router{
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
Priority: math.MinInt32,
|
Priority: math.MinInt32,
|
||||||
Service: "default-backend",
|
Service: "default-backend",
|
||||||
}
|
}
|
||||||
|
@ -571,6 +573,8 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nativeLB := p.NativeLBByDefault
|
||||||
|
|
||||||
if svcConfig != nil && svcConfig.Service != nil {
|
if svcConfig != nil && svcConfig.Service != nil {
|
||||||
svc.LoadBalancer.Sticky = svcConfig.Service.Sticky
|
svc.LoadBalancer.Sticky = svcConfig.Service.Sticky
|
||||||
|
|
||||||
|
@ -582,19 +586,8 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
|
||||||
svc.LoadBalancer.ServersTransport = svcConfig.Service.ServersTransport
|
svc.LoadBalancer.ServersTransport = svcConfig.Service.ServersTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
if svcConfig.Service.NativeLB {
|
if svcConfig.Service.NativeLB != nil {
|
||||||
address, err := getNativeServiceAddress(*service, portSpec)
|
nativeLB = *svcConfig.Service.NativeLB
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
|
|
||||||
|
|
||||||
svc.LoadBalancer.Servers = []dynamic.Server{
|
|
||||||
{URL: fmt.Sprintf("%s://%s", protocol, address)},
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if svcConfig.Service.NodePortLB && service.Spec.Type == corev1.ServiceTypeNodePort {
|
if svcConfig.Service.NodePortLB && service.Spec.Type == corev1.ServiceTypeNodePort {
|
||||||
|
@ -644,6 +637,20 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nativeLB {
|
||||||
|
address, err := getNativeServiceAddress(*service, portSpec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting native Kubernetes Service address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
|
||||||
|
svc.LoadBalancer.Servers = []dynamic.Server{
|
||||||
|
{URL: fmt.Sprintf("%s://%s", protocol, address)},
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc, nil
|
||||||
|
}
|
||||||
|
|
||||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name)
|
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name)
|
||||||
if endpointsErr != nil {
|
if endpointsErr != nil {
|
||||||
return nil, endpointsErr
|
return nil, endpointsErr
|
||||||
|
|
|
@ -529,6 +529,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
Routers: map[string]*dynamic.Router{
|
Routers: map[string]*dynamic.Router{
|
||||||
"default-router": {
|
"default-router": {
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
Service: "default-backend",
|
Service: "default-backend",
|
||||||
Priority: math.MinInt32,
|
Priority: math.MinInt32,
|
||||||
},
|
},
|
||||||
|
@ -994,6 +995,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
Routers: map[string]*dynamic.Router{
|
Routers: map[string]*dynamic.Router{
|
||||||
"default-router": {
|
"default-router": {
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
Service: "default-backend",
|
Service: "default-backend",
|
||||||
Priority: math.MinInt32,
|
Priority: math.MinInt32,
|
||||||
},
|
},
|
||||||
|
@ -1470,6 +1472,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
Routers: map[string]*dynamic.Router{
|
Routers: map[string]*dynamic.Router{
|
||||||
"default-router": {
|
"default-router": {
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
Priority: math.MinInt32,
|
Priority: math.MinInt32,
|
||||||
Service: "default-backend",
|
Service: "default-backend",
|
||||||
},
|
},
|
||||||
|
@ -1914,3 +1917,84 @@ func TestGetCertificates(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
ingressClass string
|
||||||
|
expected *dynamic.Configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Ingress with native service lb",
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"testing-traefik-tchouk-bar": {
|
||||||
|
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||||
|
Service: "testing-service1-8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"testing-service1-8080": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.0.0.1:8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Ingress with native lb by default",
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-global-native-lb-traefik-tchouk-bar": {
|
||||||
|
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||||
|
Service: "default-service1-8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-service1-8080": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.0.0.1:8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
clientMock := newClientMock(generateTestFilename(test.desc))
|
||||||
|
|
||||||
|
p := Provider{
|
||||||
|
IngressClass: test.ingressClass,
|
||||||
|
NativeLBByDefault: true,
|
||||||
|
}
|
||||||
|
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, conf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806
|
"priority": 9223372036854775806
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805
|
"priority": 9223372036854775805
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806
|
"priority": 9223372036854775806
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806
|
"priority": 9223372036854775806
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
],
|
],
|
||||||
"service": "dashboard@internal",
|
"service": "dashboard@internal",
|
||||||
"rule": "PathPrefix(`/`)",
|
"rule": "PathPrefix(`/`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775805
|
"priority": 9223372036854775805
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
|
@ -27,6 +29,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/debug`)",
|
"rule": "PathPrefix(`/debug`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775806
|
"priority": 9223372036854775806
|
||||||
},
|
},
|
||||||
"ping": {
|
"ping": {
|
||||||
|
@ -35,6 +38,7 @@
|
||||||
],
|
],
|
||||||
"service": "ping@internal",
|
"service": "ping@internal",
|
||||||
"rule": "PathPrefix(`/ping`)",
|
"rule": "PathPrefix(`/ping`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775807
|
"priority": 9223372036854775807
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
|
@ -43,6 +47,7 @@
|
||||||
],
|
],
|
||||||
"service": "prometheus@internal",
|
"service": "prometheus@internal",
|
||||||
"rule": "PathPrefix(`/metrics`)",
|
"rule": "PathPrefix(`/metrics`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775807
|
"priority": 9223372036854775807
|
||||||
},
|
},
|
||||||
"rest": {
|
"rest": {
|
||||||
|
@ -51,6 +56,7 @@
|
||||||
],
|
],
|
||||||
"service": "rest@internal",
|
"service": "rest@internal",
|
||||||
"rule": "PathPrefix(`/api/providers`)",
|
"rule": "PathPrefix(`/api/providers`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775807
|
"priority": 9223372036854775807
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"service": "ping@internal",
|
"service": "ping@internal",
|
||||||
"rule": "PathPrefix(`/ping`)",
|
"rule": "PathPrefix(`/ping`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775807
|
"priority": 9223372036854775807
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"service": "prometheus@internal",
|
"service": "prometheus@internal",
|
||||||
"rule": "PathPrefix(`/metrics`)",
|
"rule": "PathPrefix(`/metrics`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775807
|
"priority": 9223372036854775807
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"redirect-web-to-websecure"
|
"redirect-web-to-websecure"
|
||||||
],
|
],
|
||||||
"service": "noop@internal",
|
"service": "noop@internal",
|
||||||
"rule": "HostRegexp(`^.+$`)"
|
"rule": "HostRegexp(`^.+$`)",
|
||||||
|
"ruleSyntax": "v3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"redirect-web-to-443"
|
"redirect-web-to-443"
|
||||||
],
|
],
|
||||||
"service": "noop@internal",
|
"service": "noop@internal",
|
||||||
"rule": "HostRegexp(`^.+$`)"
|
"rule": "HostRegexp(`^.+$`)",
|
||||||
|
"ruleSyntax": "v3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"redirect-web-to-websecure"
|
"redirect-web-to-websecure"
|
||||||
],
|
],
|
||||||
"service": "noop@internal",
|
"service": "noop@internal",
|
||||||
"rule": "HostRegexp(`^.+$`)"
|
"rule": "HostRegexp(`^.+$`)",
|
||||||
|
"ruleSyntax": "v3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
],
|
],
|
||||||
"service": "rest@internal",
|
"service": "rest@internal",
|
||||||
"rule": "PathPrefix(`/api/providers`)",
|
"rule": "PathPrefix(`/api/providers`)",
|
||||||
|
"ruleSyntax": "v3",
|
||||||
"priority": 9223372036854775807
|
"priority": 9223372036854775807
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -106,6 +106,7 @@ func (i *Provider) acme(cfg *dynamic.Configuration) {
|
||||||
if len(eps) > 0 {
|
if len(eps) > 0 {
|
||||||
rt := &dynamic.Router{
|
rt := &dynamic.Router{
|
||||||
Rule: "PathPrefix(`/.well-known/acme-challenge/`)",
|
Rule: "PathPrefix(`/.well-known/acme-challenge/`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
EntryPoints: eps,
|
EntryPoints: eps,
|
||||||
Service: "acme-http@internal",
|
Service: "acme-http@internal",
|
||||||
Priority: math.MaxInt,
|
Priority: math.MaxInt,
|
||||||
|
@ -141,6 +142,7 @@ func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration)
|
||||||
|
|
||||||
rt := &dynamic.Router{
|
rt := &dynamic.Router{
|
||||||
Rule: "HostRegexp(`^.+$`)",
|
Rule: "HostRegexp(`^.+$`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
EntryPoints: []string{name},
|
EntryPoints: []string{name},
|
||||||
Middlewares: []string{mdName},
|
Middlewares: []string{mdName},
|
||||||
Service: "noop@internal",
|
Service: "noop@internal",
|
||||||
|
@ -241,6 +243,7 @@ func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
|
||||||
Service: "api@internal",
|
Service: "api@internal",
|
||||||
Priority: math.MaxInt - 1,
|
Priority: math.MaxInt - 1,
|
||||||
Rule: "PathPrefix(`/api`)",
|
Rule: "PathPrefix(`/api`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.staticCfg.API.Dashboard {
|
if i.staticCfg.API.Dashboard {
|
||||||
|
@ -249,6 +252,7 @@ func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
|
||||||
Service: "dashboard@internal",
|
Service: "dashboard@internal",
|
||||||
Priority: math.MaxInt - 2,
|
Priority: math.MaxInt - 2,
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
Middlewares: []string{"dashboard_redirect@internal", "dashboard_stripprefix@internal"},
|
Middlewares: []string{"dashboard_redirect@internal", "dashboard_stripprefix@internal"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +274,7 @@ func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
|
||||||
Service: "api@internal",
|
Service: "api@internal",
|
||||||
Priority: math.MaxInt - 1,
|
Priority: math.MaxInt - 1,
|
||||||
Rule: "PathPrefix(`/debug`)",
|
Rule: "PathPrefix(`/debug`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,6 +297,7 @@ func (i *Provider) pingConfiguration(cfg *dynamic.Configuration) {
|
||||||
Service: "ping@internal",
|
Service: "ping@internal",
|
||||||
Priority: math.MaxInt,
|
Priority: math.MaxInt,
|
||||||
Rule: "PathPrefix(`/ping`)",
|
Rule: "PathPrefix(`/ping`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,6 +315,7 @@ func (i *Provider) restConfiguration(cfg *dynamic.Configuration) {
|
||||||
Service: "rest@internal",
|
Service: "rest@internal",
|
||||||
Priority: math.MaxInt,
|
Priority: math.MaxInt,
|
||||||
Rule: "PathPrefix(`/api/providers`)",
|
Rule: "PathPrefix(`/api/providers`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +333,7 @@ func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) {
|
||||||
Service: "prometheus@internal",
|
Service: "prometheus@internal",
|
||||||
Priority: math.MaxInt,
|
Priority: math.MaxInt,
|
||||||
Rule: "PathPrefix(`/metrics`)",
|
Rule: "PathPrefix(`/metrics`)",
|
||||||
|
RuleSyntax: "v3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,9 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
||||||
for serviceName, service := range configuration.TCP.Services {
|
for serviceName, service := range configuration.TCP.Services {
|
||||||
conf.TCP.Services[provider.MakeQualifiedName(pvd, serviceName)] = service
|
conf.TCP.Services[provider.MakeQualifiedName(pvd, serviceName)] = service
|
||||||
}
|
}
|
||||||
|
for modelName, model := range configuration.TCP.Models {
|
||||||
|
conf.TCP.Models[provider.MakeQualifiedName(pvd, modelName)] = model
|
||||||
|
}
|
||||||
for serversTransportName, serversTransport := range configuration.TCP.ServersTransports {
|
for serversTransportName, serversTransport := range configuration.TCP.ServersTransports {
|
||||||
conf.TCP.ServersTransports[provider.MakeQualifiedName(pvd, serversTransportName)] = serversTransport
|
conf.TCP.ServersTransports[provider.MakeQualifiedName(pvd, serversTransportName)] = serversTransport
|
||||||
}
|
}
|
||||||
|
@ -146,10 +149,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
if cfg.HTTP == nil || len(cfg.HTTP.Models) == 0 {
|
if cfg.HTTP != nil && len(cfg.HTTP.Models) > 0 {
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
rts := make(map[string]*dynamic.Router)
|
rts := make(map[string]*dynamic.Router)
|
||||||
|
|
||||||
for name, rt := range cfg.HTTP.Routers {
|
for name, rt := range cfg.HTTP.Routers {
|
||||||
|
@ -192,6 +192,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.HTTP.Routers = rts
|
cfg.HTTP.Routers = rts
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
|
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
|
||||||
return cfg
|
return cfg
|
||||||
|
@ -199,7 +200,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
|
|
||||||
tcpRouters := make(map[string]*dynamic.TCPRouter)
|
tcpRouters := make(map[string]*dynamic.TCPRouter)
|
||||||
|
|
||||||
for _, rt := range cfg.TCP.Routers {
|
for name, rt := range cfg.TCP.Routers {
|
||||||
router := rt.DeepCopy()
|
router := rt.DeepCopy()
|
||||||
|
|
||||||
if router.RuleSyntax == "" {
|
if router.RuleSyntax == "" {
|
||||||
|
@ -208,6 +209,8 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tcpRouters[name] = router
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.TCP.Routers = tcpRouters
|
cfg.TCP.Routers = tcpRouters
|
||||||
|
|
|
@ -656,6 +656,50 @@ func Test_applyModel(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "with TCP model, two entry points",
|
||||||
|
input: dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"test": {
|
||||||
|
EntryPoints: []string{"websecure", "web"},
|
||||||
|
},
|
||||||
|
"test2": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
RuleSyntax: "barfoo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||||
|
Services: make(map[string]*dynamic.TCPService),
|
||||||
|
Models: map[string]*dynamic.TCPModel{
|
||||||
|
"websecure@internal": {
|
||||||
|
DefaultRuleSyntax: "foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: dynamic.Configuration{
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"test": {
|
||||||
|
EntryPoints: []string{"websecure", "web"},
|
||||||
|
RuleSyntax: "foobar",
|
||||||
|
},
|
||||||
|
"test2": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
RuleSyntax: "barfoo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||||
|
Services: make(map[string]*dynamic.TCPService),
|
||||||
|
Models: map[string]*dynamic.TCPModel{
|
||||||
|
"websecure@internal": {
|
||||||
|
DefaultRuleSyntax: "foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -123,6 +123,11 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if postgres {
|
if postgres {
|
||||||
|
// Remove read/write deadline and delegate this to underlying TCP server.
|
||||||
|
if err := conn.SetDeadline(time.Time{}); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error while setting deadline")
|
||||||
|
}
|
||||||
|
|
||||||
r.servePostgres(r.GetConn(conn, getPeeked(br)))
|
r.servePostgres(r.GetConn(conn, getPeeked(br)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,6 +222,14 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
|
||||||
|
|
||||||
balancer := wrr.New(config.Sticky, config.HealthCheck != nil)
|
balancer := wrr.New(config.Sticky, config.HealthCheck != nil)
|
||||||
for _, service := range shuffle(config.Services, m.rand) {
|
for _, service := range shuffle(config.Services, m.rand) {
|
||||||
|
if service.Status != nil {
|
||||||
|
serviceHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(*service.Status)
|
||||||
|
})
|
||||||
|
balancer.Add(service.Name, serviceHandler, service.Weight)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
|
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -26,8 +26,8 @@ import (
|
||||||
|
|
||||||
// Config provides configuration settings for the open-telemetry tracer.
|
// Config provides configuration settings for the open-telemetry tracer.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
GRPC *types.OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"`
|
GRPC *types.OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
HTTP *types.OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
|
HTTP *types.OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults sets the default values.
|
// SetDefaults sets the default values.
|
||||||
|
|
|
@ -108,8 +108,8 @@ func (i *InfluxDB2) SetDefaults() {
|
||||||
|
|
||||||
// OTLP contains specific configuration used by the OpenTelemetry Metrics exporter.
|
// OTLP contains specific configuration used by the OpenTelemetry Metrics exporter.
|
||||||
type OTLP struct {
|
type OTLP struct {
|
||||||
GRPC *OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"`
|
GRPC *OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
HTTP *OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
|
HTTP *OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
|
||||||
AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"`
|
AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"`
|
||||||
AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"`
|
AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"`
|
||||||
|
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||||
OutputType = "file"
|
OutputType = "file"
|
||||||
FileName = "traefik_changelog.md"
|
FileName = "traefik_changelog.md"
|
||||||
|
|
||||||
# example new bugfix v2.11.2
|
# example new bugfix v3.0.1
|
||||||
CurrentRef = "v2.11"
|
CurrentRef = "v3.0"
|
||||||
PreviousRef = "v2.11.1"
|
PreviousRef = "v3.0.0"
|
||||||
BaseBranch = "v2.11"
|
BaseBranch = "v3.0"
|
||||||
FutureCurrentRefName = "v2.11.2"
|
FutureCurrentRefName = "v3.0.1"
|
||||||
|
|
||||||
ThresholdPreviousRef = 10
|
ThresholdPreviousRef = 10
|
||||||
ThresholdCurrentRef = 10
|
ThresholdCurrentRef = 10
|
||||||
|
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||||
OutputType = "file"
|
OutputType = "file"
|
||||||
FileName = "traefik_changelog.md"
|
FileName = "traefik_changelog.md"
|
||||||
|
|
||||||
# example final release of v2.11.0
|
# example final release of v3.0.0
|
||||||
CurrentRef = "v2.11"
|
CurrentRef = "v3.0"
|
||||||
PreviousRef = "v2.11.0-rc1"
|
PreviousRef = "v3.0.0-beta1"
|
||||||
BaseBranch = "v2.11"
|
BaseBranch = "v3.0"
|
||||||
FutureCurrentRefName = "v2.11.0"
|
FutureCurrentRefName = "v3.0.0"
|
||||||
|
|
||||||
ThresholdPreviousRef = 10
|
ThresholdPreviousRef = 10
|
||||||
ThresholdCurrentRef = 10
|
ThresholdCurrentRef = 10
|
||||||
|
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||||
OutputType = "file"
|
OutputType = "file"
|
||||||
FileName = "traefik_changelog.md"
|
FileName = "traefik_changelog.md"
|
||||||
|
|
||||||
# example final release of v2.11.0
|
# example final release of v3.0.0
|
||||||
CurrentRef = "v2.11.0-rc1"
|
CurrentRef = "v3.0.0-beta1"
|
||||||
PreviousRef = "v2.10.0-rc1"
|
PreviousRef = "v2.9.0-rc1"
|
||||||
BaseBranch = "master"
|
BaseBranch = "master"
|
||||||
FutureCurrentRefName = "v2.11.0-rc1"
|
FutureCurrentRefName = "v3.0.0-beta1"
|
||||||
|
|
||||||
ThresholdPreviousRef = 10
|
ThresholdPreviousRef = 10
|
||||||
ThresholdCurrentRef = 10
|
ThresholdCurrentRef = 10
|
||||||
|
|
|
@ -9,7 +9,6 @@ module.exports = {
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
browser: true,
|
browser: true,
|
||||||
mocha: true,
|
|
||||||
'vue/setup-compiler-macros': true
|
'vue/setup-compiler-macros': true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -18,14 +17,12 @@ module.exports = {
|
||||||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
||||||
'plugin:vue/vue3-essential',
|
'plugin:vue/vue3-essential',
|
||||||
'plugin:vue/vue3-recommended',
|
'plugin:vue/vue3-recommended',
|
||||||
'plugin:mocha/recommended',
|
|
||||||
'standard'
|
'standard'
|
||||||
],
|
],
|
||||||
|
|
||||||
// required to lint *.vue files
|
// required to lint *.vue files
|
||||||
plugins: [
|
plugins: [
|
||||||
'vue',
|
'vue',
|
||||||
'mocha'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
globals: {
|
globals: {
|
||||||
|
|
|
@ -8,12 +8,14 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"transfer": "node dev/scripts/transfer.js",
|
"transfer": "node dev/scripts/transfer.js",
|
||||||
"lint": "eslint --ext .js,.vue src",
|
"lint": "eslint --ext .js,.vue src",
|
||||||
"test-unit": "mocha-webpack --mode=production './src/**/*.spec.js'",
|
|
||||||
"dev": "export APP_ENV='development' && quasar dev",
|
"dev": "export APP_ENV='development' && quasar dev",
|
||||||
"build-quasar": "quasar build",
|
"build-quasar": "quasar build",
|
||||||
"build-staging": "export NODE_ENV='production' && export APP_ENV='development' && yarn build-quasar",
|
"build-staging": "export NODE_ENV='production' && export APP_ENV='development' && yarn build-quasar",
|
||||||
"build": "export NODE_ENV='production' && export APP_ENV='production' && yarn build-quasar && yarn transfer spa",
|
"build": "export NODE_ENV='production' && export APP_ENV='production' && yarn build-quasar && yarn transfer spa",
|
||||||
"build:nc": "yarn build"
|
"build:nc": "yarn build",
|
||||||
|
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"test:unit:ci": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.16.9",
|
"@quasar/extras": "^1.16.9",
|
||||||
|
@ -39,18 +41,17 @@
|
||||||
"@babel/eslint-parser": "^7.23.10",
|
"@babel/eslint-parser": "^7.23.10",
|
||||||
"@quasar/app-vite": "^1.4.3",
|
"@quasar/app-vite": "^1.4.3",
|
||||||
"@quasar/babel-preset-app": "^2.0.2",
|
"@quasar/babel-preset-app": "^2.0.2",
|
||||||
|
"@quasar/quasar-app-extension-testing-unit-vitest": "^1.0.0",
|
||||||
"@vue/test-utils": "^2.4.4",
|
"@vue/test-utils": "^2.4.4",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"chai": "5.0.3",
|
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
"eslint-config-standard": "^17.0.0",
|
"eslint-config-standard": "^17.0.0",
|
||||||
"eslint-plugin-import": "^2.19.1",
|
"eslint-plugin-import": "^2.19.1",
|
||||||
"eslint-plugin-mocha": "^10.2.0",
|
|
||||||
"eslint-plugin-n": "^16.6.2",
|
"eslint-plugin-n": "^16.6.2",
|
||||||
"eslint-plugin-promise": "^6.0.0",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-vue": "^9.0.0",
|
"eslint-plugin-vue": "^9.0.0",
|
||||||
"mocha": "^10.2.0",
|
"postcss": "^8.4.14",
|
||||||
"postcss": "^8.4.14"
|
"vitest": "^1.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20 || ^18 || ^16",
|
"node": "^20 || ^18 || ^16",
|
||||||
|
|
|
@ -122,8 +122,8 @@ module.exports = configure(function (ctx) {
|
||||||
build: {
|
build: {
|
||||||
// Needed to have relative assets in the index.html
|
// Needed to have relative assets in the index.html
|
||||||
// https://github.com/quasarframework/quasar/issues/8513#issuecomment-1127654470
|
// https://github.com/quasarframework/quasar/issues/8513#issuecomment-1127654470
|
||||||
extendViteConf(viteConf, {isServer, isClient}) {
|
extendViteConf (viteConf, { isServer, isClient }) {
|
||||||
viteConf.base = "";
|
viteConf.base = ''
|
||||||
},
|
},
|
||||||
viteVuePluginOptions: {
|
viteVuePluginOptions: {
|
||||||
template: {
|
template: {
|
||||||
|
@ -173,7 +173,7 @@ module.exports = configure(function (ctx) {
|
||||||
animations: [],
|
animations: [],
|
||||||
|
|
||||||
ssr: {
|
ssr: {
|
||||||
pwa: false,
|
pwa: false
|
||||||
},
|
},
|
||||||
|
|
||||||
pwa: {
|
pwa: {
|
||||||
|
@ -201,29 +201,29 @@ module.exports = configure(function (ctx) {
|
||||||
theme_color: '#027be3',
|
theme_color: '#027be3',
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
'src': 'icons/icon-128x128.png',
|
src: 'icons/icon-128x128.png',
|
||||||
'sizes': '128x128',
|
sizes: '128x128',
|
||||||
'type': 'image/png'
|
type: 'image/png'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'src': 'icons/icon-192x192.png',
|
src: 'icons/icon-192x192.png',
|
||||||
'sizes': '192x192',
|
sizes: '192x192',
|
||||||
'type': 'image/png'
|
type: 'image/png'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'src': 'icons/icon-256x256.png',
|
src: 'icons/icon-256x256.png',
|
||||||
'sizes': '256x256',
|
sizes: '256x256',
|
||||||
'type': 'image/png'
|
type: 'image/png'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'src': 'icons/icon-384x384.png',
|
src: 'icons/icon-384x384.png',
|
||||||
'sizes': '384x384',
|
sizes: '384x384',
|
||||||
'type': 'image/png'
|
type: 'image/png'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'src': 'icons/icon-512x512.png',
|
src: 'icons/icon-512x512.png',
|
||||||
'sizes': '512x512',
|
sizes: '512x512',
|
||||||
'type': 'image/png'
|
type: 'image/png'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
5
webui/quasar.extensions.json
Normal file
5
webui/quasar.extensions.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"@quasar/testing-unit-vitest": {
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ make clean-webui generate-webui # Generate static contents in `webui/static/` fo
|
||||||
|
|
||||||
## How to build (only for frontend developer)
|
## How to build (only for frontend developer)
|
||||||
|
|
||||||
- prerequisite: [Node 12.11+](https://nodejs.org) [Yarn](https://yarnpkg.com/)
|
- prerequisite: [Node 20.11+](https://nodejs.org) [Yarn 1.22.19](https://yarnpkg.com/)
|
||||||
|
|
||||||
- Go to the `webui/` directory
|
- Go to the `webui/` directory
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ make clean-webui generate-webui # Generate static contents in `webui/static/` fo
|
||||||
|
|
||||||
- [Node](https://nodejs.org)
|
- [Node](https://nodejs.org)
|
||||||
- [Yarn](https://yarnpkg.com/)
|
- [Yarn](https://yarnpkg.com/)
|
||||||
- [Webpack](https://github.com/webpack/webpack)
|
- [Quasar](https://quasar.dev/)
|
||||||
- [Vue](https://vuejs.org/)
|
- [Vue](https://vuejs.org/)
|
||||||
- [Bulma](https://bulma.io)
|
- [Bulma](https://bulma.io)
|
||||||
- [D3](https://d3js.org)
|
- [D3](https://d3js.org)
|
||||||
|
|
|
@ -809,12 +809,11 @@
|
||||||
<div class="text-subtitle2">
|
<div class="text-subtitle2">
|
||||||
Content Security Policy
|
Content Security Policy
|
||||||
</div>
|
</div>
|
||||||
<q-chip
|
<q-card class="app-chip app-chip-green app-card-as-chip">
|
||||||
dense
|
<q-card-section>
|
||||||
class="app-chip app-chip-green"
|
|
||||||
>
|
|
||||||
{{ exData(middleware).contentSecurityPolicy }}
|
{{ exData(middleware).contentSecurityPolicy }}
|
||||||
</q-chip>
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
@ -945,8 +944,8 @@
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<!-- EXTRA FIELDS FROM MIDDLEWARES - [ipAllowList] - sourceRange -->
|
<!-- EXTRA FIELDS FROM MIDDLEWARES - [ipWhiteList] - sourceRange -->
|
||||||
<q-card-section v-if="middleware.ipAllowList">
|
<q-card-section v-if="middleware.ipWhiteList">
|
||||||
<div class="row items-start no-wrap">
|
<div class="row items-start no-wrap">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="text-subtitle2">
|
<div class="text-subtitle2">
|
||||||
|
@ -963,8 +962,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<!-- EXTRA FIELDS FROM MIDDLEWARES - [ipAllowList] - ipStrategy -->
|
<!-- EXTRA FIELDS FROM MIDDLEWARES - [ipWhiteList] - ipStrategy -->
|
||||||
<q-card-section v-if="middleware.ipAllowList">
|
<q-card-section v-if="middleware.ipWhiteList">
|
||||||
<div class="row items-start">
|
<div class="row items-start">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="text-subtitle2">
|
<div class="text-subtitle2">
|
||||||
|
|
|
@ -121,6 +121,14 @@ body {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-card-as-chip {
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
.q-card__section {
|
||||||
|
padding: 5px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Chips
|
// Chips
|
||||||
.app-chip {
|
.app-chip {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { expect } from 'chai'
|
import { describe, expect, it } from 'vitest'
|
||||||
import store from './index.js'
|
import store from './index.js'
|
||||||
|
|
||||||
const {
|
const {
|
|
@ -1,4 +1,4 @@
|
||||||
import { expect } from 'chai'
|
import { describe, expect, it } from 'vitest'
|
||||||
import store from './index.js'
|
import store from './index.js'
|
||||||
|
|
||||||
const {
|
const {
|
|
@ -1,4 +1,4 @@
|
||||||
import { expect } from 'chai'
|
import { describe, expect, it } from 'vitest'
|
||||||
import store from './index.js'
|
import store from './index.js'
|
||||||
|
|
||||||
const {
|
const {
|
1
webui/test/vitest/setup-file.js
Normal file
1
webui/test/vitest/setup-file.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
// This file will be run before each test file
|
24
webui/vitest.config.mjs
Normal file
24
webui/vitest.config.mjs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
|
||||||
|
import jsconfigPaths from 'vite-jsconfig-paths';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
setupFiles: 'test/vitest/setup-file.js',
|
||||||
|
include: [
|
||||||
|
// Matches vitest tests in any subfolder of 'src' or into 'test/vitest/__tests__'
|
||||||
|
// Matches all files with extension 'js', 'jsx', 'ts' and 'tsx'
|
||||||
|
'src/**/*.vitest.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue({
|
||||||
|
template: { transformAssetUrls },
|
||||||
|
}),
|
||||||
|
quasar(),
|
||||||
|
jsconfigPaths(),
|
||||||
|
],
|
||||||
|
});
|
1064
webui/yarn.lock
1064
webui/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue