Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
RWDai committed Apr 18, 2024
1 parent 803664a commit d349e4c
Show file tree
Hide file tree
Showing 15 changed files with 719 additions and 554 deletions.
9 changes: 9 additions & 0 deletions crates/config/src/service/k8s/convert.rs
Original file line number Diff line number Diff line change
@@ -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;
}
222 changes: 222 additions & 0 deletions crates/config/src/service/k8s/convert/filter_k8s_conv.rs
Original file line number Diff line number Diff line change
@@ -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<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.
fn to_singe_filter(&self, value: serde_json::Value, target: Option<K8sSgFilterSpecTargetRef>, namespace: &str) -> Option<SgSingeFilter>;

/// # to_http_route_filter
/// ref [SgRouteFilter::to_singe_filter]
async fn to_http_route_filter(self, client: &K8s) -> Option<HttpRouteFilter>;

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<HttpRouteFilter>;
}

impl PluginIdConv for PluginInstanceId {
fn to_singe_filter(&self, value: serde_json::Value, target: Option<K8sSgFilterSpecTargetRef>, namespace: &str) -> Option<SgSingeFilter> {
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<HttpRouteFilter> {
let filter_api: Api<SgFilter> = 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::<SgFilterHeaderModifier>(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::<SgFilterRedirect>(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::<SgFilterRewrite>(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<PluginConfig> {
let process_header_modifier = |header_modifier: HttpRequestHeaderFilter, modifier_kind: SgFilterHeaderModifierKind| -> BoxResult<PluginConfig> {
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<HttpRouteFilter> {
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 },
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Secret>, Vec<SgSingeFilter>) {
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 {
Expand Down Expand Up @@ -66,6 +68,7 @@ impl SgGateway {
options: None,
})
}
_ => None,
},
allowed_routes: None,
})
Expand All @@ -75,32 +78,17 @@ impl SgGateway {
status: None,
};

let sgfilters: Vec<SgSingeFilter> = 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<String, String> {
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);
Expand All @@ -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 {
Expand All @@ -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(),
}
}
}
Loading

0 comments on commit d349e4c

Please sign in to comment.