Skip to content

Commit 74674a9

Browse files
committed
Added split routest annotation.
1 parent 9b2d711 commit 74674a9

File tree

3 files changed

+124
-49
lines changed

3 files changed

+124
-49
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1-
# Ingress 2 gateway operator
1+
# ingress2gateway operator
22

33
> [!IMPORTANT]
4-
> This project is not intended to use for your applications since you config might be complicated. It's main goal is to convert third-party ingresses to gateways.
4+
> This project is not intended to use for your applications since you have direct access to this config. It's main goal is to convert third-party ingresses to gateways.
55
6+
### Installation
67

8+
We recommend using our helm chart in order to install this project.
9+
10+
```bash
11+
helm upgrade --install --namespace i2g --create-namespace i2g oci://ghcr.io/intreecom/charts/i2g-operator -f values.yaml
12+
```
13+
14+
You can see our values file for helm chart by running this command:
15+
16+
```bash
17+
helm show values oci://ghcr.io/intreecom/charts/i2g-operator
18+
```

src/consts.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/// This annotation will split ingress rules to a new HTTPSerice for each rule.
2+
/// Of the ingress. It's usefull because HTTPRoute resource can only have up to 16
3+
/// rules.
4+
pub const SPLIT_ROUTES: &'static str = "i2g-gateway/split_routes";

src/main.rs

Lines changed: 106 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{sync::Arc, time::Duration};
1+
use std::{fs::OpenOptions, sync::Arc, time::Duration};
22

