mirror of
https://github.com/faas-rs/faasd-in-rust.git
synced 2025-06-08 07:55:04 +00:00
feat: 添加namespace相关api (#108)
This commit is contained in:
parent
308e9bcc5d
commit
ce81aa84c5
@ -2,6 +2,7 @@ pub mod cni;
|
|||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod function;
|
pub mod function;
|
||||||
|
pub mod namespace;
|
||||||
pub mod oci_image;
|
pub mod oci_image;
|
||||||
pub mod snapshot;
|
pub mod snapshot;
|
||||||
pub mod spec;
|
pub mod spec;
|
||||||
|
144
crates/faas-containerd/src/impls/namespace.rs
Normal file
144
crates/faas-containerd/src/impls/namespace.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use containerd_client::services::v1::{
|
||||||
|
CreateNamespaceRequest, DeleteNamespaceRequest, ListNamespacesRequest, Namespace,
|
||||||
|
UpdateNamespaceRequest,
|
||||||
|
};
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
|
use super::ContainerdService;
|
||||||
|
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
pub enum NamespaceServiceError {
|
||||||
|
AlreadyExists,
|
||||||
|
NotFound,
|
||||||
|
Internal(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<tonic::Status> for NamespaceServiceError {
|
||||||
|
fn from(status: tonic::Status) -> Self {
|
||||||
|
use tonic::Code::*;
|
||||||
|
match status.code() {
|
||||||
|
NotFound => NamespaceServiceError::NotFound,
|
||||||
|
AlreadyExists => NamespaceServiceError::AlreadyExists,
|
||||||
|
_ => NamespaceServiceError::Internal(status.message().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerdService {
|
||||||
|
// 创建命名空间
|
||||||
|
pub async fn create_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: &str,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> Result<(), NamespaceServiceError> {
|
||||||
|
let exist = self.namespace_exist(namespace).await?;
|
||||||
|
if exist.is_some() {
|
||||||
|
log::info!("Namespace {} already exists", namespace);
|
||||||
|
return Err(NamespaceServiceError::AlreadyExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut c = self.client.namespaces();
|
||||||
|
let ns_name = namespace;
|
||||||
|
|
||||||
|
let namespace = Namespace {
|
||||||
|
name: namespace.to_string(),
|
||||||
|
labels,
|
||||||
|
};
|
||||||
|
let req = CreateNamespaceRequest {
|
||||||
|
namespace: Some(namespace),
|
||||||
|
};
|
||||||
|
c.create(req).await.map_err(|e| {
|
||||||
|
log::error!("Failed to create namespace: {}", e);
|
||||||
|
NamespaceServiceError::Internal(e.to_string())
|
||||||
|
})?;
|
||||||
|
log::info!("Namespace {} created", ns_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除命名空间
|
||||||
|
pub async fn delete_namespace(&self, namespace: &str) -> Result<(), NamespaceServiceError> {
|
||||||
|
let exist = self.namespace_exist(namespace).await?;
|
||||||
|
if exist.is_none() {
|
||||||
|
log::info!("Namespace {} not found", namespace);
|
||||||
|
return Err(NamespaceServiceError::NotFound);
|
||||||
|
}
|
||||||
|
let mut c = self.client.namespaces();
|
||||||
|
let ns_name = namespace;
|
||||||
|
let req = DeleteNamespaceRequest {
|
||||||
|
name: namespace.to_string(),
|
||||||
|
};
|
||||||
|
c.delete(req).await.map_err(|e| {
|
||||||
|
log::error!("Failed to delete namespace: {}", e);
|
||||||
|
NamespaceServiceError::Internal(e.to_string())
|
||||||
|
})?;
|
||||||
|
log::info!("Namespace {} deleted", ns_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断命名空间是否存在
|
||||||
|
pub async fn namespace_exist(
|
||||||
|
&self,
|
||||||
|
namespace: &str,
|
||||||
|
) -> Result<Option<Namespace>, NamespaceServiceError> {
|
||||||
|
let mut c = self.client.namespaces();
|
||||||
|
let req = ListNamespacesRequest {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let ns_list_resp = c.list(req).await.map_err(|e| {
|
||||||
|
log::error!("Failed to list namespaces: {}", e);
|
||||||
|
NamespaceServiceError::Internal(e.to_string())
|
||||||
|
})?;
|
||||||
|
let ns_list = ns_list_resp.into_inner().namespaces;
|
||||||
|
for ns in ns_list {
|
||||||
|
if ns.name == namespace {
|
||||||
|
return Ok(Some(ns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取命名空间列表
|
||||||
|
pub async fn list_namespace(&self) -> Result<Vec<Namespace>, NamespaceServiceError> {
|
||||||
|
let mut c = self.client.namespaces();
|
||||||
|
let req = ListNamespacesRequest {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let ns_list_resp = c.list(req).await.map_err(|e| {
|
||||||
|
log::error!("Failed to list namespaces: {}", e);
|
||||||
|
NamespaceServiceError::Internal(e.to_string())
|
||||||
|
})?;
|
||||||
|
let ns_list = ns_list_resp.into_inner().namespaces;
|
||||||
|
Ok(ns_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新命名空间信息
|
||||||
|
pub async fn update_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: &str,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> Result<(), NamespaceServiceError> {
|
||||||
|
let exist = self.namespace_exist(namespace).await?;
|
||||||
|
if exist.is_none() {
|
||||||
|
log::info!("Namespace {} not found", namespace);
|
||||||
|
return Err(NamespaceServiceError::NotFound);
|
||||||
|
}
|
||||||
|
let ns_name = namespace;
|
||||||
|
let namespace = Namespace {
|
||||||
|
name: namespace.to_string(),
|
||||||
|
labels,
|
||||||
|
};
|
||||||
|
let mut c = self.client.namespaces();
|
||||||
|
let req = UpdateNamespaceRequest {
|
||||||
|
namespace: Some(namespace),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
c.update(req).await.map_err(|e| {
|
||||||
|
log::error!("Failed to update namespace: {}", e);
|
||||||
|
NamespaceServiceError::Internal(e.to_string())
|
||||||
|
})?;
|
||||||
|
log::info!("Namespace {} updated", ns_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod deploy;
|
pub mod deploy;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
pub mod namespace;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
91
crates/faas-containerd/src/provider/function/namespace.rs
Normal file
91
crates/faas-containerd/src/provider/function/namespace.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use gateway::{handlers::namespace::NamespaceError, types::namespace::Namespace};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
impls::{backend, namespace::NamespaceServiceError},
|
||||||
|
provider::ContainerdProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl ContainerdProvider {
|
||||||
|
pub(crate) async fn _create_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> Result<(), NamespaceError> {
|
||||||
|
backend()
|
||||||
|
.create_namespace(&namespace, labels)
|
||||||
|
.await
|
||||||
|
.map_err(|e| match e {
|
||||||
|
NamespaceServiceError::AlreadyExists => NamespaceError::AlreadyExists(format!(
|
||||||
|
"namespace {} has been existed",
|
||||||
|
namespace
|
||||||
|
)),
|
||||||
|
_ => NamespaceError::Internal(e.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn _get_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
) -> Result<Namespace, NamespaceError> {
|
||||||
|
let exist = backend()
|
||||||
|
.namespace_exist(&namespace)
|
||||||
|
.await
|
||||||
|
.map_err(|e| NamespaceError::Internal(e.to_string()))?;
|
||||||
|
if exist.is_none() {
|
||||||
|
return Err(NamespaceError::NotFound(format!(
|
||||||
|
"namespace {} not found",
|
||||||
|
namespace
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let ns = exist.unwrap();
|
||||||
|
Ok(Namespace {
|
||||||
|
name: Some(ns.name),
|
||||||
|
labels: ns.labels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn _namespace_list(&self) -> Result<Vec<Namespace>, NamespaceError> {
|
||||||
|
let ns_list = backend()
|
||||||
|
.list_namespace()
|
||||||
|
.await
|
||||||
|
.map_err(|e| NamespaceError::Internal(e.to_string()))?;
|
||||||
|
let mut ns_list_result = Vec::new();
|
||||||
|
for ns in ns_list {
|
||||||
|
ns_list_result.push(Namespace {
|
||||||
|
name: Some(ns.name),
|
||||||
|
labels: ns.labels,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(ns_list_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn _delete_namespace(&self, namespace: String) -> Result<(), NamespaceError> {
|
||||||
|
backend()
|
||||||
|
.delete_namespace(&namespace)
|
||||||
|
.await
|
||||||
|
.map_err(|e| match e {
|
||||||
|
NamespaceServiceError::NotFound => {
|
||||||
|
NamespaceError::NotFound(format!("namespace {} not found", namespace))
|
||||||
|
}
|
||||||
|
_ => NamespaceError::Internal(e.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn _update_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> Result<(), NamespaceError> {
|
||||||
|
backend()
|
||||||
|
.update_namespace(&namespace, labels)
|
||||||
|
.await
|
||||||
|
.map_err(|e| match e {
|
||||||
|
NamespaceServiceError::NotFound => {
|
||||||
|
NamespaceError::NotFound(format!("namespace {} not found", namespace))
|
||||||
|
}
|
||||||
|
_ => NamespaceError::Internal(e.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,16 @@
|
|||||||
pub mod function;
|
pub mod function;
|
||||||
|
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||||
use std::{path::Path, sync::Arc};
|
|
||||||
|
|
||||||
use gateway::{
|
use gateway::{
|
||||||
handlers::function::{DeleteError, DeployError, ListError, ResolveError, UpdateError},
|
handlers::{
|
||||||
|
function::{DeleteError, DeployError, ListError, ResolveError, UpdateError},
|
||||||
|
namespace::NamespaceError,
|
||||||
|
},
|
||||||
provider::Provider,
|
provider::Provider,
|
||||||
types::function::{Deployment, Query, Status},
|
types::{
|
||||||
|
function::{Deployment, Query, Status},
|
||||||
|
namespace::Namespace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ContainerdProvider {
|
pub struct ContainerdProvider {
|
||||||
@ -46,4 +51,32 @@ impl Provider for ContainerdProvider {
|
|||||||
async fn status(&self, function: Query) -> Result<Status, ResolveError> {
|
async fn status(&self, function: Query) -> Result<Status, ResolveError> {
|
||||||
self._status(function).await
|
self._status(function).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> Result<(), NamespaceError> {
|
||||||
|
self._create_namespace(namespace, labels).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> Result<(), NamespaceError> {
|
||||||
|
self._update_namespace(namespace, labels).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_namespace(&self, namespace: String) -> Result<(), NamespaceError> {
|
||||||
|
self._delete_namespace(namespace).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_namespace(&self, namespace: String) -> Result<Namespace, NamespaceError> {
|
||||||
|
self._get_namespace(namespace).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn namespace_list(&self) -> Result<Vec<Namespace>, NamespaceError> {
|
||||||
|
self._namespace_list().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,16 @@ pub fn config_app<P: Provider>(provider: Arc<P>) -> impl FnOnce(&mut ServiceConf
|
|||||||
.service(
|
.service(
|
||||||
web::resource("/function/{functionName}")
|
web::resource("/function/{functionName}")
|
||||||
.route(web::get().to(handlers::function::status::<P>)),
|
.route(web::get().to(handlers::function::status::<P>)),
|
||||||
), // .service(
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/namespace/{namespace}")
|
||||||
|
.route(web::to(handlers::namespace::mut_namespace::<P>)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/namespaces")
|
||||||
|
.route(web::get().to(handlers::namespace::namespace_list::<P>)),
|
||||||
|
),
|
||||||
|
// .service(
|
||||||
// web::resource("/scale-function/{name}")
|
// web::resource("/scale-function/{name}")
|
||||||
// .route(web::post().to(handlers::scale_function)),
|
// .route(web::post().to(handlers::scale_function)),
|
||||||
// )
|
// )
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod function;
|
pub mod function;
|
||||||
|
pub mod namespace;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
106
crates/gateway/src/handlers/namespace.rs
Normal file
106
crates/gateway/src/handlers/namespace.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use crate::{provider::Provider, types::namespace::Namespace};
|
||||||
|
use actix_http::{Method, StatusCode};
|
||||||
|
use actix_web::{HttpRequest, HttpResponse, ResponseError, web};
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
pub enum NamespaceError {
|
||||||
|
#[display("Invalid: {}", _0)]
|
||||||
|
Invalid(String),
|
||||||
|
#[display("AlreadyExists: {}", _0)]
|
||||||
|
AlreadyExists(String),
|
||||||
|
#[display("NotFound: {}", _0)]
|
||||||
|
NotFound(String),
|
||||||
|
#[display("Internal: {}", _0)]
|
||||||
|
Internal(String),
|
||||||
|
#[display("MethodNotAllowed: {}", _0)]
|
||||||
|
MethodNotAllowed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for NamespaceError {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
NamespaceError::Invalid(_) => StatusCode::BAD_REQUEST,
|
||||||
|
NamespaceError::AlreadyExists(_) => StatusCode::BAD_REQUEST,
|
||||||
|
NamespaceError::NotFound(_) => StatusCode::NOT_FOUND,
|
||||||
|
NamespaceError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
NamespaceError::MethodNotAllowed(_) => StatusCode::METHOD_NOT_ALLOWED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mut_namespace<P: Provider>(
|
||||||
|
req: HttpRequest,
|
||||||
|
provider: web::Data<P>,
|
||||||
|
info: Option<web::Json<Namespace>>,
|
||||||
|
) -> Result<HttpResponse, NamespaceError> {
|
||||||
|
let namespace = req.match_info().get("namespace");
|
||||||
|
if namespace.is_none() {
|
||||||
|
return Err(NamespaceError::Invalid("namespace is required".to_string()));
|
||||||
|
}
|
||||||
|
let namespace = namespace.unwrap();
|
||||||
|
let labels;
|
||||||
|
match *req.method() {
|
||||||
|
Method::POST => {
|
||||||
|
match info {
|
||||||
|
Some(info) => {
|
||||||
|
labels = info.0.labels;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(NamespaceError::Invalid(
|
||||||
|
"Request body is required".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*provider)
|
||||||
|
.create_namespace(namespace.to_string(), labels)
|
||||||
|
.await
|
||||||
|
.map(|_| {
|
||||||
|
HttpResponse::Created()
|
||||||
|
.body(format!("namespace {} was created successfully", namespace))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::DELETE => (*provider)
|
||||||
|
.delete_namespace(namespace.to_string())
|
||||||
|
.await
|
||||||
|
.map(|_| {
|
||||||
|
HttpResponse::Accepted()
|
||||||
|
.body(format!("namespace {} was deleted successfully", namespace))
|
||||||
|
}),
|
||||||
|
Method::PUT => {
|
||||||
|
match info {
|
||||||
|
Some(info) => {
|
||||||
|
labels = info.0.labels;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(NamespaceError::Invalid(
|
||||||
|
"Request body is required".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*provider)
|
||||||
|
.update_namespace(namespace.to_string(), labels)
|
||||||
|
.await
|
||||||
|
.map(|_| {
|
||||||
|
HttpResponse::Accepted()
|
||||||
|
.body(format!("namespace {} was updated successfully", namespace))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Method::GET => (*provider)
|
||||||
|
.get_namespace(namespace.to_string())
|
||||||
|
.await
|
||||||
|
.map(|ns| HttpResponse::Ok().json(ns)),
|
||||||
|
_ => Err(NamespaceError::MethodNotAllowed(
|
||||||
|
"Method not allowed".to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn namespace_list<P: Provider>(
|
||||||
|
provider: web::Data<P>,
|
||||||
|
) -> Result<HttpResponse, NamespaceError> {
|
||||||
|
(*provider)
|
||||||
|
.namespace_list()
|
||||||
|
.await
|
||||||
|
.map(|ns_list| HttpResponse::Ok().json(ns_list))
|
||||||
|
}
|
@ -1,6 +1,14 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
handlers::function::{DeleteError, DeployError, ListError, ResolveError, UpdateError},
|
handlers::{
|
||||||
types::function::{Deployment, Query, Status},
|
function::{DeleteError, DeployError, ListError, ResolveError, UpdateError},
|
||||||
|
namespace::NamespaceError,
|
||||||
|
},
|
||||||
|
types::{
|
||||||
|
function::{Deployment, Query, Status},
|
||||||
|
namespace::Namespace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Provider: Send + Sync + 'static {
|
pub trait Provider: Send + Sync + 'static {
|
||||||
@ -42,4 +50,30 @@ pub trait Provider: Send + Sync + 'static {
|
|||||||
&self,
|
&self,
|
||||||
function: Query,
|
function: Query,
|
||||||
) -> impl std::future::Future<Output = Result<Status, ResolveError>> + Send;
|
) -> impl std::future::Future<Output = Result<Status, ResolveError>> + Send;
|
||||||
|
|
||||||
|
fn create_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> impl std::future::Future<Output = Result<(), NamespaceError>> + Send;
|
||||||
|
|
||||||
|
fn update_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
) -> impl std::future::Future<Output = Result<(), NamespaceError>> + Send;
|
||||||
|
|
||||||
|
fn delete_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
) -> impl std::future::Future<Output = Result<(), NamespaceError>> + Send;
|
||||||
|
|
||||||
|
fn get_namespace(
|
||||||
|
&self,
|
||||||
|
namespace: String,
|
||||||
|
) -> impl std::future::Future<Output = Result<Namespace, NamespaceError>> + Send;
|
||||||
|
|
||||||
|
fn namespace_list(
|
||||||
|
&self,
|
||||||
|
) -> impl std::future::Future<Output = Result<Vec<Namespace>, NamespaceError>> + Send;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod function;
|
pub mod function;
|
||||||
|
pub mod namespace;
|
||||||
|
8
crates/gateway/src/types/namespace.rs
Normal file
8
crates/gateway/src/types/namespace.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Namespace {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub labels: HashMap<String, String>,
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user