spacegate-config 0.2.0-alpha.4

A library-first, lightweight, high-performance, cloud-native supported API gateway
Documentation
use std::{collections::BTreeMap, hash::Hasher};

use k8s_gateway_api::{Gateway, GatewaySpec, GatewayTlsConfig, Listener, SecretObjectReference};
use k8s_openapi::{api::core::v1::Secret, ByteString};
use kube::{api::ObjectMeta, ResourceExt};
use spacegate_model::{ext::k8s::helper_struct::SgTargetKind, PluginInstanceId};

use crate::{constants, ext::k8s::crd::sg_filter::K8sSgFilterSpecTargetRef, service::k8s::K8s, SgGateway, SgParameters};

use super::ToTarget;
pub(crate) trait SgGatewayConv {
    fn to_kube_gateway(self, namespace: &str) -> (Gateway, Option<Secret>, Vec<PluginInstanceId>);
}
impl SgGatewayConv for SgGateway {
    fn to_kube_gateway(self, namespace: &str) -> (Gateway, Option<Secret>, Vec<PluginInstanceId>) {
        let mut secret = None;

        let gateway = Gateway {
            metadata: ObjectMeta {
                annotations: Some(self.parameters.into_kube_gateway()),
                labels: None,
                name: Some(self.name.clone()),
                namespace: Some(namespace.to_string()),
                owner_references: None,
                self_link: None,
                ..Default::default()
            },
            spec: GatewaySpec {
                gateway_class_name: constants::GATEWAY_CLASS_NAME.to_string(),
                listeners: self
                    .listeners
                    .into_iter()
                    .map(|l| Listener {
                        name: l.name,
                        hostname: l.hostname,
                        port: l.port,
                        protocol: l.protocol.to_string(),
                        tls: match l.protocol {
                            crate::SgProtocolConfig::Http => None,
                            crate::SgProtocolConfig::Https { tls } => {
                                let key = tls.key.trim().as_bytes();
                                let cert = tls.cert.trim().as_bytes();
                                let mut hasher = std::hash::DefaultHasher::new();
                                hasher.write(key);
                                let name = format!("{:016x}", hasher.finish());
                                secret = Some(Secret {
                                    metadata: ObjectMeta {
                                        name: Some(name.clone()),
                                        namespace: Some(namespace.to_string()),
                                        ..Default::default()
                                    },
                                    type_: Some("kubernetes.io/tls".to_string()),
                                    data: Some(BTreeMap::from([
                                        ("tls.key".to_string(), ByteString(key.to_vec())),
                                        ("tls.crt".to_string(), ByteString(cert.to_vec())),
                                    ])),
                                    ..Default::default()
                                });
                                Some(GatewayTlsConfig {
                                    mode: Some(tls.mode.to_pascal_case().to_string()),
                                    certificate_refs: Some(vec![SecretObjectReference {
                                        kind: Some("Secret".to_string()),
                                        name,
                                        namespace: Some(namespace.to_string()),
                                        ..Default::default()
                                    }]),
                                    options: Some(BTreeMap::from([(K8s::HTTP2_KEY.to_string(), tls.http2.unwrap_or_default().to_string())])),
                                })
                            }
                            _ => None,
                        },
                        allowed_routes: None,
                    })
                    .collect(),
                addresses: None,
            },
            status: None,
        };

        (gateway, secret, self.plugins)
    }
}

pub(crate) trait SgParametersConv {
    fn from_kube_gateway(gateway: &Gateway) -> Self;
    fn into_kube_gateway(self) -> BTreeMap<String, String>;
}

impl SgParametersConv for SgParameters {
    fn into_kube_gateway(self) -> BTreeMap<String, String> {
        let mut ann = BTreeMap::new();
        if let Some(redis_url) = self.redis_url {
            ann.insert(crate::constants::GATEWAY_ANNOTATION_REDIS_URL.to_string(), redis_url);
        }
        if let Some(log_level) = self.log_level {
            ann.insert(crate::constants::GATEWAY_ANNOTATION_LOG_LEVEL.to_string(), log_level);
        }
        if let Some(lang) = self.lang {
            ann.insert(crate::constants::GATEWAY_ANNOTATION_LANGUAGE.to_string(), lang);
        }
        if let Some(ignore_tls_verification) = self.ignore_tls_verification {
            ann.insert(
                crate::constants::GATEWAY_ANNOTATION_IGNORE_TLS_VERIFICATION.to_string(),
                ignore_tls_verification.to_string(),
            );
        }
        if let Some(enable_x_request_id) = self.enable_x_request_id {
            ann.insert(crate::constants::GATEWAY_ANNOTATION_ENABLE_X_REQUEST_ID.to_string(), enable_x_request_id.to_string());
        }
        ann
    }

    fn from_kube_gateway(gateway: &Gateway) -> Self {
        let gateway_annotations = gateway.metadata.annotations.clone();
        if let Some(gateway_annotations) = gateway_annotations {
            SgParameters {
                redis_url: gateway_annotations.get(crate::constants::GATEWAY_ANNOTATION_REDIS_URL).map(|v| v.to_string()),
                log_level: gateway_annotations.get(crate::constants::GATEWAY_ANNOTATION_LOG_LEVEL).map(|v| v.to_string()),
                lang: gateway_annotations.get(crate::constants::GATEWAY_ANNOTATION_LANGUAGE).map(|v| v.to_string()),
                ignore_tls_verification: gateway_annotations.get(crate::constants::GATEWAY_ANNOTATION_IGNORE_TLS_VERIFICATION).and_then(|v| v.parse::<bool>().ok()),
                enable_x_request_id: gateway_annotations.get(crate::constants::GATEWAY_ANNOTATION_ENABLE_X_REQUEST_ID).and_then(|v| v.parse::<bool>().ok()),
            }
        } else {
            SgParameters {
                redis_url: None,
                log_level: None,
                lang: None,
                ignore_tls_verification: None,
                enable_x_request_id: None,
            }
        }
    }
}

impl ToTarget for Gateway {
    fn to_target_ref(&self) -> K8sSgFilterSpecTargetRef {
        K8sSgFilterSpecTargetRef {
            kind: SgTargetKind::Gateway.into(),
            name: self.name_any(),
            namespace: self.namespace(),
        }
    }
}