rws_gateway_api/gateway_api/
mod.rs1pub mod backend_tls;
18pub mod controller;
19pub mod dataplane;
20pub mod filters;
21pub mod l4;
22pub mod lease;
23pub mod provision;
24pub mod resources;
25pub mod router;
26pub mod routes;
27pub mod services;
28pub mod translate;
29pub mod types;
30pub(crate) use crate::proxy::upgrade;
31
32pub const CONTROLLER_NAME: &str = "rws8.tech/gateway-controller";
34
35pub const FIELD_MANAGER: &str = "rws-gateway";
37
38use std::collections::BTreeMap;
39use std::sync::atomic::AtomicBool;
40use std::sync::Arc;
41
42pub const RWS_CONFIG_GATEWAY_API_ENABLED: &str = "RWS_CONFIG_GATEWAY_API_ENABLED";
45pub const RWS_CONFIG_GATEWAY_API_CONTROLLER_NAME: &str = "RWS_CONFIG_GATEWAY_API_CONTROLLER_NAME";
47pub const RWS_CONFIG_GATEWAY_API_POD_SELECTOR: &str = "RWS_CONFIG_GATEWAY_API_POD_SELECTOR";
49pub const RWS_CONFIG_GATEWAY_API_PORT_RANGE: &str = "RWS_CONFIG_GATEWAY_API_PORT_RANGE";
51pub const RWS_CONFIG_GATEWAY_API_SERVICE_TYPE: &str = "RWS_CONFIG_GATEWAY_API_SERVICE_TYPE";
53pub const RWS_CONFIG_GATEWAY_API_LEASE_NAMESPACE: &str = "RWS_CONFIG_GATEWAY_API_LEASE_NAMESPACE";
55pub const RWS_CONFIG_GATEWAY_API_MODE: &str = "RWS_CONFIG_GATEWAY_API_MODE";
58pub const RWS_CONFIG_GATEWAY_API_IMAGE: &str = "RWS_CONFIG_GATEWAY_API_IMAGE";
60pub const RWS_CONFIG_GATEWAY_API_SERVICE_ACCOUNT: &str = "RWS_CONFIG_GATEWAY_API_SERVICE_ACCOUNT";
63pub const RWS_CONFIG_GATEWAY_API_GATEWAY: &str = "RWS_CONFIG_GATEWAY_API_GATEWAY";
66
67fn setting(key: &str) -> Option<String> {
68 std::env::var(key).ok().map(|v| v.trim().to_string()).filter(|v| !v.is_empty())
69}
70
71pub fn enabled() -> bool {
73 setting(RWS_CONFIG_GATEWAY_API_ENABLED).is_some_and(|v| matches!(v.to_ascii_lowercase().as_str(), "true" | "1" | "yes" | "on"))
74}
75
76pub fn parse_labels(value: &str) -> BTreeMap<String, String> {
78 value.split(',').filter_map(|pair| pair.split_once('=')).map(|(k, v)| (k.trim().to_string(), v.trim().to_string())).filter(|(k, _)| !k.is_empty()).collect()
79}
80
81pub fn parse_port_range(value: &str) -> Option<(u16, u16)> {
83 let (first, last) = value.split_once('-')?;
84 let (first, last) = (first.trim().parse::<u16>().ok()?, last.trim().parse::<u16>().ok()?);
85 (first >= 1024 && first <= last).then_some((first, last))
86}
87
88pub fn settings_from_env() -> translate::Settings {
90 let mut settings = translate::Settings::default();
91 if let Some(name) = setting(RWS_CONFIG_GATEWAY_API_CONTROLLER_NAME) {
92 settings.controller_name = name;
93 }
94 if let Some(selector) = setting(RWS_CONFIG_GATEWAY_API_POD_SELECTOR).map(|v| parse_labels(&v)).filter(|l| !l.is_empty()) {
95 settings.services.pod_selector = selector;
96 }
97 if let Some(range) = setting(RWS_CONFIG_GATEWAY_API_PORT_RANGE).and_then(|v| parse_port_range(&v)) {
98 settings.services.port_range = range;
99 }
100 if let Some(kind) = setting(RWS_CONFIG_GATEWAY_API_SERVICE_TYPE) {
101 settings.services.service_type = kind;
102 }
103 settings.services.pod_namespace = pod_namespace();
104 if setting(RWS_CONFIG_GATEWAY_API_MODE).is_some_and(|m| m.eq_ignore_ascii_case("provisioned")) {
105 settings.services.provisioned = true;
106 settings.provision = Some(provision::ProvisionSettings {
107 image: setting(RWS_CONFIG_GATEWAY_API_IMAGE).unwrap_or_else(|| format!("ghcr.io/bohdaq/rws:{}", env!("CARGO_PKG_VERSION"))),
108 service_account: setting(RWS_CONFIG_GATEWAY_API_SERVICE_ACCOUNT).or_else(|| setting("POD_SERVICE_ACCOUNT")).unwrap_or_else(|| "default".to_string()),
109 ..Default::default()
110 });
111 }
112 settings
113}
114
115pub fn own_gateway() -> Option<(String, String)> {
118 let value = setting(RWS_CONFIG_GATEWAY_API_GATEWAY)?;
119 let (namespace, name) = value.split_once('/')?;
120 (!namespace.is_empty() && !name.is_empty()).then(|| (namespace.to_string(), name.to_string()))
121}
122
123fn pod_namespace() -> String {
126 setting("POD_NAMESPACE")
127 .or_else(|| std::fs::read_to_string("/var/run/secrets/kubernetes.io/serviceaccount/namespace").ok().map(|n| n.trim().to_string()))
128 .unwrap_or_else(|| "default".to_string())
129}
130
131fn lease_namespace() -> String {
133 setting(RWS_CONFIG_GATEWAY_API_LEASE_NAMESPACE).unwrap_or_else(pod_namespace)
134}
135
136pub fn start_from_env() {
140 if !enabled() {
141 return;
142 }
143 tokio::spawn(async {
144 let client = match kube::Client::try_default().await {
145 Ok(client) => client,
146 Err(e) => {
147 eprintln!("[gateway-api] no Kubernetes API: {e}");
148 return;
149 }
150 };
151 let settings = settings_from_env();
152 let leader = Arc::new(AtomicBool::new(false));
153 let became_leader = Arc::new(tokio::sync::Notify::new());
154 let own = own_gateway();
155 match &own {
156 Some((namespace, name)) => println!("[gateway-api] serving Gateway {namespace}/{name}"),
158 None => {
159 println!("[gateway-api] serving GatewayClasses with controllerName {}", settings.controller_name);
160 let identity = setting("POD_NAME").or_else(|| setting("HOSTNAME")).unwrap_or_else(|| format!("rws-{}", std::process::id()));
161 let lease = lease::LeaseSettings { namespace: lease_namespace(), name: "rws-gateway-controller".to_string(), identity };
162 tokio::spawn(lease::run(client.clone(), lease, Arc::clone(&leader), Arc::clone(&became_leader)));
163 }
164 }
165 let plane = Arc::new(dataplane::DataPlane::new("0.0.0.0"));
166 let provisioned = settings.provision.is_some();
167 let on_config = move |mut config: translate::GatewayConfig| {
168 match &own {
170 Some(gateway) => config.listeners.retain(|l| &l.gateway == gateway),
171 None if provisioned => config.listeners.clear(),
172 None => {}
173 }
174 plane.apply(&config)
175 };
176 let options = controller::Options { settings, leader, on_config: Arc::new(on_config) };
177 controller::run(client, options, became_leader).await;
178 });
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn settings_parse() {
187 assert_eq!(BTreeMap::from([("a".to_string(), "1".to_string()), ("b".to_string(), "x".to_string())]), parse_labels(" a=1, b = x ,junk"));
188 assert_eq!(Some((10000, 10999)), parse_port_range("10000-10999"));
189 assert_eq!(None, parse_port_range("80-90"), "no privileged ports");
190 assert_eq!(None, parse_port_range("2000-1000"));
191 assert_eq!(None, parse_port_range("x"));
192 }
193}