mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-10 16:01:20 +00:00
1218 lines
34 KiB
Rust
1218 lines
34 KiB
Rust
use std::{cmp::Ordering, collections::HashMap};
|
|
|
|
use comfy_table::{Attribute, Cell, Color};
|
|
use futures_util::{FutureExt, try_join};
|
|
use komodo_client::{
|
|
KomodoClient,
|
|
api::read::{
|
|
ListActions, ListAlerters, ListBuilders, ListBuilds,
|
|
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
|
|
ListSchedules, ListServers, ListStacks, ListTags, ListTerminals,
|
|
},
|
|
entities::{
|
|
ResourceTargetVariant,
|
|
action::{ActionListItem, ActionListItemInfo, ActionState},
|
|
alerter::{AlerterListItem, AlerterListItemInfo},
|
|
build::{BuildListItem, BuildListItemInfo, BuildState},
|
|
builder::{BuilderListItem, BuilderListItemInfo},
|
|
config::cli::args::{
|
|
self,
|
|
list::{ListCommand, ResourceFilters},
|
|
},
|
|
deployment::{
|
|
DeploymentListItem, DeploymentListItemInfo, DeploymentState,
|
|
},
|
|
procedure::{
|
|
ProcedureListItem, ProcedureListItemInfo, ProcedureState,
|
|
},
|
|
repo::{RepoListItem, RepoListItemInfo, RepoState},
|
|
resource::{ResourceListItem, ResourceQuery},
|
|
resource_link,
|
|
schedule::Schedule,
|
|
server::{ServerListItem, ServerListItemInfo, ServerState},
|
|
stack::{StackListItem, StackListItemInfo, StackState},
|
|
sync::{
|
|
ResourceSyncListItem, ResourceSyncListItemInfo,
|
|
ResourceSyncState,
|
|
},
|
|
terminal::Terminal,
|
|
},
|
|
};
|
|
use serde::Serialize;
|
|
|
|
use crate::{
|
|
command::{
|
|
PrintTable, format_timetamp, matches_wildcards, parse_wildcards,
|
|
print_items,
|
|
},
|
|
config::cli_config,
|
|
};
|
|
|
|
pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
|
match &list.command {
|
|
None => list_all(list).await,
|
|
Some(ListCommand::Servers(filters)) => {
|
|
list_resources::<ServerListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Stacks(filters)) => {
|
|
list_resources::<StackListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Deployments(filters)) => {
|
|
list_resources::<DeploymentListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Builds(filters)) => {
|
|
list_resources::<BuildListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Repos(filters)) => {
|
|
list_resources::<RepoListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Procedures(filters)) => {
|
|
list_resources::<ProcedureListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Actions(filters)) => {
|
|
list_resources::<ActionListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Syncs(filters)) => {
|
|
list_resources::<ResourceSyncListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Terminals(filters)) => {
|
|
list_terminals(filters).await
|
|
}
|
|
Some(ListCommand::Schedules(filters)) => {
|
|
list_schedules(filters).await
|
|
}
|
|
Some(ListCommand::Builders(filters)) => {
|
|
list_resources::<BuilderListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Alerters(filters)) => {
|
|
list_resources::<AlerterListItem>(filters, false).await
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Includes all resources besides builds and alerters.
|
|
async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
|
let filters: ResourceFilters = list.clone().into();
|
|
let client = super::komodo_client().await?;
|
|
let (
|
|
tags,
|
|
mut servers,
|
|
mut stacks,
|
|
mut deployments,
|
|
mut builds,
|
|
mut repos,
|
|
mut procedures,
|
|
mut actions,
|
|
mut syncs,
|
|
) = try_join!(
|
|
client.read(ListTags::default()).map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|t| (t.id, t.name))
|
|
.collect::<HashMap<_, _>>())),
|
|
ServerListItem::list(client, &filters, true),
|
|
StackListItem::list(client, &filters, true),
|
|
DeploymentListItem::list(client, &filters, true),
|
|
BuildListItem::list(client, &filters, true),
|
|
RepoListItem::list(client, &filters, true),
|
|
ProcedureListItem::list(client, &filters, true),
|
|
ActionListItem::list(client, &filters, true),
|
|
ResourceSyncListItem::list(client, &filters, true),
|
|
)?;
|
|
|
|
if !servers.is_empty() {
|
|
fix_tags(&mut servers, &tags);
|
|
print_items(servers, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !stacks.is_empty() {
|
|
fix_tags(&mut stacks, &tags);
|
|
print_items(stacks, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !deployments.is_empty() {
|
|
fix_tags(&mut deployments, &tags);
|
|
print_items(deployments, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !builds.is_empty() {
|
|
fix_tags(&mut builds, &tags);
|
|
print_items(builds, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !repos.is_empty() {
|
|
fix_tags(&mut repos, &tags);
|
|
print_items(repos, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !procedures.is_empty() {
|
|
fix_tags(&mut procedures, &tags);
|
|
print_items(procedures, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !actions.is_empty() {
|
|
fix_tags(&mut actions, &tags);
|
|
print_items(actions, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !syncs.is_empty() {
|
|
fix_tags(&mut syncs, &tags);
|
|
print_items(syncs, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn list_resources<T>(
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<()>
|
|
where
|
|
T: ListResources,
|
|
ResourceListItem<T::Info>: PrintTable + Serialize,
|
|
{
|
|
let client = crate::command::komodo_client().await?;
|
|
let (mut resources, tags) = tokio::try_join!(
|
|
T::list(client, filters, minimal),
|
|
client.read(ListTags::default()).map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|t| (t.id, t.name))
|
|
.collect::<HashMap<_, _>>()))
|
|
)?;
|
|
fix_tags(&mut resources, &tags);
|
|
if !resources.is_empty() {
|
|
print_items(resources, filters.format, filters.links)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn list_terminals(
|
|
filters: &ResourceFilters,
|
|
) -> anyhow::Result<()> {
|
|
let client = crate::command::komodo_client().await?;
|
|
// let query = ResourceQuery::builder()
|
|
// .tags(filters.tags.clone())
|
|
// .templates(TemplatesQueryBehavior::Exclude)
|
|
// .build();
|
|
let terminals = client
|
|
.read(ListTerminals {
|
|
target: None,
|
|
use_names: true,
|
|
})
|
|
.await?;
|
|
if !terminals.is_empty() {
|
|
print_items(terminals, filters.format, filters.links)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn list_schedules(
|
|
filters: &ResourceFilters,
|
|
) -> anyhow::Result<()> {
|
|
let client = crate::command::komodo_client().await?;
|
|
let (mut schedules, tags) = tokio::try_join!(
|
|
client
|
|
.read(ListSchedules {
|
|
tags: filters.tags.clone(),
|
|
tag_behavior: Default::default(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.filter(|s| s.next_scheduled_run.is_some())
|
|
.collect::<Vec<_>>())),
|
|
client.read(ListTags::default()).map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|t| (t.id, t.name))
|
|
.collect::<HashMap<_, _>>()))
|
|
)?;
|
|
schedules.iter_mut().for_each(|resource| {
|
|
resource.tags.iter_mut().for_each(|id| {
|
|
let Some(name) = tags.get(id) else {
|
|
*id = String::new();
|
|
return;
|
|
};
|
|
id.clone_from(name);
|
|
});
|
|
});
|
|
schedules.sort_by(|a, b| {
|
|
match (a.next_scheduled_run, b.next_scheduled_run) {
|
|
(Some(_), None) => return Ordering::Less,
|
|
(None, Some(_)) => return Ordering::Greater,
|
|
(Some(a), Some(b)) => return a.cmp(&b),
|
|
(None, None) => {}
|
|
}
|
|
a.name.cmp(&b.name).then(a.enabled.cmp(&b.enabled))
|
|
});
|
|
if !schedules.is_empty() {
|
|
print_items(schedules, filters.format, filters.links)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn fix_tags<T>(
|
|
resources: &mut [ResourceListItem<T>],
|
|
tags: &HashMap<String, String>,
|
|
) {
|
|
resources.iter_mut().for_each(|resource| {
|
|
resource.tags.iter_mut().for_each(|id| {
|
|
let Some(name) = tags.get(id) else {
|
|
*id = String::new();
|
|
return;
|
|
};
|
|
id.clone_from(name);
|
|
});
|
|
});
|
|
}
|
|
|
|
trait ListResources: Sized
|
|
where
|
|
ResourceListItem<Self::Info>: PrintTable,
|
|
{
|
|
type Info;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
// For use with root `km ls`
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<ResourceListItem<Self::Info>>>;
|
|
}
|
|
|
|
// LIST
|
|
|
|
impl ListResources for ServerListItem {
|
|
type Info = ServerListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
_minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let servers = client
|
|
.read(ListServers {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?;
|
|
let names = parse_wildcards(&filters.names);
|
|
let server_wildcards = parse_wildcards(&filters.servers);
|
|
let mut servers = servers
|
|
.into_iter()
|
|
.filter(|server| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
!matches!(server.info.state, ServerState::Ok)
|
|
} else if filters.in_progress {
|
|
false
|
|
} else {
|
|
matches!(server.info.state, ServerState::Ok)
|
|
};
|
|
let name_items = &[server.name.as_str()];
|
|
state_check
|
|
&& matches_wildcards(&names, name_items)
|
|
&& matches_wildcards(&server_wildcards, name_items)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
servers.sort_by(|a, b| {
|
|
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
|
});
|
|
Ok(servers)
|
|
}
|
|
}
|
|
|
|
impl ListResources for StackListItem {
|
|
type Info = StackListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
_minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let (servers, mut stacks) = tokio::try_join!(
|
|
client
|
|
.read(ListServers {
|
|
query: ResourceQuery::builder().build(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|s| (s.id.clone(), s))
|
|
.collect::<HashMap<_, _>>())),
|
|
client.read(ListStacks {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
)?;
|
|
stacks.iter_mut().for_each(|stack| {
|
|
if stack.info.server_id.is_empty() {
|
|
return;
|
|
}
|
|
let Some(server) = servers.get(&stack.info.server_id) else {
|
|
return;
|
|
};
|
|
stack.info.server_id.clone_from(&server.name);
|
|
});
|
|
let names = parse_wildcards(&filters.names);
|
|
let servers = parse_wildcards(&filters.servers);
|
|
let mut stacks = stacks
|
|
.into_iter()
|
|
.filter(|stack| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
!matches!(
|
|
stack.info.state,
|
|
StackState::Running | StackState::Deploying
|
|
)
|
|
} else if filters.in_progress {
|
|
matches!(stack.info.state, StackState::Deploying)
|
|
} else {
|
|
matches!(
|
|
stack.info.state,
|
|
StackState::Running | StackState::Deploying
|
|
)
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[stack.name.as_str()])
|
|
&& matches_wildcards(
|
|
&servers,
|
|
&[stack.info.server_id.as_str()],
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
stacks.sort_by(|a, b| {
|
|
a.info
|
|
.state
|
|
.cmp(&b.info.state)
|
|
.then(a.name.cmp(&b.name))
|
|
.then(a.info.server_id.cmp(&b.info.server_id))
|
|
});
|
|
Ok(stacks)
|
|
}
|
|
}
|
|
|
|
impl ListResources for DeploymentListItem {
|
|
type Info = DeploymentListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
_minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let (servers, mut deployments) = tokio::try_join!(
|
|
client
|
|
.read(ListServers {
|
|
query: ResourceQuery::builder().build(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|s| (s.id.clone(), s))
|
|
.collect::<HashMap<_, _>>())),
|
|
client.read(ListDeployments {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
)?;
|
|
deployments.iter_mut().for_each(|deployment| {
|
|
if deployment.info.server_id.is_empty() {
|
|
return;
|
|
}
|
|
let Some(server) = servers.get(&deployment.info.server_id)
|
|
else {
|
|
return;
|
|
};
|
|
deployment.info.server_id.clone_from(&server.name);
|
|
});
|
|
let names = parse_wildcards(&filters.names);
|
|
let servers = parse_wildcards(&filters.servers);
|
|
let mut deployments = deployments
|
|
.into_iter()
|
|
.filter(|deployment| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
!matches!(
|
|
deployment.info.state,
|
|
DeploymentState::Running | DeploymentState::Deploying
|
|
)
|
|
} else if filters.in_progress {
|
|
matches!(deployment.info.state, DeploymentState::Deploying)
|
|
} else {
|
|
matches!(
|
|
deployment.info.state,
|
|
DeploymentState::Running | DeploymentState::Deploying
|
|
)
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[deployment.name.as_str()])
|
|
&& matches_wildcards(
|
|
&servers,
|
|
&[deployment.info.server_id.as_str()],
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
deployments.sort_by(|a, b| {
|
|
a.info
|
|
.state
|
|
.cmp(&b.info.state)
|
|
.then(a.name.cmp(&b.name))
|
|
.then(a.info.server_id.cmp(&b.info.server_id))
|
|
});
|
|
Ok(deployments)
|
|
}
|
|
}
|
|
|
|
impl ListResources for BuildListItem {
|
|
type Info = BuildListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let (builders, mut builds) = tokio::try_join!(
|
|
client
|
|
.read(ListBuilders {
|
|
query: ResourceQuery::builder().build(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|s| (s.id.clone(), s))
|
|
.collect::<HashMap<_, _>>())),
|
|
client.read(ListBuilds {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
)?;
|
|
builds.iter_mut().for_each(|build| {
|
|
if build.info.builder_id.is_empty() {
|
|
return;
|
|
}
|
|
let Some(builder) = builders.get(&build.info.builder_id) else {
|
|
return;
|
|
};
|
|
build.info.builder_id.clone_from(&builder.name);
|
|
});
|
|
let names = parse_wildcards(&filters.names);
|
|
let builders = parse_wildcards(&filters.builders);
|
|
let mut builds = builds
|
|
.into_iter()
|
|
.filter(|build| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
build.info.state,
|
|
BuildState::Failed | BuildState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(build.info.state, BuildState::Building)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[build.name.as_str()])
|
|
&& matches_wildcards(
|
|
&builders,
|
|
&[build.info.builder_id.as_str()],
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
builds.sort_by(|a, b| {
|
|
a.name
|
|
.cmp(&b.name)
|
|
.then(a.info.builder_id.cmp(&b.info.builder_id))
|
|
.then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(builds)
|
|
}
|
|
}
|
|
|
|
impl ListResources for RepoListItem {
|
|
type Info = RepoListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut repos = client
|
|
.read(ListRepos {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|repo| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
repo.info.state,
|
|
RepoState::Failed | RepoState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(
|
|
repo.info.state,
|
|
RepoState::Building | RepoState::Cloning
|
|
)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[repo.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
repos.sort_by(|a, b| {
|
|
a.name
|
|
.cmp(&b.name)
|
|
.then(a.info.server_id.cmp(&b.info.server_id))
|
|
.then(a.info.builder_id.cmp(&b.info.builder_id))
|
|
});
|
|
Ok(repos)
|
|
}
|
|
}
|
|
|
|
impl ListResources for ProcedureListItem {
|
|
type Info = ProcedureListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut procedures = client
|
|
.read(ListProcedures {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|procedure| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
procedure.info.state,
|
|
ProcedureState::Failed | ProcedureState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(procedure.info.state, ProcedureState::Running)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[procedure.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
procedures.sort_by(|a, b| {
|
|
match (a.info.next_scheduled_run, b.info.next_scheduled_run) {
|
|
(Some(_), None) => return Ordering::Less,
|
|
(None, Some(_)) => return Ordering::Greater,
|
|
(Some(a), Some(b)) => return a.cmp(&b),
|
|
(None, None) => {}
|
|
}
|
|
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(procedures)
|
|
}
|
|
}
|
|
|
|
impl ListResources for ActionListItem {
|
|
type Info = ActionListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut actions = client
|
|
.read(ListActions {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|action| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
action.info.state,
|
|
ActionState::Failed | ActionState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(action.info.state, ActionState::Running)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[action.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
actions.sort_by(|a, b| {
|
|
match (a.info.next_scheduled_run, b.info.next_scheduled_run) {
|
|
(Some(_), None) => return Ordering::Less,
|
|
(None, Some(_)) => return Ordering::Greater,
|
|
(Some(a), Some(b)) => return a.cmp(&b),
|
|
(None, None) => {}
|
|
}
|
|
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(actions)
|
|
}
|
|
}
|
|
|
|
impl ListResources for ResourceSyncListItem {
|
|
type Info = ResourceSyncListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut syncs = client
|
|
.read(ListResourceSyncs {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|sync| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
sync.info.state,
|
|
ResourceSyncState::Failed | ResourceSyncState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(
|
|
sync.info.state,
|
|
ResourceSyncState::Syncing | ResourceSyncState::Pending
|
|
)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[sync.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
syncs.sort_by(|a, b| {
|
|
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(syncs)
|
|
}
|
|
}
|
|
|
|
impl ListResources for BuilderListItem {
|
|
type Info = BuilderListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut builders = client
|
|
.read(ListBuilders {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|builder| {
|
|
(!minimal || filters.all)
|
|
&& matches_wildcards(&names, &[builder.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
builders.sort_by(|a, b| {
|
|
a.name
|
|
.cmp(&b.name)
|
|
.then(a.info.builder_type.cmp(&b.info.builder_type))
|
|
});
|
|
Ok(builders)
|
|
}
|
|
}
|
|
|
|
impl ListResources for AlerterListItem {
|
|
type Info = AlerterListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut syncs = client
|
|
.read(ListAlerters {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|sync| {
|
|
(!minimal || filters.all)
|
|
&& matches_wildcards(&names, &[sync.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
syncs.sort_by(|a, b| {
|
|
a.info
|
|
.enabled
|
|
.cmp(&b.info.enabled)
|
|
.then(a.name.cmp(&b.name))
|
|
.then(a.info.endpoint_type.cmp(&b.info.endpoint_type))
|
|
});
|
|
Ok(syncs)
|
|
}
|
|
}
|
|
|
|
// TABLE
|
|
|
|
impl PrintTable for ResourceListItem<ServerListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Server", "State", "Address", "Tags", "Link"]
|
|
} else {
|
|
&["Server", "State", "Address", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<Cell> {
|
|
let color = match self.info.state {
|
|
ServerState::Ok => Color::Green,
|
|
ServerState::NotOk => Color::Red,
|
|
ServerState::Disabled => Color::Blue,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.address.as_deref().unwrap_or("inbound")),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Server,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<StackListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Stack", "State", "Server", "Tags", "Link"]
|
|
} else {
|
|
&["Stack", "State", "Server", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
StackState::Down => Color::Blue,
|
|
StackState::Running => Color::Green,
|
|
StackState::Deploying => Color::DarkYellow,
|
|
StackState::Paused => Color::DarkYellow,
|
|
StackState::Unknown => Color::Magenta,
|
|
_ => Color::Red,
|
|
};
|
|
// let source = if self.info.files_on_host {
|
|
// "On Host"
|
|
// } else if !self.info.repo.is_empty() {
|
|
// self.info.repo_link.as_str()
|
|
// } else {
|
|
// "UI Defined"
|
|
// };
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.server_id),
|
|
// Cell::new(source),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Stack,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<DeploymentListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Deployment", "State", "Server", "Tags", "Link"]
|
|
} else {
|
|
&["Deployment", "State", "Server", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
DeploymentState::NotDeployed => Color::Blue,
|
|
DeploymentState::Running => Color::Green,
|
|
DeploymentState::Deploying => Color::DarkYellow,
|
|
DeploymentState::Paused => Color::DarkYellow,
|
|
DeploymentState::Unknown => Color::Magenta,
|
|
_ => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.server_id),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Deployment,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<BuildListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Build", "State", "Builder", "Tags", "Link"]
|
|
} else {
|
|
&["Build", "State", "Builder", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
BuildState::Ok => Color::Green,
|
|
BuildState::Building => Color::DarkYellow,
|
|
BuildState::Unknown => Color::Magenta,
|
|
BuildState::Failed => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.builder_id),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Build,
|
|
&self.id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<RepoListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Repo", "State", "Link", "Tags", "Link"]
|
|
} else {
|
|
&["Repo", "State", "Link", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
RepoState::Ok => Color::Green,
|
|
RepoState::Building
|
|
| RepoState::Cloning
|
|
| RepoState::Pulling => Color::DarkYellow,
|
|
RepoState::Unknown => Color::Magenta,
|
|
RepoState::Failed => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.repo_link),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Repo,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<ProcedureListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Procedure", "State", "Next Run", "Tags", "Link"]
|
|
} else {
|
|
&["Procedure", "State", "Next Run", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
ProcedureState::Ok => Color::Green,
|
|
ProcedureState::Running => Color::DarkYellow,
|
|
ProcedureState::Unknown => Color::Magenta,
|
|
ProcedureState::Failed => Color::Red,
|
|
};
|
|
let next_run = if let Some(ts) = self.info.next_scheduled_run {
|
|
Cell::new(
|
|
format_timetamp(ts)
|
|
.unwrap_or(String::from("Invalid next ts")),
|
|
)
|
|
.add_attribute(Attribute::Bold)
|
|
} else {
|
|
Cell::new(String::from("None"))
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
next_run,
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Procedure,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<ActionListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Action", "State", "Next Run", "Tags", "Link"]
|
|
} else {
|
|
&["Action", "State", "Next Run", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
ActionState::Ok => Color::Green,
|
|
ActionState::Running => Color::DarkYellow,
|
|
ActionState::Unknown => Color::Magenta,
|
|
ActionState::Failed => Color::Red,
|
|
};
|
|
let next_run = if let Some(ts) = self.info.next_scheduled_run {
|
|
Cell::new(
|
|
format_timetamp(ts)
|
|
.unwrap_or(String::from("Invalid next ts")),
|
|
)
|
|
.add_attribute(Attribute::Bold)
|
|
} else {
|
|
Cell::new(String::from("None"))
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
next_run,
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Action,
|
|
&self.id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<ResourceSyncListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Sync", "State", "Tags", "Link"]
|
|
} else {
|
|
&["Sync", "State", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
ResourceSyncState::Ok => Color::Green,
|
|
ResourceSyncState::Pending | ResourceSyncState::Syncing => {
|
|
Color::DarkYellow
|
|
}
|
|
ResourceSyncState::Unknown => Color::Magenta,
|
|
ResourceSyncState::Failed => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::ResourceSync,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<BuilderListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Builder", "Type", "Tags", "Link"]
|
|
} else {
|
|
&["Builder", "Type", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.builder_type),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Builder,
|
|
&self.id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<AlerterListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Alerter", "Type", "Enabled", "Tags", "Link"]
|
|
} else {
|
|
&["Alerter", "Type", "Enabled", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let mut row = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.endpoint_type),
|
|
if self.info.enabled {
|
|
Cell::new(self.info.enabled.to_string()).fg(Color::Green)
|
|
} else {
|
|
Cell::new(self.info.enabled.to_string()).fg(Color::Red)
|
|
},
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
row.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Alerter,
|
|
&self.id,
|
|
)));
|
|
}
|
|
row
|
|
}
|
|
}
|
|
|
|
impl PrintTable for Terminal {
|
|
fn header(_links: bool) -> &'static [&'static str] {
|
|
&["Terminal", "Target", "Command", "Size", "Created"]
|
|
}
|
|
fn row(self, _links: bool) -> Vec<comfy_table::Cell> {
|
|
vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(format!("{:?}", self.target)),
|
|
Cell::new(self.command),
|
|
Cell::new(if self.stored_size_kb < 1.0 {
|
|
format!("{:.1} KiB", self.stored_size_kb)
|
|
} else {
|
|
format!("{:.} KiB", self.stored_size_kb)
|
|
}),
|
|
Cell::new(
|
|
format_timetamp(self.created_at)
|
|
.unwrap_or_else(|_| String::from("Invalid created at")),
|
|
),
|
|
]
|
|
}
|
|
}
|
|
|
|
impl PrintTable for Schedule {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Name", "Type", "Next Run", "Tags", "Link"]
|
|
} else {
|
|
&["Name", "Type", "Next Run", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let next_run = if let Some(ts) = self.next_scheduled_run {
|
|
Cell::new(
|
|
format_timetamp(ts)
|
|
.unwrap_or_else(|_| String::from("Invalid next ts")),
|
|
)
|
|
.add_attribute(Attribute::Bold)
|
|
} else {
|
|
Cell::new(String::from("None"))
|
|
};
|
|
let (resource_type, id) = self.target.extract_variant_id();
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.target.extract_variant_id().0),
|
|
next_run,
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
resource_type,
|
|
id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|