From d349e4cd93e5eeb22a0439320262cfa984a596ae Mon Sep 17 00:00:00 2001 From: RWDai <27391645+RWDai@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:02:17 +0000 Subject: [PATCH] update --- crates/config/src/service/k8s/convert.rs | 9 + .../service/k8s/convert/filter_k8s_conv.rs | 222 +++++++++++ .../service}/k8s/convert/gateway_k8s_conv.rs | 60 ++- .../src/service/k8s/convert/route_k8s_conv.rs | 344 ++++++++++++++++++ crates/config/src/service/k8s/create.rs | 40 +- crates/config/src/service/k8s/mod.rs | 7 + crates/config/src/service/k8s/retrieve.rs | 60 ++- crates/model/src/ext/k8s.rs | 1 - crates/model/src/ext/k8s/convert.rs | 3 - .../src/ext/k8s/convert/filter_k8s_conv.rs | 178 --------- .../src/ext/k8s/convert/route_k8s_conv.rs | 288 --------------- crates/model/src/ext/k8s/crd/sg_filter.rs | 10 + crates/model/src/ext/k8s/helper_filter.rs | 46 +-- crates/shell/res/spacegate-gateway.yaml | 3 + .../spacegate-admin-server/admin-server.yaml | 2 +- 15 files changed, 719 insertions(+), 554 deletions(-) create mode 100644 crates/config/src/service/k8s/convert.rs create mode 100644 crates/config/src/service/k8s/convert/filter_k8s_conv.rs rename crates/{model/src/ext => config/src/service}/k8s/convert/gateway_k8s_conv.rs (80%) create mode 100644 crates/config/src/service/k8s/convert/route_k8s_conv.rs delete mode 100644 crates/model/src/ext/k8s/convert.rs delete mode 100644 crates/model/src/ext/k8s/convert/filter_k8s_conv.rs delete mode 100644 crates/model/src/ext/k8s/convert/route_k8s_conv.rs diff --git a/crates/config/src/service/k8s/convert.rs b/crates/config/src/service/k8s/convert.rs new file mode 100644 index 00000000..875d6b79 --- /dev/null +++ b/crates/config/src/service/k8s/convert.rs @@ -0,0 +1,9 @@ +use spacegate_model::ext::k8s::crd::sg_filter::K8sSgFilterSpecTargetRef; + +pub mod filter_k8s_conv; +pub mod gateway_k8s_conv; +pub mod route_k8s_conv; + +pub(crate) trait ToTarget { + fn to_target_ref(&self) -> K8sSgFilterSpecTargetRef; +} diff --git a/crates/config/src/service/k8s/convert/filter_k8s_conv.rs b/crates/config/src/service/k8s/convert/filter_k8s_conv.rs new file mode 100644 index 00000000..b5e9b34d --- /dev/null +++ b/crates/config/src/service/k8s/convert/filter_k8s_conv.rs @@ -0,0 +1,222 @@ +use std::collections::HashMap; + +use k8s_gateway_api::{HttpHeader, HttpPathModifier, HttpRequestHeaderFilter, HttpRequestRedirectFilter, HttpRouteFilter, HttpUrlRewriteFilter}; +use kube::Api; +use spacegate_model::ext::k8s::crd::sg_filter::SgFilter; + +use crate::{ + ext::k8s::{ + crd::sg_filter::{K8sSgFilterSpecFilter, K8sSgFilterSpecTargetRef}, + helper_filter::SgSingeFilter, + }, + plugin::{ + gatewayapi_support_filter::{ + SgFilterHeaderModifier, SgFilterHeaderModifierKind, SgFilterRedirect, SgFilterRewrite, SgHttpPathModifier, SgHttpPathModifierType, SG_FILTER_HEADER_MODIFIER_CODE, + SG_FILTER_REDIRECT_CODE, SG_FILTER_REWRITE_CODE, + }, + PluginConfig, + }, + service::{self, k8s::K8s}, + BoxResult, PluginInstanceId, PluginInstanceName, +}; + +pub(crate) trait PluginIdConv { + fn from_http_route_filter(route_filter: HttpRouteFilter) -> BoxResult; + + /// # to_singe_filter + /// `to_single_filter` method and [SgRouteFilter::to_http_route_filter] method both convert from + /// `SgRouteFilter`to the k8s model. The difference lies in that `to_single_filter` only includes + /// the k8s object of `SGFilter`, whereas `to_http_route_filter` is used to convert to `HttpRouteFilter`, + /// a filter defined in the Gateway API. + fn to_singe_filter(&self, value: serde_json::Value, target: Option, namespace: &str) -> Option; + + /// # to_http_route_filter + /// ref [SgRouteFilter::to_singe_filter] + async fn to_http_route_filter(self, client: &K8s) -> Option; + + async fn add_filter_target(&self, target: K8sSgFilterSpecTargetRef, client: &K8s); + + /// mix of [SgRouteFilter::to_singe_filter] and [SgRouteFilter::to_http_route_filter] + /// PluginInstanceId can be converted into `SgRouteFilter` or `HttpRouteFilter` + async fn to_route_filter_or_add_filter_target(&self, target: K8sSgFilterSpecTargetRef, client: &K8s) -> Option; +} + +impl PluginIdConv for PluginInstanceId { + fn to_singe_filter(&self, value: serde_json::Value, target: Option, namespace: &str) -> Option { + match self.name.clone() { + PluginInstanceName::Anon { uid: _ } => None, + PluginInstanceName::Named { name } => Some(SgSingeFilter { + name: Some(name.clone()), + namespace: namespace.to_owned(), + filter: K8sSgFilterSpecFilter { + code: self.code.to_string(), + name: Some(name), + config: value, + enable: true, + }, + target_ref: target, + }), + PluginInstanceName::Mono => None, + } + } + + async fn to_http_route_filter(self, client: &service::k8s::K8s) -> Option { + let filter_api: Api = client.get_namespace_api(); + if let Ok(filter) = filter_api.get(&self.name.to_string()).await { + if let Some(plugin) = filter.spec.filters.iter().find(|f| f.code == self.code && f.name == Some(self.name.to_string())) { + let value = plugin.config.clone(); + if self.code == SG_FILTER_HEADER_MODIFIER_CODE { + if let Ok(header) = serde_json::from_value::(value) { + let header_filter = HttpRequestHeaderFilter { + set: header.sets.map(|header_map| header_map.into_iter().map(|(k, v)| HttpHeader { name: k, value: v }).collect()), + add: None, + remove: header.remove, + }; + match header.kind { + SgFilterHeaderModifierKind::Request => Some(HttpRouteFilter::RequestHeaderModifier { + request_header_modifier: header_filter, + }), + SgFilterHeaderModifierKind::Response => Some(HttpRouteFilter::ResponseHeaderModifier { + response_header_modifier: header_filter, + }), + } + } else { + None + } + } else if self.code == SG_FILTER_REDIRECT_CODE { + if let Ok(redirect) = serde_json::from_value::(value) { + Some(HttpRouteFilter::RequestRedirect { + request_redirect: HttpRequestRedirectFilter { + scheme: redirect.scheme, + hostname: redirect.hostname, + path: redirect.path.map(|p| p.to_http_path_modifier()), + port: redirect.port, + status_code: redirect.status_code, + }, + }) + } else { + None + } + } else if self.code == SG_FILTER_REWRITE_CODE { + if let Ok(rewrite) = serde_json::from_value::(value) { + Some(HttpRouteFilter::URLRewrite { + url_rewrite: HttpUrlRewriteFilter { + hostname: rewrite.hostname, + path: rewrite.path.map(|p| p.to_http_path_modifier()), + }, + }) + } else { + None + } + } else { + None + } + } else { + None + } + } else { + None + } + } + + fn from_http_route_filter(route_filter: HttpRouteFilter) -> BoxResult { + let process_header_modifier = |header_modifier: HttpRequestHeaderFilter, modifier_kind: SgFilterHeaderModifierKind| -> BoxResult { + let mut sg_sets = HashMap::new(); + if let Some(adds) = header_modifier.add { + for add in adds { + sg_sets.insert(add.name, add.value); + } + } + if let Some(sets) = header_modifier.set { + for set in sets { + sg_sets.insert(set.name, set.value); + } + } + + Ok(PluginConfig { + id: PluginInstanceId { + code: SG_FILTER_HEADER_MODIFIER_CODE.into(), + name: PluginInstanceName::Mono {}, + }, + spec: serde_json::to_value(SgFilterHeaderModifier { + kind: modifier_kind, + sets: if sg_sets.is_empty() { None } else { Some(sg_sets) }, + remove: header_modifier.remove, + })?, + }) + }; + let sg_filter = match route_filter { + k8s_gateway_api::HttpRouteFilter::RequestHeaderModifier { request_header_modifier } => { + process_header_modifier(request_header_modifier, SgFilterHeaderModifierKind::Request)? + } + k8s_gateway_api::HttpRouteFilter::ResponseHeaderModifier { response_header_modifier } => { + process_header_modifier(response_header_modifier, SgFilterHeaderModifierKind::Response)? + } + k8s_gateway_api::HttpRouteFilter::RequestRedirect { request_redirect } => PluginConfig { + id: PluginInstanceId { + code: SG_FILTER_REDIRECT_CODE.into(), + name: PluginInstanceName::Mono {}, + }, + spec: serde_json::to_value(SgFilterRedirect { + scheme: request_redirect.scheme, + hostname: request_redirect.hostname, + path: request_redirect.path.map(|path| match path { + k8s_gateway_api::HttpPathModifier::ReplaceFullPath { replace_full_path } => SgHttpPathModifier { + kind: SgHttpPathModifierType::ReplaceFullPath, + value: replace_full_path, + }, + k8s_gateway_api::HttpPathModifier::ReplacePrefixMatch { replace_prefix_match } => SgHttpPathModifier { + kind: SgHttpPathModifierType::ReplacePrefixMatch, + value: replace_prefix_match, + }, + }), + port: request_redirect.port, + status_code: request_redirect.status_code, + })?, + }, + k8s_gateway_api::HttpRouteFilter::URLRewrite { url_rewrite } => PluginConfig { + id: PluginInstanceId { + code: SG_FILTER_REWRITE_CODE.into(), + name: PluginInstanceName::Mono {}, + }, + spec: serde_json::to_value(SgFilterRewrite { + hostname: url_rewrite.hostname, + path: url_rewrite.path.map(|path| match path { + k8s_gateway_api::HttpPathModifier::ReplaceFullPath { replace_full_path } => SgHttpPathModifier { + kind: SgHttpPathModifierType::ReplaceFullPath, + value: replace_full_path, + }, + k8s_gateway_api::HttpPathModifier::ReplacePrefixMatch { replace_prefix_match } => SgHttpPathModifier { + kind: SgHttpPathModifierType::ReplacePrefixMatch, + value: replace_prefix_match, + }, + }), + })?, + }, + k8s_gateway_api::HttpRouteFilter::RequestMirror { .. } => return Err("[SG.Common] HttpRoute [spec.rules.filters.type=RequestMirror] not supported yet".into()), + k8s_gateway_api::HttpRouteFilter::ExtensionRef { .. } => return Err("[SG.Common] HttpRoute [spec.rules.filters.type=ExtensionRef] not supported yet".into()), + }; + Ok(sg_filter) + } + + async fn add_filter_target(&self, target: K8sSgFilterSpecTargetRef, client: &K8s) { + todo!() + } + + async fn to_route_filter_or_add_filter_target(&self, target: K8sSgFilterSpecTargetRef, client: &K8s) -> Option { + todo!() + } +} + +pub(crate) trait PathModifierConv { + fn to_http_path_modifier(self) -> HttpPathModifier; +} + +impl PathModifierConv for SgHttpPathModifier { + fn to_http_path_modifier(self) -> HttpPathModifier { + match self.kind { + SgHttpPathModifierType::ReplaceFullPath => HttpPathModifier::ReplaceFullPath { replace_full_path: self.value }, + SgHttpPathModifierType::ReplacePrefixMatch => HttpPathModifier::ReplacePrefixMatch { replace_prefix_match: self.value }, + } + } +} diff --git a/crates/model/src/ext/k8s/convert/gateway_k8s_conv.rs b/crates/config/src/service/k8s/convert/gateway_k8s_conv.rs similarity index 80% rename from crates/model/src/ext/k8s/convert/gateway_k8s_conv.rs rename to crates/config/src/service/k8s/convert/gateway_k8s_conv.rs index 65b0c027..ceaeb518 100644 --- a/crates/model/src/ext/k8s/convert/gateway_k8s_conv.rs +++ b/crates/config/src/service/k8s/convert/gateway_k8s_conv.rs @@ -3,19 +3,21 @@ use std::collections::BTreeMap; use chrono::Utc; use k8s_gateway_api::{Gateway, GatewaySpec, GatewayTlsConfig, Listener, SecretObjectReference}; use k8s_openapi::{api::core::v1::Secret, ByteString}; -use kube::api::ObjectMeta; +use kube::{api::ObjectMeta, ResourceExt}; +use spacegate_model::PluginInstanceId; use crate::{ constants, - ext::k8s::{ - crd::sg_filter::{K8sSgFilterSpecFilter, K8sSgFilterSpecTargetRef, SgFilterTargetKind}, - helper_filter::SgSingeFilter, - }, + ext::k8s::crd::sg_filter::{K8sSgFilterSpecTargetRef, SgFilterTargetKind}, SgGateway, SgParameters, }; -impl SgGateway { - pub fn to_kube_gateway(self, namespace: &str) -> (Gateway, Option, Vec) { +use super::ToTarget; +pub(crate) trait SgGatewayConv { + fn to_kube_gateway(self, namespace: &str) -> (Gateway, Option, Vec); +} +impl SgGatewayConv for SgGateway { + fn to_kube_gateway(self, namespace: &str) -> (Gateway, Option, Vec) { let mut secret = None; let gateway = Gateway { @@ -66,6 +68,7 @@ impl SgGateway { options: None, }) } + _ => None, }, allowed_routes: None, }) @@ -75,32 +78,17 @@ impl SgGateway { status: None, }; - let sgfilters: Vec = self - .plugins - .into_iter() - .map(|f| SgSingeFilter { - name: f.name, - namespace: namespace.to_string(), - filter: K8sSgFilterSpecFilter { - code: f.code, - name: None, - enable: true, - config: f.spec, - }, - target_ref: K8sSgFilterSpecTargetRef { - kind: SgFilterTargetKind::Gateway.into(), - name: self.name.clone(), - namespace: Some(namespace.to_string()), - }, - }) - .collect(); - - (gateway, secret, sgfilters) + (gateway, secret, self.plugins) } } -impl SgParameters { - pub fn into_kube_gateway(self) -> BTreeMap { +pub(crate) trait SgParametersConv { + fn from_kube_gateway(gateway: &Gateway) -> Self; + fn into_kube_gateway(self) -> BTreeMap; +} + +impl SgParametersConv for SgParameters { + fn into_kube_gateway(self) -> BTreeMap { 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); @@ -120,7 +108,7 @@ impl SgParameters { ann } - pub fn from_kube_gateway(gateway: &Gateway) -> Self { + fn from_kube_gateway(gateway: &Gateway) -> Self { let gateway_annotations = gateway.metadata.annotations.clone(); if let Some(gateway_annotations) = gateway_annotations { SgParameters { @@ -139,3 +127,13 @@ impl SgParameters { } } } + +impl ToTarget for Gateway { + fn to_target_ref(&self) -> K8sSgFilterSpecTargetRef { + K8sSgFilterSpecTargetRef { + kind: SgFilterTargetKind::Gateway.into(), + name: self.name_any(), + namespace: self.namespace(), + } + } +} diff --git a/crates/config/src/service/k8s/convert/route_k8s_conv.rs b/crates/config/src/service/k8s/convert/route_k8s_conv.rs new file mode 100644 index 00000000..c859a998 --- /dev/null +++ b/crates/config/src/service/k8s/convert/route_k8s_conv.rs @@ -0,0 +1,344 @@ +use std::collections::BTreeMap; + +use futures_util::future::join_all; +use gateway::SgBackendProtocol; +use http_route::SgHttpRoute; +use hyper::client; +use k8s_gateway_api::{ + BackendObjectReference, CommonRouteSpec, HttpHeaderMatch, HttpPathMatch, HttpPathModifier, HttpQueryParamMatch, HttpRouteFilter, HttpRouteMatch, HttpUrlRewriteFilter, + ParentReference, +}; +use kube::api::ObjectMeta; +use spacegate_model::PluginInstanceId; + +use crate::{ + constants, + ext::k8s::{ + crd::http_spaceroute::{self, BackendRef, HttpBackendRef, HttpRouteRule, HttpSpaceroute, HttpSpacerouteSpec}, + helper_filter::SgSingeFilter, + }, + gateway, http_route, + service::k8s::K8s, + BackendHost, BoxResult, K8sServiceData, PluginConfig, SgBackendRef, SgHttpHeaderMatch, SgHttpPathMatch, SgHttpQueryMatch, SgHttpRouteMatch, SgHttpRouteRule, +}; + +use super::filter_k8s_conv::PluginIdConv as _; +pub(crate) trait SgHttpRouteConv { + /// Convert to HttpSpaceroute and SgSingeFilter + /// And SgSingeFilter ref kind is 'HTTPRoute' + async fn to_kube_httproute_route_filters(self, gateway_name: &str, name: &str, client: &K8s) -> (HttpSpaceroute, Vec); + /// Convert to HttpSpaceroute and SgSingeFilter + /// And SgSingeFilter ref kind is 'HTTPSpaceroute' + async fn to_kube_httproute_spaceroute_filters(self, gateway_name: &str, name: &str, client: &K8s) -> (HttpSpaceroute, Vec); + + async fn to_kube_httproute(self, gateway_name: &str, name: &str, client: &K8s, self_kind: &str) -> (HttpSpaceroute, Vec); +} + +impl SgHttpRouteConv for SgHttpRoute { + async fn to_kube_httproute_spaceroute_filters(self, gateway_name: &str, name: &str, client: &K8s) -> (HttpSpaceroute, Vec) { + self.to_kube_httproute(gateway_name, name, client, constants::RAW_HTTP_ROUTE_KIND_SPACEROUTE).await + } + + async fn to_kube_httproute_route_filters(self, gateway_name: &str, name: &str, client: &K8s) -> (HttpSpaceroute, Vec) { + self.to_kube_httproute(gateway_name, name, client, constants::RAW_HTTP_ROUTE_KIND_DEFAULT).await + } + + async fn to_kube_httproute(self, gateway_name: &str, name: &str, client: &K8s, self_kind: &str) -> (HttpSpaceroute, Vec) { + let httproute = HttpSpaceroute { + metadata: ObjectMeta { + labels: None, + name: Some(name.to_string()), + owner_references: None, + self_link: None, + annotations: Some(BTreeMap::from([(constants::ANNOTATION_RESOURCE_PRIORITY.to_string(), self.priority.to_string())])), + ..Default::default() + }, + spec: HttpSpacerouteSpec { + inner: CommonRouteSpec { + parent_refs: Some(vec![ParentReference { + group: None, + kind: Some("Gateway".to_string()), + namespace: Some(client.namespace.to_string()), + name: gateway_name.to_string(), + section_name: None, + port: None, + }]), + }, + hostnames: self.hostnames, + rules: Some(self.rules.into_iter().map(|r| r.into_kube_httproute()).collect::>()), + }, + status: None, + }; + (httproute, self.plugins) + } +} + +pub(crate) trait SgHttpRouteRuleConv { + /// # to_kube_httproute + /// `SgHttpRouteRule` to `HttpRouteRule`, include `HttpRouteFilter` and excluding `SgFilter`. + async fn into_kube_httproute(self, client: &K8s) -> HttpRouteRule; + fn from_kube_httproute(rule: http_spaceroute::HttpRouteRule) -> BoxResult; +} + +impl SgHttpRouteRuleConv for SgHttpRouteRule { + async fn into_kube_httproute(self, client: &K8s) -> HttpRouteRule { + let (matches, plugins): (Option>, Option>) = self + .matches + .map(|m_vec| { + let (a, b): (Vec<_>, Vec<_>) = m_vec.into_iter().map(|m| m.into_kube_httproute()).unzip(); + (Some(a.into_iter().flatten().collect()), Some(b.into_iter().flatten().collect())) + }) + .unwrap_or_default(); + HttpRouteRule { + matches: matches, + filters: plugins, + backend_refs: Some(self.backends.into_iter().map(|b| b.into_kube_httproute()).collect::>()), + timeout_ms: self.timeout_ms, + } + } + + fn from_kube_httproute(rule: http_spaceroute::HttpRouteRule) -> BoxResult { + Ok(SgHttpRouteRule { + matches: rule.matches.map(|m_vec| m_vec.into_iter().map(SgHttpRouteMatch::from_kube_httproute).collect::>()), + plugins: rule.filters.map(|f_vec| f_vec.into_iter().map(PluginConfig::from_http_route_filter).collect::>>()).transpose()?.unwrap_or_default(), + backends: rule + .backend_refs + .map(|b_vec| b_vec.into_iter().filter_map(|b| SgBackendRef::from_kube_httproute(b).transpose()).collect::>>()) + .transpose()? + .unwrap_or_default(), + timeout_ms: rule.timeout_ms, + }) + } +} + +pub(crate) trait SgHttpRouteMatchConv { + fn from_kube_httproute(route_match: HttpRouteMatch) -> SgHttpRouteMatch; + fn into_kube_httproute(self) -> (Vec, Vec); +} +impl SgHttpRouteMatchConv for SgHttpRouteMatch { + fn into_kube_httproute(self) -> (Vec, Vec) { + let (matchs, plugins) = if let Some(method_vec) = self.method { + method_vec + .into_iter() + .map(|m| { + let (path, plugin) = self + .path + .clone() + .map(|p| { + let (path, plugin) = p.into_kube_httproute(); + (Some(path), plugin) + }) + .unwrap_or((None, None)); + ( + HttpRouteMatch { + path: path, + headers: self.header.clone().map(|h_vec| h_vec.into_iter().map(|h| h.into_kube_httproute()).collect::>()), + query_params: self.query.clone().map(|q_vec| q_vec.into_iter().map(|q| q.into_kube_httproute()).collect::>()), + method: Some(m.0), + }, + plugin, + ) + }) + .unzip() + } else { + let (path, plugin) = self + .path + .clone() + .map(|p| { + let (path, plugin) = p.into_kube_httproute(); + (Some(path), plugin) + }) + .unwrap_or((None, None)); + ( + vec![HttpRouteMatch { + path: path, + headers: self.header.map(|h_vec| h_vec.into_iter().map(|h| h.into_kube_httproute()).collect::>()), + query_params: self.query.map(|q_vec| q_vec.into_iter().map(|q| q.into_kube_httproute()).collect::>()), + method: None, + }], + vec![plugin], + ) + }; + (matchs, plugins.into_iter().filter_map(|x| x).collect()) + } + + fn from_kube_httproute(route_match: HttpRouteMatch) -> SgHttpRouteMatch { + SgHttpRouteMatch { + method: route_match.method.map(|m_vec| vec![http_route::SgHttpMethodMatch(m_vec)]), + path: route_match.path.map(SgHttpPathMatch::from_kube_httproute), + header: route_match.headers.map(|h_vec| h_vec.into_iter().map(SgHttpHeaderMatch::from_kube_httproute).collect::>()), + query: route_match.query_params.map(|q_vec| q_vec.into_iter().map(SgHttpQueryMatch::from_kube_httproute).collect::>()), + } + } +} + +pub(crate) trait SgHttpPathMatchConv { + fn from_kube_httproute(path_match: HttpPathMatch) -> SgHttpPathMatch; + fn into_kube_httproute(self) -> (HttpPathMatch, Option); +} +impl SgHttpPathMatchConv for SgHttpPathMatch { + fn into_kube_httproute(self) -> (HttpPathMatch, Option) { + match self { + SgHttpPathMatch::Exact { value, replace } => ( + HttpPathMatch::Exact { value }, + replace.map(|r| HttpRouteFilter::URLRewrite { + url_rewrite: HttpUrlRewriteFilter { + hostname: None, + path: Some(HttpPathModifier::ReplaceFullPath { replace_full_path: r }), + }, + }), + ), + SgHttpPathMatch::Prefix { value, replace } => ( + HttpPathMatch::PathPrefix { value }, + replace.map(|r| HttpRouteFilter::URLRewrite { + url_rewrite: HttpUrlRewriteFilter { + hostname: None, + path: Some(HttpPathModifier::ReplacePrefixMatch { replace_prefix_match: r }), + }, + }), + ), + SgHttpPathMatch::RegExp { value, replace } => ( + HttpPathMatch::RegularExpression { value }, + replace.map(|r| HttpRouteFilter::URLRewrite { + url_rewrite: HttpUrlRewriteFilter { + hostname: None, + path: Some(HttpPathModifier::ReplaceFullPath { replace_full_path: r }), + }, + }), + ), + } + } + + fn from_kube_httproute(path_match: HttpPathMatch) -> SgHttpPathMatch { + match path_match { + HttpPathMatch::Exact { value } => SgHttpPathMatch::Exact { value, replace: None }, + HttpPathMatch::PathPrefix { value } => SgHttpPathMatch::Prefix { value, replace: None }, + HttpPathMatch::RegularExpression { value } => SgHttpPathMatch::RegExp { value, replace: None }, + } + } +} + +pub(crate) trait SgHttpHeaderMatchConv { + fn from_kube_httproute(header_match: HttpHeaderMatch) -> SgHttpHeaderMatch; + fn into_kube_httproute(self) -> HttpHeaderMatch; +} + +impl SgHttpHeaderMatchConv for SgHttpHeaderMatch { + //todo to plugin + fn into_kube_httproute(self) -> HttpHeaderMatch { + match self { + SgHttpHeaderMatch::Exact { name, value, replace } => HttpHeaderMatch::Exact { name, value }, + SgHttpHeaderMatch::RegExp { name, re, replace } => HttpHeaderMatch::RegularExpression { name, value: re }, + } + } + + fn from_kube_httproute(header_match: HttpHeaderMatch) -> SgHttpHeaderMatch { + match header_match { + HttpHeaderMatch::Exact { name, value } => SgHttpHeaderMatch::Exact { name, value, replace: None }, + HttpHeaderMatch::RegularExpression { name, value } => SgHttpHeaderMatch::Regular { name, re: value }, + } + } +} + +pub(crate) trait SgHttpQueryMatchConv { + fn into_kube_httproute(self) -> HttpQueryParamMatch; + fn from_kube_httproute(query_match: HttpQueryParamMatch) -> SgHttpQueryMatch; +} + +impl SgHttpQueryMatchConv for SgHttpQueryMatch { + fn into_kube_httproute(self) -> HttpQueryParamMatch { + match self { + SgHttpQueryMatch::Exact { key: name, value } => HttpQueryParamMatch::Exact { name, value }, + SgHttpQueryMatch::Regular { key: name, re: value } => HttpQueryParamMatch::RegularExpression { name, value }, + } + } + + fn from_kube_httproute(query_match: HttpQueryParamMatch) -> SgHttpQueryMatch { + match query_match { + HttpQueryParamMatch::Exact { name, value } => SgHttpQueryMatch::Exact { key: name, value }, + HttpQueryParamMatch::RegularExpression { name, value } => SgHttpQueryMatch::Regular { key: name, re: value }, + } + } +} + +pub(crate) trait SgBackendRefConv { + fn into_kube_httproute(self) -> HttpBackendRef; + fn from_kube_httproute(http_backend: HttpBackendRef) -> BoxResult>; +} + +impl SgBackendRefConv for SgBackendRef { + fn into_kube_httproute(self) -> HttpBackendRef { + let backend_inner_ref = match self.host { + BackendHost::Host { host } => { + let kind = match self.protocol { + Some(SgBackendProtocol::Https) => Some(constants::BANCKEND_KIND_EXTERNAL_HTTPS.to_string()), + _ => Some(constants::BANCKEND_KIND_EXTERNAL_HTTP.to_string()), + }; + BackendObjectReference { + group: None, + kind, + name: host, + namespace: None, + port: Some(self.port), + } + } + BackendHost::K8sService(k8s_param) => BackendObjectReference { + group: None, + kind: None, + name: k8s_param.name, + namespace: k8s_param.namespace, + port: Some(self.port), + }, + }; + HttpBackendRef { + backend_ref: Some(BackendRef { + weight: Some(self.weight), + timeout_ms: self.timeout_ms, + inner: backend_inner_ref, + }), + filters: Some(self.plugins.into_iter().filter_map(|f| f.to_http_route_filter()).collect()), + } + } + + fn from_kube_httproute(http_backend: HttpBackendRef) -> BoxResult> { + http_backend + .backend_ref + .map(|backend| { + let (protocol, backend_host) = if let Some(kind) = backend.inner.kind.as_ref() { + match kind.as_str() { + constants::BANCKEND_KIND_SERVICE => ( + None, + BackendHost::K8sService(K8sServiceData { + name: backend.inner.name, + namespace: backend.inner.namespace, + }), + ), + constants::BANCKEND_KIND_EXTERNAL_HTTP => (Some(gateway::SgBackendProtocol::Http), BackendHost::Host { host: backend.inner.name }), + constants::BANCKEND_KIND_EXTERNAL_HTTPS => (Some(gateway::SgBackendProtocol::Https), BackendHost::Host { host: backend.inner.name }), + _ => (None, BackendHost::Host { host: backend.inner.name }), + } + } else { + ( + None, + BackendHost::K8sService(K8sServiceData { + name: backend.inner.name, + namespace: backend.inner.namespace, + }), + ) + }; + Ok(SgBackendRef { + host: backend_host, + port: backend.inner.port.unwrap_or(80), + timeout_ms: backend.timeout_ms, + protocol, + weight: backend.weight.unwrap_or(1), + plugins: http_backend + .filters + .map(|f_vec| f_vec.into_iter().map(PluginConfig::from_http_route_filter).collect::>>()) + .transpose()? + .unwrap_or_default(), + }) + }) + .transpose() + } +} diff --git a/crates/config/src/service/k8s/create.rs b/crates/config/src/service/k8s/create.rs index 14629695..e0032fda 100644 --- a/crates/config/src/service/k8s/create.rs +++ b/crates/config/src/service/k8s/create.rs @@ -1,18 +1,26 @@ use k8s_gateway_api::Gateway; use k8s_openapi::api::core::v1::Secret; use kube::{api::PostParams, Api}; - -use crate::{ - k8s_crd::{http_spaceroute, sg_filter::SgFilter}, - service::backend::k8s::K8s, - BoxResult, +use spacegate_model::{ + ext::k8s::crd::{http_spaceroute, sg_filter::SgFilter}, + BoxError, PluginInstanceId, }; -use super::Create; +use crate::{service::Create, BoxResult}; + +use super::{ + convert::{ + filter_k8s_conv::PluginIdConv as _, + gateway_k8s_conv::{GatewayConv as _, SgGatewayConv as _}, + route_k8s_conv::SgHttpRouteConv as _, + ToTarget as _, + }, + K8s, +}; impl Create for K8s { async fn create_config_item_gateway(&self, _gateway_name: &str, gateway: crate::model::SgGateway) -> BoxResult<()> { - let (gateway, secret, filters) = gateway.to_kube_gateway(&self.namespace); + let (gateway, secret, plugin_ids) = gateway.to_kube_gateway(&self.namespace); let gateway_api: Api = self.get_namespace_api(); gateway_api.create(&PostParams::default(), &gateway).await?; @@ -22,20 +30,26 @@ impl Create for K8s { secret_api.create(&PostParams::default(), &secret).await?; } - for filter in filters { - let filter_api: Api = self.get_namespace_api(); - filter_api.create(&PostParams::default(), &filter.into()).await?; + for plugin_id in plugin_ids { + plugin_id.add_filter_target(gateway.to_target_ref(), self); } + Ok(()) } - async fn create_config_item_route(&self, _gateway_name: &str, route_name: &str, route: crate::model::SgHttpRoute) -> BoxResult<()> { - let (http_spaceroute, filters) = route.to_kube_httproute_spaceroute_filters(route_name, &self.namespace); + async fn create_config_item_route(&self, gateway_name: &str, route_name: &str, route: crate::model::SgHttpRoute) -> BoxResult<()> { + let http_spaceroute = route.to_kube_httproute_spaceroute_filters(gateway_name, route_name, &self.namespace); let http_spaceroute_api: Api = self.get_namespace_api(); http_spaceroute_api.create(&PostParams::default(), &http_spaceroute).await?; - for filter in filters { + Ok(()) + } + + async fn create_plugin(&self, id: &PluginInstanceId, value: serde_json::Value) -> Result<(), BoxError> { + let filter = id.to_singe_filter(value, None, &self.namespace); + + if let Some(filter) = filter { let filter_api: Api = self.get_namespace_api(); filter_api.create(&PostParams::default(), &filter.into()).await?; } diff --git a/crates/config/src/service/k8s/mod.rs b/crates/config/src/service/k8s/mod.rs index 26c69fba..35283ac2 100644 --- a/crates/config/src/service/k8s/mod.rs +++ b/crates/config/src/service/k8s/mod.rs @@ -2,6 +2,13 @@ use std::sync::Arc; use k8s_openapi::NamespaceResourceScope; +pub mod convert; +// pub mod create; +// pub mod delete; +// pub mod listen; +pub mod retrieve; +// pub mod update; + pub struct K8s { pub namespace: Arc, client: kube::Client, diff --git a/crates/config/src/service/k8s/retrieve.rs b/crates/config/src/service/k8s/retrieve.rs index 2982df33..cb821448 100644 --- a/crates/config/src/service/k8s/retrieve.rs +++ b/crates/config/src/service/k8s/retrieve.rs @@ -4,19 +4,17 @@ use http_route::SgHttpRouteRule; use k8s_gateway_api::{Gateway, HttpRoute, Listener}; use k8s_openapi::api::core::v1::Secret; use kube::{api::ListParams, Api, ResourceExt}; +use spacegate_model::ext::k8s::crd::{http_spaceroute::HttpSpaceroute, sg_filter::SgFilter}; -use super::Retrieve; use crate::{ constants::{self, GATEWAY_CLASS_NAME}, - k8s_crd::{ - http_spaceroute::HttpSpaceroute, - sg_filter::{K8sSgFilterSpecTargetRef, SgFilter, SgFilterTargetKind}, - }, model::{gateway, http_route, PluginConfig, SgGateway, SgHttpRoute}, - service::backend::k8s::K8s, + service::Retrieve, BoxError, BoxResult, }; +use super::K8s; + impl Retrieve for K8s { async fn retrieve_config_item_gateway(&self, gateway_name: &str) -> BoxResult> { let gateway_api: Api = self.get_namespace_api(); @@ -124,12 +122,48 @@ impl Retrieve for K8s { Ok(result) } + + async fn retrieve_all_plugins(&self) -> Result, BoxError> { + let filter_api: Api = self.get_namespace_api(); + + let result = filter_api.list(&ListParams::default()).await?.iter().map(|gateway| gateway.name_any()).collect(); + + Ok(result) + } + + async fn retrieve_plugin(&self, id: &spacegate_model::PluginInstanceId) -> Result, BoxError> { + let filter_api: Api = self.get_namespace_api(); + + match id.name { + spacegate_model::PluginInstanceName::Anon { uid } => Ok(None), + spacegate_model::PluginInstanceName::Named { name } => { + let result = if let Some(gateway_obj) = filter_api.get_opt(&name).await?.and_then(|gateway_obj| { + if gateway_obj.spec. == GATEWAY_CLASS_NAME { + Some(gateway_obj) + } else { + None + } + }) { + Some() + } else { + None + } + Ok(result) + } + spacegate_model::PluginInstanceName::Mono => Ok(None), + } + + } + + async fn retrieve_plugins_by_code(&self, code: &str) -> Result, BoxError> { + todo!() + } } impl K8s { async fn kube_gateway_2_sg_gateway(&self, gateway_obj: Gateway) -> BoxResult { let gateway_name = gateway_obj.name_any(); - let filters = self + let plugins = self .retrieve_config_item_filters(K8sSgFilterSpecTargetRef { kind: SgFilterTargetKind::Gateway.into(), name: gateway_name.clone(), @@ -140,7 +174,7 @@ impl K8s { name: gateway_name, parameters: SgParameters::from_kube_gateway(&gateway_obj), listeners: self.retrieve_config_item_listeners(&gateway_obj.spec.listeners).await?, - filters, + plugins, }; Ok(result) } @@ -161,7 +195,6 @@ impl K8s { }) .await?; Ok(SgHttpRoute { - gateway_name: gateway_refs.first().map(|x| x.name.clone()).unwrap_or_default(), hostnames: httpspace_route.spec.hostnames.clone(), plugins, rules: httpspace_route @@ -171,6 +204,7 @@ impl K8s { .transpose()? .unwrap_or_default(), priority, + route_name: httpspace_route.name_any(), }) } @@ -196,13 +230,7 @@ impl K8s { && target_ref.namespace.as_deref().unwrap_or("default").eq_ignore_ascii_case(&namespace) }) }) - .flat_map(|filter_obj| { - filter_obj.spec.filters.into_iter().map(|filter| PluginConfig { - code: filter.code, - name: filter.name, - spec: filter.config, - }) - }) + .flat_map(|filter_obj| filter_obj.spec.filters.into_iter().map(|filter| PluginConfig { spec: filter.config, id: todo!() })) .collect(); if !filter_objs.is_empty() { diff --git a/crates/model/src/ext/k8s.rs b/crates/model/src/ext/k8s.rs index 96d45728..e51dec9e 100644 --- a/crates/model/src/ext/k8s.rs +++ b/crates/model/src/ext/k8s.rs @@ -1,3 +1,2 @@ -pub mod convert; pub mod crd; pub mod helper_filter; diff --git a/crates/model/src/ext/k8s/convert.rs b/crates/model/src/ext/k8s/convert.rs deleted file mode 100644 index a26e5e42..00000000 --- a/crates/model/src/ext/k8s/convert.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod filter_k8s_conv; -pub mod gateway_k8s_conv; -pub mod route_k8s_conv; diff --git a/crates/model/src/ext/k8s/convert/filter_k8s_conv.rs b/crates/model/src/ext/k8s/convert/filter_k8s_conv.rs deleted file mode 100644 index b22f8e67..00000000 --- a/crates/model/src/ext/k8s/convert/filter_k8s_conv.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::collections::HashMap; - -use k8s_gateway_api::{HttpHeader, HttpPathModifier, HttpRequestHeaderFilter, HttpRequestRedirectFilter, HttpRouteFilter, HttpUrlRewriteFilter}; - -use crate::{ - constants, - ext::k8s::{ - crd::sg_filter::{K8sSgFilterSpecFilter, K8sSgFilterSpecTargetRef}, - helper_filter::SgSingeFilter, - }, - plugin::{ - gatewayapi_support_filter::{ - SgFilterHeaderModifier, SgFilterHeaderModifierKind, SgFilterRedirect, SgFilterRewrite, SgHttpPathModifier, SgHttpPathModifierType, SG_FILTER_HEADER_MODIFIER_CODE, - SG_FILTER_REDIRECT_CODE, SG_FILTER_REWRITE_CODE, - }, - PluginConfig, - }, - BoxResult, PluginInstanceId, -}; - -impl PluginConfig { - /// # to_singe_filter - /// `to_single_filter` method and [SgRouteFilter::to_http_route_filter] method both convert from - /// `SgRouteFilter`to the k8s model. The difference lies in that `to_single_filter` only includes - /// the k8s object of `SGFilter`, whereas `to_http_route_filter` is used to convert to `HttpRouteFilter`, - /// a filter defined in the Gateway API. - pub fn to_singe_filter(self, target: K8sSgFilterSpecTargetRef) -> Option { - if self.code == SG_FILTER_HEADER_MODIFIER_CODE || self.code == SG_FILTER_REDIRECT_CODE || self.code == SG_FILTER_REWRITE_CODE { - None - } else { - Some(SgSingeFilter { - name: self.name.clone(), - namespace: target.namespace.clone().unwrap_or(constants::DEFAULT_NAMESPACE.to_string()), - filter: K8sSgFilterSpecFilter { - code: self.code, - name: self.name, - config: self.spec, - enable: true, - }, - target_ref: target, - }) - } - } - - /// # to_http_route_filter - /// ref [SgRouteFilter::to_singe_filter] - pub fn to_http_route_filter(self) -> Option { - if self.code == SG_FILTER_HEADER_MODIFIER_CODE { - if let Ok(header) = serde_json::from_value::(self.spec) { - let header_filter = HttpRequestHeaderFilter { - set: header.sets.map(|header_map| header_map.into_iter().map(|(k, v)| HttpHeader { name: k, value: v }).collect()), - add: None, - remove: header.remove, - }; - match header.kind { - SgFilterHeaderModifierKind::Request => Some(HttpRouteFilter::RequestHeaderModifier { - request_header_modifier: header_filter, - }), - SgFilterHeaderModifierKind::Response => Some(HttpRouteFilter::ResponseHeaderModifier { - response_header_modifier: header_filter, - }), - } - } else { - None - } - } else if self.code == SG_FILTER_REDIRECT_CODE { - if let Ok(redirect) = serde_json::from_value::(self.spec) { - Some(HttpRouteFilter::RequestRedirect { - request_redirect: HttpRequestRedirectFilter { - scheme: redirect.scheme, - hostname: redirect.hostname, - path: redirect.path.map(|p| p.to_http_path_modifier()), - port: redirect.port, - status_code: redirect.status_code, - }, - }) - } else { - None - } - } else if self.code == SG_FILTER_REWRITE_CODE { - if let Ok(rewrite) = serde_json::from_value::(self.spec) { - Some(HttpRouteFilter::URLRewrite { - url_rewrite: HttpUrlRewriteFilter { - hostname: rewrite.hostname, - path: rewrite.path.map(|p| p.to_http_path_modifier()), - }, - }) - } else { - None - } - } else { - None - } - } - - pub(crate) fn from_http_route_filter(route_filter: HttpRouteFilter) -> BoxResult { - let process_header_modifier = |header_modifier: HttpRequestHeaderFilter, modifier_kind: SgFilterHeaderModifierKind| -> BoxResult { - let mut sg_sets = HashMap::new(); - if let Some(adds) = header_modifier.add { - for add in adds { - sg_sets.insert(add.name, add.value); - } - } - if let Some(sets) = header_modifier.set { - for set in sets { - sg_sets.insert(set.name, set.value); - } - } - - Ok(PluginConfig { - code: SG_FILTER_HEADER_MODIFIER_CODE.to_string(), - name: None, - spec: serde_json::to_value(SgFilterHeaderModifier { - kind: modifier_kind, - sets: if sg_sets.is_empty() { None } else { Some(sg_sets) }, - remove: header_modifier.remove, - })?, - }) - }; - let sg_filter = match route_filter { - k8s_gateway_api::HttpRouteFilter::RequestHeaderModifier { request_header_modifier } => { - process_header_modifier(request_header_modifier, SgFilterHeaderModifierKind::Request)? - } - k8s_gateway_api::HttpRouteFilter::ResponseHeaderModifier { response_header_modifier } => { - process_header_modifier(response_header_modifier, SgFilterHeaderModifierKind::Response)? - } - k8s_gateway_api::HttpRouteFilter::RequestRedirect { request_redirect } => PluginConfig { - code: SG_FILTER_REDIRECT_CODE.to_string(), - name: None, - spec: serde_json::to_value(SgFilterRedirect { - scheme: request_redirect.scheme, - hostname: request_redirect.hostname, - path: request_redirect.path.map(|path| match path { - k8s_gateway_api::HttpPathModifier::ReplaceFullPath { replace_full_path } => SgHttpPathModifier { - kind: SgHttpPathModifierType::ReplaceFullPath, - value: replace_full_path, - }, - k8s_gateway_api::HttpPathModifier::ReplacePrefixMatch { replace_prefix_match } => SgHttpPathModifier { - kind: SgHttpPathModifierType::ReplacePrefixMatch, - value: replace_prefix_match, - }, - }), - port: request_redirect.port, - status_code: request_redirect.status_code, - })?, - }, - k8s_gateway_api::HttpRouteFilter::URLRewrite { url_rewrite } => PluginConfig { - code: SG_FILTER_REWRITE_CODE.to_string(), - name: None, - spec: serde_json::to_value(SgFilterRewrite { - hostname: url_rewrite.hostname, - path: url_rewrite.path.map(|path| match path { - k8s_gateway_api::HttpPathModifier::ReplaceFullPath { replace_full_path } => SgHttpPathModifier { - kind: SgHttpPathModifierType::ReplaceFullPath, - value: replace_full_path, - }, - k8s_gateway_api::HttpPathModifier::ReplacePrefixMatch { replace_prefix_match } => SgHttpPathModifier { - kind: SgHttpPathModifierType::ReplacePrefixMatch, - value: replace_prefix_match, - }, - }), - })?, - }, - k8s_gateway_api::HttpRouteFilter::RequestMirror { .. } => return Err("[SG.Common] HttpRoute [spec.rules.filters.type=RequestMirror] not supported yet".into()), - k8s_gateway_api::HttpRouteFilter::ExtensionRef { .. } => return Err("[SG.Common] HttpRoute [spec.rules.filters.type=ExtensionRef] not supported yet".into()), - }; - Ok(sg_filter) - } -} - -impl SgHttpPathModifier { - pub fn to_http_path_modifier(self) -> HttpPathModifier { - match self.kind { - SgHttpPathModifierType::ReplaceFullPath => HttpPathModifier::ReplaceFullPath { replace_full_path: self.value }, - SgHttpPathModifierType::ReplacePrefixMatch => HttpPathModifier::ReplacePrefixMatch { replace_prefix_match: self.value }, - } - } -} diff --git a/crates/model/src/ext/k8s/convert/route_k8s_conv.rs b/crates/model/src/ext/k8s/convert/route_k8s_conv.rs deleted file mode 100644 index a9c5d614..00000000 --- a/crates/model/src/ext/k8s/convert/route_k8s_conv.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::collections::BTreeMap; - -use gateway::SgBackendProtocol; -use http_route::SgHttpRoute; -use k8s_gateway_api::{BackendObjectReference, CommonRouteSpec, HttpHeaderMatch, HttpPathMatch, HttpQueryParamMatch, HttpRouteMatch, ParentReference}; -use kube::api::ObjectMeta; - -use crate::{ - constants, - ext::k8s::{ - crd::{ - http_spaceroute::{self, BackendRef, HttpBackendRef, HttpRouteRule, HttpSpaceroute, HttpSpacerouteSpec}, - sg_filter::K8sSgFilterSpecTargetRef, - }, - helper_filter::SgSingeFilter, - }, - gateway, http_route, BackendHost, BoxResult, K8sServiceData, PluginConfig, SgBackendRef, SgHttpHeaderMatch, SgHttpPathMatch, SgHttpQueryMatch, SgHttpRouteMatch, - SgHttpRouteRule, -}; - -impl SgHttpRoute { - /// Convert to HttpSpaceroute and SgSingeFilter - /// And SgSingeFilter ref kind is 'HTTPSpaceroute' - pub fn to_kube_httproute_spaceroute_filters(self, name: &str, namespace: &str) -> (HttpSpaceroute, Vec) { - self.to_kube_httproute(name, namespace, constants::RAW_HTTP_ROUTE_KIND_SPACEROUTE) - } - - /// Convert to HttpSpaceroute and SgSingeFilter - /// And SgSingeFilter ref kind is 'HTTPRoute' - pub fn to_kube_httproute_route_filters(self, name: &str, namespace: &str) -> (HttpSpaceroute, Vec) { - self.to_kube_httproute(name, namespace, constants::RAW_HTTP_ROUTE_KIND_DEFAULT) - } - - pub fn to_kube_httproute(self, name: &str, namespace: &str, self_kind: &str) -> (HttpSpaceroute, Vec) { - let mut sgfilters: Vec = self - .rules - .iter() - .flat_map(|r| { - let mut route_filters_vec = r - .plugins - .clone() - .into_iter() - .filter_map(|f| { - f.to_singe_filter(K8sSgFilterSpecTargetRef { - kind: self_kind.to_string(), - name: name.to_string(), - namespace: Some(namespace.to_string()), - }) - }) - .collect::>(); - - let mut b_singe_f_vec = r - .backends - .iter() - .flat_map(|b| { - b.plugins - .iter() - .filter_map(|b_f| { - b_f.clone().to_singe_filter(K8sSgFilterSpecTargetRef { - kind: self_kind.to_string(), - name: name.to_string(), - namespace: Some(namespace.to_string()), - }) - }) - .collect::>() - }) - .collect::>(); - - route_filters_vec.append(&mut b_singe_f_vec); - route_filters_vec - }) - .collect::>(); - - sgfilters.append(&mut self.plugins.into_iter().filter_map(|f| todo!("convert")).collect::>()); - - let httproute = HttpSpaceroute { - metadata: ObjectMeta { - labels: None, - name: Some(name.to_string()), - owner_references: None, - self_link: None, - annotations: Some(BTreeMap::from([(constants::ANNOTATION_RESOURCE_PRIORITY.to_string(), self.priority.to_string())])), - ..Default::default() - }, - spec: HttpSpacerouteSpec { - inner: CommonRouteSpec { - parent_refs: Some(vec![ParentReference { - group: None, - kind: Some("Gateway".to_string()), - namespace: Some(namespace.to_string()), - name: self.gateway_name.clone(), - section_name: None, - port: None, - }]), - }, - hostnames: self.hostnames, - rules: Some(self.rules.into_iter().map(|r| r.into_kube_httproute()).collect::>()), - }, - status: None, - }; - (httproute, sgfilters) - } -} - -impl SgHttpRouteRule { - /// # to_kube_httproute - /// `SgHttpRouteRule` to `HttpRouteRule`, include `HttpRouteFilter` and excluding `SgFilter`. - pub(crate) fn into_kube_httproute(self) -> HttpRouteRule { - HttpRouteRule { - matches: self.matches.map(|m_vec| m_vec.into_iter().flat_map(|m| m.into_kube_httproute()).collect::>()), - filters: Some(self.plugins.into_iter().filter_map(|f| f.to_http_route_filter()).collect::>()), - backend_refs: Some(self.backends.into_iter().map(|b| b.into_kube_httproute()).collect::>()), - timeout_ms: self.timeout_ms, - } - } - - pub(crate) fn from_kube_httproute(rule: http_spaceroute::HttpRouteRule) -> BoxResult { - Ok(SgHttpRouteRule { - matches: rule.matches.map(|m_vec| m_vec.into_iter().map(SgHttpRouteMatch::from_kube_httproute).collect::>()), - plugins: rule.filters.map(|f_vec| f_vec.into_iter().map(PluginConfig::from_http_route_filter).collect::>>()).transpose()?.unwrap_or_default(), - backends: rule - .backend_refs - .map(|b_vec| b_vec.into_iter().filter_map(|b| SgBackendRef::from_kube_httproute(b).transpose()).collect::>>()) - .transpose()? - .unwrap_or_default(), - timeout_ms: rule.timeout_ms, - }) - } -} - -impl SgHttpRouteMatch { - pub(crate) fn into_kube_httproute(self) -> Vec { - if let Some(method_vec) = self.method { - method_vec - .into_iter() - .map(|m| HttpRouteMatch { - path: self.path.clone().map(|p| p.into_kube_httproute()), - headers: self.header.clone().map(|h_vec| h_vec.into_iter().map(|h| h.into_kube_httproute()).collect::>()), - query_params: self.query.clone().map(|q_vec| q_vec.into_iter().map(|q| q.into_kube_httproute()).collect::>()), - method: Some(m.0), - }) - .collect::>() - } else { - vec![HttpRouteMatch { - path: self.path.map(|p| p.into_kube_httproute()), - headers: self.header.map(|h_vec| h_vec.into_iter().map(|h| h.into_kube_httproute()).collect::>()), - query_params: self.query.map(|q_vec| q_vec.into_iter().map(|q| q.into_kube_httproute()).collect::>()), - method: None, - }] - } - } - - pub(crate) fn from_kube_httproute(route_match: HttpRouteMatch) -> SgHttpRouteMatch { - SgHttpRouteMatch { - method: route_match.method.map(|m_vec| vec![http_route::SgHttpMethodMatch(m_vec)]), - path: route_match.path.map(SgHttpPathMatch::from_kube_httproute), - header: route_match.headers.map(|h_vec| h_vec.into_iter().map(SgHttpHeaderMatch::from_kube_httproute).collect::>()), - query: route_match.query_params.map(|q_vec| q_vec.into_iter().map(SgHttpQueryMatch::from_kube_httproute).collect::>()), - } - } -} - -impl SgHttpPathMatch { - pub(crate) fn into_kube_httproute(self) -> HttpPathMatch { - match self { - SgHttpPathMatch::Exact(value) => HttpPathMatch::Exact { value }, - SgHttpPathMatch::Prefix(value) => HttpPathMatch::PathPrefix { value }, - SgHttpPathMatch::Regular(value) => HttpPathMatch::RegularExpression { value }, - } - } - - pub(crate) fn from_kube_httproute(path_match: HttpPathMatch) -> SgHttpPathMatch { - match path_match { - HttpPathMatch::Exact { value } => SgHttpPathMatch::Exact(value), - HttpPathMatch::PathPrefix { value } => SgHttpPathMatch::Prefix(value), - HttpPathMatch::RegularExpression { value } => SgHttpPathMatch::Regular(value), - } - } -} - -impl SgHttpHeaderMatch { - pub(crate) fn into_kube_httproute(self) -> HttpHeaderMatch { - match self { - SgHttpHeaderMatch::Exact { name, value } => HttpHeaderMatch::Exact { name, value }, - SgHttpHeaderMatch::Regular { name, re: value } => HttpHeaderMatch::RegularExpression { name, value }, - } - } - - pub(crate) fn from_kube_httproute(header_match: HttpHeaderMatch) -> SgHttpHeaderMatch { - match header_match { - HttpHeaderMatch::Exact { name, value } => SgHttpHeaderMatch::Exact { name, value }, - HttpHeaderMatch::RegularExpression { name, value } => SgHttpHeaderMatch::Regular { name, re: value }, - } - } -} - -impl SgHttpQueryMatch { - pub(crate) fn into_kube_httproute(self) -> HttpQueryParamMatch { - match self { - SgHttpQueryMatch::Exact { key: name, value } => HttpQueryParamMatch::Exact { name, value }, - SgHttpQueryMatch::Regular { key: name, re: value } => HttpQueryParamMatch::RegularExpression { name, value }, - } - } - - pub(crate) fn from_kube_httproute(query_match: HttpQueryParamMatch) -> SgHttpQueryMatch { - match query_match { - HttpQueryParamMatch::Exact { name, value } => SgHttpQueryMatch::Exact { key: name, value }, - HttpQueryParamMatch::RegularExpression { name, value } => SgHttpQueryMatch::Regular { key: name, re: value }, - } - } -} - -impl SgBackendRef { - pub(crate) fn into_kube_httproute(self) -> HttpBackendRef { - let backend_inner_ref = match self.host { - BackendHost::Host { host } => { - let kind = match self.protocol { - Some(SgBackendProtocol::Https) => Some(constants::BANCKEND_KIND_EXTERNAL_HTTPS.to_string()), - _ => Some(constants::BANCKEND_KIND_EXTERNAL_HTTP.to_string()), - }; - BackendObjectReference { - group: None, - kind, - name: host, - namespace: None, - port: Some(self.port), - } - } - BackendHost::K8sService(k8s_param) => BackendObjectReference { - group: None, - kind: None, - name: k8s_param.name, - namespace: k8s_param.namespace, - port: Some(self.port), - }, - }; - HttpBackendRef { - backend_ref: Some(BackendRef { - weight: Some(self.weight), - timeout_ms: self.timeout_ms, - inner: backend_inner_ref, - }), - filters: Some(self.plugins.into_iter().filter_map(|f| f.to_http_route_filter()).collect()), - } - } - - pub(crate) fn from_kube_httproute(http_backend: HttpBackendRef) -> BoxResult> { - http_backend - .backend_ref - .map(|backend| { - let (protocol, backend_host) = if let Some(kind) = backend.inner.kind.as_ref() { - match kind.as_str() { - constants::BANCKEND_KIND_SERVICE => ( - None, - BackendHost::K8sService(K8sServiceData { - name: backend.inner.name, - namespace: backend.inner.namespace, - }), - ), - constants::BANCKEND_KIND_EXTERNAL_HTTP => (Some(gateway::SgBackendProtocol::Http), BackendHost::Host { host: backend.inner.name }), - constants::BANCKEND_KIND_EXTERNAL_HTTPS => (Some(gateway::SgBackendProtocol::Https), BackendHost::Host { host: backend.inner.name }), - _ => (None, BackendHost::Host { host: backend.inner.name }), - } - } else { - ( - None, - BackendHost::K8sService(K8sServiceData { - name: backend.inner.name, - namespace: backend.inner.namespace, - }), - ) - }; - Ok(SgBackendRef { - host: backend_host, - port: backend.inner.port.unwrap_or(80), - timeout_ms: backend.timeout_ms, - protocol, - weight: backend.weight.unwrap_or(1), - plugins: http_backend - .filters - .map(|f_vec| f_vec.into_iter().map(PluginConfig::from_http_route_filter).collect::>>()) - .transpose()? - .unwrap_or_default(), - }) - }) - .transpose() - } -} diff --git a/crates/model/src/ext/k8s/crd/sg_filter.rs b/crates/model/src/ext/k8s/crd/sg_filter.rs index 12dc5fc1..e36f9cf8 100644 --- a/crates/model/src/ext/k8s/crd/sg_filter.rs +++ b/crates/model/src/ext/k8s/crd/sg_filter.rs @@ -31,9 +31,15 @@ pub struct K8sSgFilterSpecTargetRef { /// - gateway /// - httproute /// - httpspaceroute + /// - HttpspacerouteRule + /// - HttpspacerouteBackend pub kind: String, pub name: String, + /// if namespace is None, use SgFilter's namespace pub namespace: Option, + /// if kind is `HttpspacerouteRule` or `HttpspacerouteBackend`, parent_ref must be set, otherwise target_ref will be ignored + /// value should be `Httpspaceroute`'s name + pub parent_ref: Option, } impl PartialEq for K8sSgFilterSpecTargetRef { @@ -46,6 +52,8 @@ pub enum SgFilterTargetKind { Gateway, Httproute, Httpspaceroute, + HttpspacerouteRule, + HttpspacerouteBackend, } impl From for String { @@ -54,6 +62,8 @@ impl From for String { SgFilterTargetKind::Gateway => "Gateway".to_string(), SgFilterTargetKind::Httproute => "HTTPRoute".to_string(), SgFilterTargetKind::Httpspaceroute => "HTTPSpaceroute".to_string(), + SgFilterTargetKind::HttpspacerouteRule => "HTTPSpacerouteRule".to_string(), + SgFilterTargetKind::HttpspacerouteBackend => "HTTPSpacerouteBackend".to_string(), } } } diff --git a/crates/model/src/ext/k8s/helper_filter.rs b/crates/model/src/ext/k8s/helper_filter.rs index dc37b8b0..808cdc57 100644 --- a/crates/model/src/ext/k8s/helper_filter.rs +++ b/crates/model/src/ext/k8s/helper_filter.rs @@ -7,32 +7,32 @@ pub struct SgSingeFilter { pub name: Option, pub namespace: String, pub filter: super::crd::sg_filter::K8sSgFilterSpecFilter, - pub target_ref: super::crd::sg_filter::K8sSgFilterSpecTargetRef, + pub target_ref: Option, } -impl PartialEq for SgSingeFilter { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - && self.namespace == other.namespace - && self.filter.code == other.filter.code - && self.target_ref.kind == other.target_ref.kind - && self.target_ref.name == other.target_ref.name - && self.target_ref.namespace.as_ref().unwrap_or(&constants::DEFAULT_NAMESPACE.to_string()) - == other.target_ref.namespace.as_ref().unwrap_or(&constants::DEFAULT_NAMESPACE.to_string()) - } -} +// impl PartialEq for SgSingeFilter { +// fn eq(&self, other: &Self) -> bool { +// self.name == other.name +// && self.namespace == other.namespace +// && self.filter.code == other.filter.code +// && self.target_ref.kind == other.target_ref.kind +// && self.target_ref.name == other.target_ref.name +// && self.target_ref.namespace.as_ref().unwrap_or(&constants::DEFAULT_NAMESPACE.to_string()) +// == other.target_ref.namespace.as_ref().unwrap_or(&constants::DEFAULT_NAMESPACE.to_string()) +// } +// } -impl Eq for SgSingeFilter {} +// impl Eq for SgSingeFilter {} -impl Hash for SgSingeFilter { - fn hash(&self, state: &mut H) { - self.name.hash(state); - self.namespace.hash(state); - self.target_ref.kind.hash(state); - self.target_ref.name.hash(state); - self.target_ref.namespace.hash(state); - } -} +// impl Hash for SgSingeFilter { +// fn hash(&self, state: &mut H) { +// self.name.hash(state); +// self.namespace.hash(state); +// self.target_ref.kind.hash(state); +// self.target_ref.name.hash(state); +// self.target_ref.namespace.hash(state); +// } +// } impl From for super::crd::sg_filter::SgFilter { fn from(value: SgSingeFilter) -> Self { @@ -44,7 +44,7 @@ impl From for super::crd::sg_filter::SgFilter { }, spec: super::crd::sg_filter::K8sSgFilterSpec { filters: vec![value.filter.clone()], - target_refs: vec![value.target_ref.clone()], + target_refs: if let Some(target_ref) = value.target_ref { vec![target_ref] } else { vec![] }, }, } } diff --git a/crates/shell/res/spacegate-gateway.yaml b/crates/shell/res/spacegate-gateway.yaml index ef81c40a..1d5d1e0a 100644 --- a/crates/shell/res/spacegate-gateway.yaml +++ b/crates/shell/res/spacegate-gateway.yaml @@ -59,6 +59,9 @@ spec: namespace: type: string minLength: 1 + parentRef: + type: string + minLength: 1 required: - kind - name diff --git a/resource/docker/spacegate-admin-server/admin-server.yaml b/resource/docker/spacegate-admin-server/admin-server.yaml index 541f662a..fb49eb96 100644 --- a/resource/docker/spacegate-admin-server/admin-server.yaml +++ b/resource/docker/spacegate-admin-server/admin-server.yaml @@ -112,7 +112,7 @@ spec: serviceAccountName: spacegate-admin containers: - name: spacegate-admin - image: ecfront/spacegate-admin:latest + image: ecfront/spacegate-admin-server:latest imagePullPolicy: IfNotPresent ports: - containerPort: 9080