@@ -2,6 +2,9 @@ use std::{sync::Arc, time::Duration};
22
33use futures:: StreamExt ;
44use gateway_api:: {
5+ apis:: experimental:: tcproutes:: {
6+ TCPRoute , TCPRouteParentRefs , TCPRouteRules , TCPRouteRulesBackendRefs , TCPRouteSpec ,
7+ } ,
58 gateways,
69 httproutes:: {
710 HTTPRoute , HTTPRouteParentRefs , HTTPRouteRules , HTTPRouteRulesBackendRefs ,
@@ -11,7 +14,7 @@ use gateway_api::{
1114} ;
1215use k8s_openapi:: api:: {
1316 core:: v1:: Service ,
14- networking:: v1:: { Ingress , ServiceBackendPort } ,
17+ networking:: v1:: { Ingress , IngressServiceBackend , ServiceBackendPort } ,
1518} ;
1619use kube:: { Api , Resource , ResourceExt , api:: PatchParams , runtime:: controller:: Action } ;
1720
@@ -63,7 +66,7 @@ async fn create_http_route(
6366 http : & k8s_openapi:: api:: networking:: v1:: HTTPIngressRuleValue ,
6467 hostname : & str ,
6568) -> anyhow:: Result < HTTPRoute > {
66- let safe_hostname = hostname . replace ( '.' , "-" ) ;
69+ let safe_hostname = utils :: sanitize_hostname ( hostname ) ;
6770 let gw_group = <gateways:: Gateway as kube:: Resource >:: group ( & ( ) ) ;
6871 let gw_kind = <gateways:: Gateway as kube:: Resource >:: kind ( & ( ) ) ;
6972
@@ -156,6 +159,68 @@ async fn create_http_route(
156159 ) )
157160}
158161
162+ async fn create_tcp_route (
163+ ctx : Arc < ctx:: Context > ,
164+ namespace : & str ,
165+ svc : & IngressServiceBackend ,
166+ hostname : & str ,
167+ ) -> anyhow:: Result < TCPRoute > {
168+ let safe_hostname = utils:: sanitize_hostname ( hostname) ;
169+ let gw_group = <gateways:: Gateway as kube:: Resource >:: group ( & ( ) ) ;
170+ let gw_kind = <gateways:: Gateway as kube:: Resource >:: kind ( & ( ) ) ;
171+
172+ let Some ( svc_port) = & svc. port else {
173+ tracing:: warn!( "Skipping backend without service port" ) ;
174+ return Err ( anyhow:: anyhow!( "Backend doesn't have port" ) . into ( ) ) ;
175+ } ;
176+
177+ let Some ( svc_port_number) = get_svc_port_number (
178+ Api :: namespaced ( ctx. client . clone ( ) , namespace) ,
179+ & svc. name ,
180+ svc_port,
181+ )
182+ . await
183+ else {
184+ tracing:: warn!(
185+ "skipping backend with unresolvable service port for service {}" ,
186+ & svc. name
187+ ) ;
188+ return Err (
189+ anyhow:: anyhow!( format!( "Couldn't resolve port for a service {}" , & svc. name) ) . into ( ) ,
190+ ) ;
191+ } ;
192+ Ok ( TCPRoute :: new (
193+ & format ! ( "{safe_hostname}-tcp" ) ,
194+ TCPRouteSpec {
195+ use_default_gateways : None ,
196+ rules : [ TCPRouteRules {
197+ name : None ,
198+ backend_refs : [ TCPRouteRulesBackendRefs {
199+ name : svc. name . clone ( ) ,
200+ port : Some ( svc_port_number) ,
201+ kind : None ,
202+ group : None ,
203+ namespace : None ,
204+ weight : None ,
205+ } ]
206+ . to_vec ( ) ,
207+ } ]
208+ . to_vec ( ) ,
209+ parent_refs : Some (
210+ [ TCPRouteParentRefs {
211+ group : Some ( gw_group. to_string ( ) ) ,
212+ kind : Some ( gw_kind. to_string ( ) ) ,
213+ name : ctx. args . default_gateway_name . clone ( ) ,
214+ namespace : Some ( ctx. args . default_gateway_namespace . clone ( ) ) ,
215+ port : Some ( 80 ) ,
216+ section_name : None ,
217+ } ]
218+ . to_vec ( ) ,
219+ ) ,
220+ } ,
221+ ) )
222+ }
223+
159224#[ tracing:: instrument( skip( ingress, ctx) , fields( ingress = ingress. name_any( ) , namespace = ingress. namespace( ) ) , err) ]
160225pub async fn reconcile ( ingress : Arc < Ingress > , ctx : Arc < ctx:: Context > ) -> I2GResult < Action > {
161226 tracing:: info!( "Reconciling Ingress" ) ;
@@ -170,12 +235,14 @@ pub async fn reconcile(ingress: Arc<Ingress>, ctx: Arc<ctx::Context>) -> I2GResu
170235 let ingress_namespace = ingress
171236 . namespace ( )
172237 . ok_or_else ( || anyhow:: anyhow!( "Ingress doesn't have a namespace" ) ) ?;
238+ let default_backend = ingress_spec. default_backend . as_ref ( ) ;
173239
174240 for rule in ingress_rules {
175241 let Some ( host) = & rule. host else {
176242 tracing:: warn!( "Skipping rule without host" ) ;
177243 continue ;
178244 } ;
245+
179246 if let Some ( http) = & rule. http {
180247 let Ok ( mut route) =
181248 create_http_route ( ctx. clone ( ) , & ingress_namespace, & http, & host) . await
@@ -198,8 +265,42 @@ pub async fn reconcile(ingress: Arc<Ingress>, ctx: Arc<ctx::Context>) -> I2GResu
198265 )
199266 . await ?;
200267 } else {
268+ if !ctx. args . experimental {
269+ tracing:: warn!(
270+ "Skipping rule non-http rule. In order to migrate it to TCPRoute, please add --experimental flag to i2g-operator."
271+ ) ;
272+ }
201273 // In case if rule.http is None
202- unimplemented ! ( "Only HTTP Ingress rules are supported for now" ) ;
274+ let Some ( backend) = default_backend else {
275+ tracing:: warn!( "Skipping non-HTTP Ingress rule without default backend" ) ;
276+ continue ;
277+ } ;
278+ let Some ( backend_svc) = & backend. service else {
279+ tracing:: warn!( "defaultBackend doesn't have a service, skipping." ) ;
280+ continue ;
281+ } ;
282+
283+ let Ok ( mut route) =
284+ create_tcp_route ( ctx. clone ( ) , & ingress_namespace, backend_svc, & host) . await
285+ else {
286+ tracing:: warn!( "Failed to create TCPRoute for host {}" , host) ;
287+ continue ;
288+ } ;
289+
290+ if ctx. args . link_to_ingress {
291+ route. meta_mut ( ) . add_owner ( ingress. as_ref ( ) ) ;
292+ }
293+
294+ Api :: < TCPRoute > :: namespaced ( ctx. client . clone ( ) , & ingress_namespace)
295+ . patch (
296+ & route. name_any ( ) ,
297+ & PatchParams {
298+ field_manager : Some ( "ingress-to-gateway-controller" . to_string ( ) ) ,
299+ ..PatchParams :: default ( )
300+ } ,
301+ & kube:: api:: Patch :: Apply ( route) ,
302+ )
303+ . await ?;
203304 }
204305 }
205306
0 commit comments