spacegate-config 0.2.0-alpha.4

A library-first, lightweight, high-performance, cloud-native supported API gateway
Documentation
use k8s_gateway_api::GatewayClass;
use k8s_openapi::api::{
    apps::v1::DaemonSet,
    core::v1::{Pod, Service},
};
use kube::{api::ListParams, Api, ResourceExt};
use spacegate_model::{constants::DEFAULT_API_PORT, BackendHost, BoxResult, K8sServiceData};

use crate::service::{Discovery, Instance};

use super::K8s;

pub struct K8sGatewayInstance {
    name: String,
    uri: String,
}
impl Instance for K8sGatewayInstance {
    fn api_url(&self) -> &str {
        &self.uri
    }
    fn id(&self) -> &str {
        &self.name
    }
}
impl Discovery for K8s {
    async fn instances(&self) -> BoxResult<Vec<impl Instance>> {
        let gateway_class_api: Api<GatewayClass> = self.get_all_api();

        let instance = if let Some(mut gateway_class) = gateway_class_api.get_opt(spacegate_model::constants::GATEWAY_CLASS_NAME).await? {
            gateway_class.labels_mut().remove(spacegate_model::constants::KUBE_OBJECT_INSTANCE).unwrap_or(spacegate_model::constants::GATEWAY_DEFAULT_INSTANCE.to_string())
        } else {
            return Err("gateway class not found".into());
        };

        let instance_split: Vec<_> = instance.split('.').collect();
        let (ds_api, pod_api, ds_name): (Api<DaemonSet>, Api<Pod>, String) = if instance_split.len() == 2 {
            let ns = instance_split.get(1).expect("unexpected");
            let ds_api: Api<DaemonSet> = self.get_specify_namespace_api(ns);
            let pod_api: Api<Pod> = self.get_specify_namespace_api(ns);
            let instance_name = instance_split.first().expect("unexpected");
            (ds_api, pod_api, instance_name.to_string())
        } else {
            let ds_api: Api<DaemonSet> = self.get_namespace_api();
            let pod_api: Api<Pod> = self.get_namespace_api();
            let instance_name = instance;
            (ds_api, pod_api, instance_name)
        };

        let ds_instance = if let Some(ds) = ds_api.get_opt(&ds_name).await? {
            ds
        } else {
            return Err("spacegate instance not found".into());
        };

        let pods = pod_api.list(&ListParams::default()).await?;
        let pods = pods.items;
        let instance_list = pods
            .into_iter()
            .filter_map(|p| {
                let ip = p.status.as_ref().and_then(|s| s.host_ip.as_ref())?;
                let port = DEFAULT_API_PORT;
                for owner_ref in p.owner_references() {
                    let instance_name = ds_instance.name_any();

                    if owner_ref.uid == ds_instance.uid().unwrap_or_default() && owner_ref.name == instance_name {
                        return Some(K8sGatewayInstance {
                            name: instance_name,
                            uri: format!("{ip}:{port}"),
                        });
                    }
                }
                None
            })
            .collect();

        Ok(instance_list)
    }

    async fn backends(&self) -> BoxResult<Vec<BackendHost>> {
        let service_api: Api<Service> = self.get_all_api();
        let result = service_api
            .list(&ListParams::default())
            .await?
            .into_iter()
            .map(|s| {
                BackendHost::K8sService(K8sServiceData {
                    name: s.name_any(),
                    namespace: s.namespace(),
                })
            })
            .collect();
        Ok(result)
    }
}