33
use futures::StreamExt;
44
use gateway_api::{
@@ -16,7 +16,11 @@ use k8s_openapi::api::{
1616
core::v1::Service,
1717
networking::v1::{Ingress, IngressServiceBackend, ServiceBackendPort},
1818
};
19-
use kube::{Api, Resource, ResourceExt, api::PatchParams, runtime::controller::Action};
19+
use kube::{
20+
Api, Resource, ResourceExt,
21+
api::{ObjectMeta, PatchParams},
22+
runtime::controller::Action,
23+
};
2024
use tracing::Instrument;
2125

2226
use crate::{
@@ -25,6 +29,7 @@ use crate::{
2529
};
2630

2731
mod args;
32+
mod consts;
2833
mod ctx;
2934
mod err;
3035
mod utils;
@@ -61,19 +66,28 @@ async fn get_svc_port_number(
6166
return Some(port.port);
6267
}
6368

64-
async fn create_http_route(
69+
async fn create_http_routes(
6570
ctx: Arc<ctx::Context>,
6671
ingress_name: &str,
72+
ingress_meta: &ObjectMeta,
6773
section_name: Option<&String>,
6874
ingress_namespace: &str,
6975
http: &k8s_openapi::api::networking::v1::HTTPIngressRuleValue,
7076
hostname: &str,
71-
) -> anyhow::Result<HTTPRoute> {
77+
) -> anyhow::Result<Vec<HTTPRoute>> {
7278
let safe_hostname = utils::sanitize_hostname(hostname);
7379
let gw_group = <gateways::Gateway as kube::Resource>::group(&());
7480
let gw_kind = <gateways::Gateway as kube::Resource>::kind(&());
7581

82+
let split_routes = ingress_meta
83+
.annotations
84+
.as_ref()
85+
.and_then(|ann| ann.get(consts::SPLIT_ROUTES))
86+
.map(|v| v.to_lowercase() == "true")
87+
.unwrap_or(false);
88+
7689
let mut rules = vec![];
90+
7791
for path in &http.paths {
7892
let Some(svc) = &path.backend.service else {
7993
tracing::warn!("Skipping backend without service");
@@ -96,28 +110,16 @@ async fn create_http_route(
96110
);
97111
continue;
98112
};
99-
let mut path_matches = vec![];
100-
for path in &http.paths {
101-
let match_type = match path.path_type.as_str() {
102-
"Prefix" => HTTPRouteRulesMatchesPathType::PathPrefix,
103-
"Exact" => HTTPRouteRulesMatchesPathType::Exact,
104-
"ImplementationSpecific" => HTTPRouteRulesMatchesPathType::PathPrefix,
105-
_ => {
106-
return Err(
107-
anyhow::anyhow!("Unknown path type: {}", path.path_type.as_str()).into(),
108-
);
109-
}
110-
};
111-
path_matches.push(HTTPRouteRulesMatches {
112-
headers: None,
113-
method: None,
114-
query_params: None,
115-
path: Some(HTTPRouteRulesMatchesPath {
116-
r#type: Some(match_type),
117-
value: path.path.clone(),
118-
}),
119-
});
120-
}
113+
let match_type = match path.path_type.as_str() {
114+
"Prefix" => HTTPRouteRulesMatchesPathType::PathPrefix,
115+
"Exact" => HTTPRouteRulesMatchesPathType::Exact,
116+
"ImplementationSpecific" => HTTPRouteRulesMatchesPathType::PathPrefix,
117+
_ => {
118+
return Err(
119+
anyhow::anyhow!("Unknown path type: {}", path.path_type.as_str()).into(),
120+
);
121+
}
122+
};
121123
rules.push(HTTPRouteRules {
122124
name: None,
123125
backend_refs: Some(
@@ -132,16 +134,64 @@ async fn create_http_route(
132134
}]
133135
.to_vec(),
134136
),
135-
matches: Some(path_matches),
137+
matches: Some(vec![HTTPRouteRulesMatches {
138+
headers: None,
139+
method: None,
140+
query_params: None,
141+
path: Some(HTTPRouteRulesMatchesPath {
142+
r#type: Some(match_type),
143+
value: path.path.clone(),
144+
}),
145+
}]),
136146
filters: None,
137147
timeouts: None,
138148
});
139149
}
140150
if rules.is_empty() {
141151
return Err(anyhow::anyhow!("No valid paths found").into());
142152
}
153+
154+
// If split_routes is enabled, create a separate HTTPRoute for each rule.
155+
if split_routes {
156+
return Ok(rules
157+
.into_iter()
158+
.map(|rule| {
159+
HTTPRoute::new(
160+
&format!(
161+
"{ingress_name}-{safe_hostname}-{}",
162+
utils::sanitize_hostname(
163+
&rule
164+
.matches
165+
.as_ref()
166+
.and_then(|m| m.first())
167+
.and_then(|mm| mm.path.as_ref())
168+
.and_then(|p| p.value.clone())
169+
.unwrap_or_else(|| "root".to_string())
170+
)
171+
),
172+
HTTPRouteSpec {
173+
hostnames: Some(vec![hostname.to_string()]),
174+
parent_refs: Some(
175+
[HTTPRouteParentRefs {
176+
group: Some(gw_group.to_string()),
177+
kind: Some(gw_kind.to_string()),
178+
name: ctx.args.default_gateway_name.clone(),
179+
namespace: Some(ctx.args.default_gateway_namespace.clone()),
180+
port: None,
181+
section_name: section_name.cloned(),
182+
}]
183+
.to_vec(),
184+
),
185+
rules: Some(vec![rule]),
186+
},
187+
)
188+
})
189+
.collect());
190+
}
191+
192+
// Split routes is disabled, create a single HTTPRoute with all rules.
143193
let route_name = format!("{ingress_name}-{safe_hostname}-http");
144-
Ok(HTTPRoute::new(
194+
Ok([HTTPRoute::new(
145195
&route_name,
146196
HTTPRouteSpec {
147197
hostnames: Some(vec![hostname.to_string()]),
@@ -159,10 +209,11 @@ async fn create_http_route(
159209
),
160210
rules: Some(rules),
161211
},
162-
))
212+
)]
213+
.to_vec())
163214
}
164215

165-
async fn create_tcp_route(
216+
async fn create_tcp_routes(
166217
ctx: Arc<ctx::Context>,
167218
ingress_name: &str,
168219
section_name: Option<&String>,
@@ -228,7 +279,7 @@ async fn create_tcp_route(
228279

229280
#[tracing::instrument(skip(ingress, ctx), fields(ingress = ingress.name_any(), namespace = ingress.namespace()), err)]
230281
pub async fn reconcile(ingress: Arc<Ingress>, ctx: Arc<ctx::Context>) -> I2GResult<Action> {
231-
if ctx.is_leader.load(std::sync::atomic::Ordering::Relaxed) {
282+
if !ctx.is_leader.load(std::sync::atomic::Ordering::Relaxed) {
232283
tracing::debug!("Not a leader, skipping reconciliation");
233284
return Ok(Action::requeue(Duration::from_secs(20)));
234285
}
@@ -261,9 +312,10 @@ pub async fn reconcile(ingress: Arc<Ingress>, ctx: Arc<ctx::Context>) -> I2GResu
261312
};
262313

263314
if let Some(http) = &rule.http {
264-
let Ok(mut route) = create_http_route(
315+
let Ok(routes) = create_http_routes(
265316
ctx.clone(),
266317
&ingress.name_any(),
318+
&ingress.meta(),
267319
desired_section_name.as_ref(),
268320
&ingress_namespace,
269321
&http,
@@ -274,21 +326,28 @@ pub async fn reconcile(ingress: Arc<Ingress>, ctx: Arc<ctx::Context>) -> I2GResu
274326
tracing::warn!("Failed to create HTTPRoute for host {}", host);
275327
continue;
276328
};
277-
if ctx.args.link_to_ingress {
278-
route.meta_mut().add_owner(ingress.as_ref());
329+
let a = OpenOptions::new()
330+
.create(true)
331+
.write(true)
332+
.open("shit.json")
333+
.unwrap();
334+
serde_json::to_writer_pretty(a, &routes).unwrap();
335+
for mut route in routes {
336+
if ctx.args.link_to_ingress {
337+
route.meta_mut().add_owner(ingress.as_ref());
338+
}
339+
Api::<HTTPRoute>::namespaced(ctx.client.clone(), &ingress_namespace)
340+
.patch(
341+
&route.name_any(),
342+
&PatchParams {
343+
field_manager: Some("ingress-to-gateway-controller".to_string()),
344+
..PatchParams::default()
345+
},
346+
&kube::api::Patch::Apply(route),
347+
)
348+
.instrument(tracing::info_span!("Applying generated HTTPRoute"))
349+
.await?;
279350
}
280-
281-
Api::<HTTPRoute>::namespaced(ctx.client.clone(), &ingress_namespace)
282-
.patch(
283-
&route.name_any(),
284-
&PatchParams {
285-
field_manager: Some("ingress-to-gateway-controller".to_string()),
286-
..PatchParams::default()
287-
},
288-
&kube::api::Patch::Apply(route),
289-
)
290-
.instrument(tracing::info_span!("Applying generated HTTPRoute"))
291-
.await?;
292351
} else {
293352
if !ctx.args.experimental {
294353
tracing::warn!(
@@ -306,7 +365,7 @@ pub async fn reconcile(ingress: Arc<Ingress>, ctx: Arc<ctx::Context>) -> I2GResu
306365
continue;
307366
};
308367

309-
let Ok(mut route) = create_tcp_route(
368+
let Ok(mut route) = create_tcp_routes(
310369
ctx.clone(),
311370
&ingress.name_any(),
312371
desired_section_name.as_ref(),

0 commit comments

Comments
 (0)