add api to search resources by tag

This commit is contained in:
mbecker20
2023-07-07 06:55:47 +00:00
parent 9b68283eb3
commit c12eb40459
21 changed files with 356 additions and 27 deletions

1
Cargo.lock generated
View File

@@ -1908,7 +1908,6 @@ dependencies = [
"async-trait",
"async_timing_util",
"bollard",
"bson",
"derive_builder",
"derive_variants",
"make_option",

View File

@@ -32,7 +32,6 @@ envy = "0.4"
anyhow = "1.0"
log = "0.4"
simple_logger = "4.1"
bson = "2.6.1"
bollard = "0.14.0"
derive_builder = "0.12"
typeshare = "1.0.1"

View File

@@ -1,6 +1,6 @@
use monitor_types::entities::{
build::Build, builder::Builder, deployment::Deployment, repo::Repo, server::Server,
update::Update, user::User,
update::Update, user::User, tag::CustomTag,
};
use mungos::{Collection, Indexed, Mungos};
@@ -13,6 +13,7 @@ pub struct DbClient {
pub builds: Collection<Build>,
pub builders: Collection<Builder>,
pub repos: Collection<Repo>,
pub tags: Collection<CustomTag>,
pub updates: Collection<Update>,
}
@@ -30,6 +31,7 @@ impl DbClient {
builds: Build::collection(&mungos, &config.mongo.db_name, true).await?,
builders: Builder::collection(&mungos, &config.mongo.db_name, true).await?,
repos: Repo::collection(&mungos, &config.mongo.db_name, true).await?,
tags: CustomTag::collection(&mungos, &config.mongo.db_name, true).await?,
updates: Update::collection(&mungos, &config.mongo.db_name, true).await?,
};
Ok(client)

View File

@@ -7,7 +7,7 @@ use monitor_types::{
entities::{
deployment::{
Deployment, DeploymentActionState, DeploymentConfig, DeploymentImage,
DockerContainerState, DockerContainerStats,
DockerContainerStats,
},
update::{Log, UpdateStatus},
Operation, PermissionLevel,
@@ -64,10 +64,7 @@ impl Resolve<ListDeployments, RequestUser> for State {
id: deployment.id,
name: deployment.name,
tags: deployment.tags,
state: status
.as_ref()
.map(|s| s.state)
.unwrap_or(DockerContainerState::Unknown),
state: status.as_ref().map(|s| s.state).unwrap_or_default(),
status: status
.as_ref()
.and_then(|s| s.container.as_ref().and_then(|c| c.status.to_owned())),

View File

@@ -20,6 +20,7 @@ mod builder;
mod deployment;
mod repo;
mod server;
mod search;
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
@@ -28,6 +29,9 @@ mod server;
#[serde(tag = "type", content = "params")]
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
pub enum ReadRequest {
// ==== SEARCH ====
FindResources(FindResources),
// ==== SERVER ====
GetPeripheryVersion(GetPeripheryVersion),
GetSystemInformation(GetSystemInformation),

View File

@@ -0,0 +1,203 @@
use std::str::FromStr;
use anyhow::Context;
use async_trait::async_trait;
use monitor_types::{
entities::{
tag::Tag,
update::ResourceTargetVariant::{self, *},
PermissionLevel,
},
permissioned::Permissioned,
requests::read::{
BuildListItem, DeploymentListItem, FindResources, FindResourcesResponse, RepoListItem,
ServerListItem,
},
};
use mungos::mongodb::bson::{doc, oid::ObjectId, Document};
use resolver_api::Resolve;
use crate::{auth::RequestUser, state::State};
const ALL_RESOURCE_TYPES: [ResourceTargetVariant; 4] = [Server, Build, Deployment, Repo];
#[async_trait]
impl Resolve<FindResources, RequestUser> for State {
async fn resolve(
&self,
FindResources { tags }: FindResources,
user: RequestUser,
) -> anyhow::Result<FindResourcesResponse> {
let SeperateTags {
resource_types,
server_ids,
custom_tag_ids,
} = seperate_tags(tags);
let mut query = Document::new();
if !custom_tag_ids.is_empty() {
query.insert("tags", doc! { "$all": custom_tag_ids });
}
let mut response = FindResourcesResponse::default();
for resource_type in resource_types {
match resource_type {
Server => {
let servers = if server_ids.is_empty() {
self.db.servers.get_some(query.clone(), None).await?
} else {
let server_ids = server_ids
.iter()
.map(|id| {
ObjectId::from_str(id)
.context("failed to parse server id as ObjectId")
})
.collect::<anyhow::Result<Vec<_>>>()?;
self.db
.servers
.get_some(doc! { "_id": { "$in": server_ids } }, None)
.await?
}
.into_iter()
.filter(|s| s.get_user_permissions(&user.id) > PermissionLevel::None);
for server in servers {
let status = self
.server_status_cache
.get(&server.id)
.await
.map(|s| s.status)
.unwrap_or_default();
let item = ServerListItem {
status,
id: server.id,
name: server.name,
tags: server.tags,
};
response.servers.push(item);
}
}
Deployment => {
let mut query = query.clone();
if !server_ids.is_empty() {
query.insert("config.server_id", doc! { "$in": &server_ids });
}
let deployments = self
.db
.deployments
.get_some(query, None)
.await?
.into_iter()
.filter(|d| d.get_user_permissions(&user.id) > PermissionLevel::Read);
for deployment in deployments {
let status = self.deployment_status_cache.get(&deployment.id).await;
let item = DeploymentListItem {
id: deployment.id,
name: deployment.name,
tags: deployment.tags,
state: status.as_ref().map(|s| s.state).unwrap_or_default(),
status: status.as_ref().and_then(|s| {
s.container.as_ref().and_then(|c| c.status.to_owned())
}),
image: String::new(),
version: String::new(),
};
response.deployments.push(item);
}
}
Build => {
let mut query = query.clone();
if !server_ids.is_empty() {
query.insert(
"config.builder.params.server_id",
doc! { "$in": &server_ids },
);
}
let builds = self
.db
.builds
.get_some(query, None)
.await?
.into_iter()
.filter(|d| d.get_user_permissions(&user.id) > PermissionLevel::Read);
for build in builds {
let item = BuildListItem {
id: build.id,
name: build.name,
tags: build.tags,
last_built_at: build.last_built_at,
version: build.config.version,
};
response.builds.push(item);
}
}
Repo => {
let mut query = query.clone();
if !server_ids.is_empty() {
query.insert("config.server_id", doc! { "$in": &server_ids });
}
let repos = self
.db
.repos
.get_some(query, None)
.await?
.into_iter()
.filter(|d| d.get_user_permissions(&user.id) > PermissionLevel::Read);
for repo in repos {
let item = RepoListItem {
id: repo.id,
name: repo.name,
tags: repo.tags,
last_pulled_at: repo.last_pulled_at,
};
response.repos.push(item);
}
}
_ => unreachable!(),
}
}
Ok(response)
}
}
#[derive(Default)]
struct SeperateTags {
resource_types: Vec<ResourceTargetVariant>,
server_ids: Vec<String>,
custom_tag_ids: Vec<String>,
}
fn seperate_tags(tags: Vec<Tag>) -> SeperateTags {
let mut seperated = SeperateTags::default();
for tag in tags {
match tag {
Tag::Custom { tag_id } => seperated.custom_tag_ids.push(tag_id),
Tag::Server { server_id } => seperated.server_ids.push(server_id),
Tag::ResourceType { resource } => {
if !matches!(resource, Builder | System,)
&& !seperated.resource_types.contains(&resource)
{
seperated.resource_types.push(resource);
}
}
}
}
if seperated.resource_types.is_empty() {
seperated.resource_types = ALL_RESOURCE_TYPES.to_vec();
}
seperated
}

View File

@@ -73,6 +73,7 @@ impl Resolve<ListServers, RequestUser> for State {
ServerListItem {
id: server.id,
name: server.name,
tags: server.tags,
status: status.map(|s| s.status).unwrap_or_default(),
}
});

View File

@@ -16,7 +16,6 @@ strum.workspace = true
strum_macros.workspace = true
async-trait.workspace = true
anyhow.workspace = true
bson.workspace = true
async_timing_util.workspace = true
resolver_api.workspace = true
derive_builder.workspace = true

View File

@@ -1,7 +1,6 @@
use bson::{doc, serde_helpers::hex_string_as_object_id};
use derive_builder::Builder;
use derive_variants::EnumVariants;
use mungos::MungosIndexed;
use mungos::{MungosIndexed, mongodb::bson::{serde_helpers::hex_string_as_object_id, doc}};
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};

View File

@@ -1,7 +1,6 @@
use bson::serde_helpers::hex_string_as_object_id;
use derive_builder::Builder;
use derive_variants::EnumVariants;
use mungos::MungosIndexed;
use mungos::{MungosIndexed, mongodb::bson::serde_helpers::hex_string_as_object_id};
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};

View File

@@ -1,7 +1,6 @@
use bson::{doc, serde_helpers::hex_string_as_object_id};
use derive_builder::Builder;
use derive_variants::EnumVariants;
use mungos::MungosIndexed;
use mungos::{mongodb::bson::{serde_helpers::hex_string_as_object_id, doc}, MungosIndexed};
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};

View File

@@ -13,6 +13,7 @@ pub mod builder;
pub mod deployment;
pub mod repo;
pub mod server;
pub mod tag;
pub mod update;
pub mod user;

View File

@@ -1,6 +1,5 @@
use bson::serde_helpers::hex_string_as_object_id;
use derive_builder::Builder;
use mungos::MungosIndexed;
use mungos::{MungosIndexed, mongodb::bson::serde_helpers::hex_string_as_object_id};
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;

View File

@@ -1,6 +1,5 @@
use bson::serde_helpers::hex_string_as_object_id;
use derive_builder::Builder;
use mungos::MungosIndexed;
use mungos::{MungosIndexed, mongodb::bson::serde_helpers::hex_string_as_object_id};
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;

View File

@@ -0,0 +1,66 @@
use derive_builder::Builder;
use derive_variants::EnumVariants;
use mungos::{
mongodb::bson::{doc, serde_helpers::hex_string_as_object_id},
MungosIndexed,
};
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};
use typeshare::typeshare;
use crate::MongoId;
use super::update::ResourceTargetVariant;
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, EnumVariants)]
#[variant_derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumString)]
#[serde(tag = "type", content = "params")]
pub enum Tag {
ResourceType { resource: ResourceTargetVariant }, // filter by resource type
Server { server_id: String }, // filter by server, eg deployments, builds, repos
Custom { tag_id: String }, // filter by presence of custom tag on resource
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, MungosIndexed, Partial)]
#[partial_derive(Serialize, Deserialize, Debug, Clone)]
#[unique_doc_index(doc! { "name": 1, "category": 1 })]
pub struct CustomTag {
#[serde(
default,
rename = "_id",
skip_serializing_if = "String::is_empty",
with = "hex_string_as_object_id"
)]
#[builder(setter(skip))]
pub id: MongoId,
#[index]
pub name: String,
#[serde(default)]
#[builder(default)]
#[index]
pub category: String,
#[serde(default)]
#[builder(default)]
pub color: TagColor,
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, Display, EnumString, MungosIndexed, Default,
)]
pub enum TagColor {
#[default]
Red,
Green,
Blue,
Yellow,
Purple,
Magenta,
Cyan,
}

View File

@@ -1,7 +1,6 @@
use async_timing_util::unix_timestamp_ms;
use bson::serde_helpers::hex_string_as_object_id;
use derive_variants::EnumVariants;
use mungos::MungosIndexed;
use mungos::{MungosIndexed, mongodb::bson::serde_helpers::hex_string_as_object_id};
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};
use typeshare::typeshare;
@@ -88,7 +87,7 @@ impl Log {
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default, MungosIndexed, EnumVariants)]
#[variant_derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumString)]
#[variant_derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumString, PartialEq, Eq)]
#[serde(tag = "type", content = "id")]
pub enum ResourceTarget {
#[default]

View File

@@ -1,5 +1,4 @@
use bson::serde_helpers::hex_string_as_object_id;
use mungos::MungosIndexed;
use mungos::{MungosIndexed, mongodb::bson::serde_helpers::hex_string_as_object_id};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;

View File

@@ -6,6 +6,7 @@ mod build;
mod builder;
mod deployment;
mod repo;
mod search;
mod server;
mod update;
@@ -13,6 +14,7 @@ pub use build::*;
pub use builder::*;
pub use deployment::*;
pub use repo::*;
pub use search::*;
pub use server::*;
pub use update::*;

View File

@@ -0,0 +1,25 @@
use resolver_api::derive::Request;
use serde::{Serialize, Deserialize};
use typeshare::typeshare;
use crate::entities::tag::Tag;
use super::{ServerListItem, DeploymentListItem, BuildListItem, RepoListItem};
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(FindResourcesResponse)]
pub struct FindResources {
pub tags: Vec<Tag>,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FindResourcesResponse {
pub servers: Vec<ServerListItem>,
pub deployments: Vec<DeploymentListItem>,
pub builds: Vec<BuildListItem>,
pub repos: Vec<RepoListItem>,
}

View File

@@ -42,6 +42,7 @@ pub struct ServerListItem {
pub id: String,
pub name: String,
pub status: ServerStatus,
pub tags: Vec<String>,
}
//

View File

@@ -2,7 +2,44 @@ use resolver_api::derive::Request;
use serde::{Serialize, Deserialize};
use typeshare::typeshare;
use crate::entities::update::ResourceTarget;
use crate::entities::{update::ResourceTarget, tag::{CustomTag, TagColor, PartialCustomTag}};
#[typeshare(serialized_as = "Partial<CustomTag>")]
type _PartialCustomTag = PartialCustomTag;
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(CustomTag)]
pub struct CreateTag {
pub name: String,
#[serde(default)]
pub category: String,
#[serde(default)]
pub color: TagColor
}
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(CustomTag)]
pub struct DeleteTag {
pub id: String,
}
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(CustomTag)]
pub struct UpdateTag {
pub id: String,
pub config: _PartialCustomTag
}
//
@@ -11,7 +48,7 @@ use crate::entities::update::ResourceTarget;
#[response(())]
pub struct AddTags {
pub target: ResourceTarget,
pub tags: Vec<String>,
pub tags: Vec<String>, // custom tag ids
}
//
@@ -21,5 +58,5 @@ pub struct AddTags {
#[response(())]
pub struct RemoveTags {
pub target: ResourceTarget,
pub tags: Vec<String>,
pub tags: Vec<String>, // custom tag ids
}