mirror of
https://github.com/moghtech/komodo.git
synced 2025-12-30 16:59:46 -06:00
1.16.0 (#140)
* consolidate deserializers * key value list doc * use string list deserializers for all entity Vec<String> * add additional env files support * plumbing for Action resource * js client readme indentation * regen lock * add action UI * action backend * start on action frontend * update lock * get up to speed * get action started * clean up default action file * seems to work * toml export include action * action works * action works part 2 * bump rust version to 1.82.0 * copy deno bin from bin image * action use local dir * update not having changes doesn't return error * format with prettier * support yaml formatting with prettier * variable no change is Ok
This commit is contained in:
474
Cargo.lock
generated
474
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.15.12"
|
||||
version = "1.16.0"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -64,12 +64,12 @@ tokio-tungstenite = "0.24.0"
|
||||
ordered_hash_map = { version = "0.4.0", features = ["serde"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
serde_json = "1.0.132"
|
||||
serde_yaml = "0.9.34"
|
||||
toml = "0.8.19"
|
||||
|
||||
# ERROR
|
||||
anyhow = "1.0.89"
|
||||
anyhow = "1.0.90"
|
||||
thiserror = "1.0.64"
|
||||
|
||||
# LOGGING
|
||||
|
||||
@@ -21,6 +21,9 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> {
|
||||
Execution::None(data) => {
|
||||
println!("{}: {data:?}", "Data".dimmed())
|
||||
}
|
||||
Execution::RunAction(data) => {
|
||||
println!("{}: {data:?}", "Data".dimmed())
|
||||
}
|
||||
Execution::RunProcedure(data) => {
|
||||
println!("{}: {data:?}", "Data".dimmed())
|
||||
}
|
||||
@@ -168,6 +171,9 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> {
|
||||
info!("Running Execution...");
|
||||
|
||||
let res = match execution {
|
||||
Execution::RunAction(request) => {
|
||||
komodo_client().execute(request).await
|
||||
}
|
||||
Execution::RunProcedure(request) => {
|
||||
komodo_client().execute(request).await
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ komodo_client = { workspace = true, features = ["mongo"] }
|
||||
periphery_client.workspace = true
|
||||
environment_file.workspace = true
|
||||
formatting.workspace = true
|
||||
command.workspace = true
|
||||
logger.workspace = true
|
||||
git.workspace = true
|
||||
# mogh
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
## and may negatively affect runtime performance.
|
||||
|
||||
# Build Core
|
||||
FROM rust:1.81.0-alpine AS core-builder
|
||||
FROM rust:1.82.0-alpine AS core-builder
|
||||
WORKDIR /builder
|
||||
RUN apk update && apk --no-cache add musl-dev openssl-dev openssl-libs-static
|
||||
COPY . .
|
||||
@@ -23,7 +23,7 @@ FROM alpine:3.20
|
||||
|
||||
# Install Deps
|
||||
RUN apk update && apk add --no-cache --virtual .build-deps \
|
||||
openssl ca-certificates git git-lfs
|
||||
openssl ca-certificates git git-lfs curl
|
||||
|
||||
# Setup an application directory
|
||||
WORKDIR /app
|
||||
@@ -32,6 +32,7 @@ WORKDIR /app
|
||||
COPY ./config/core.config.toml /config/config.toml
|
||||
COPY --from=core-builder /builder/target/release/core /app
|
||||
COPY --from=frontend-builder /builder/frontend/dist /app/frontend
|
||||
COPY --from=denoland/deno:bin /deno /usr/local/bin/deno
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9120
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build Core
|
||||
FROM rust:1.81.0-bullseye AS core-builder
|
||||
FROM rust:1.82.0-bullseye AS core-builder
|
||||
WORKDIR /builder
|
||||
COPY . .
|
||||
RUN cargo build -p komodo_core --release
|
||||
@@ -27,6 +27,7 @@ WORKDIR /app
|
||||
COPY ./config/core.config.toml /config/config.toml
|
||||
COPY --from=core-builder /builder/target/release/core /app
|
||||
COPY --from=frontend-builder /builder/frontend/dist /app/frontend
|
||||
COPY --from=denoland/deno:bin /deno /usr/local/bin/deno
|
||||
|
||||
# Hint at the port
|
||||
EXPOSE 9120
|
||||
|
||||
@@ -201,6 +201,9 @@ fn resource_link(
|
||||
ResourceTargetVariant::Procedure => {
|
||||
format!("/procedures/{id}")
|
||||
}
|
||||
ResourceTargetVariant::Action => {
|
||||
format!("/actions/{id}")
|
||||
}
|
||||
ResourceTargetVariant::ServerTemplate => {
|
||||
format!("/server-templates/{id}")
|
||||
}
|
||||
|
||||
206
bin/core/src/api/execute/action.rs
Normal file
206
bin/core/src/api/execute/action.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Context;
|
||||
use command::run_komodo_command;
|
||||
use komodo_client::{
|
||||
api::{
|
||||
execute::RunAction,
|
||||
user::{CreateApiKey, CreateApiKeyResponse, DeleteApiKey},
|
||||
},
|
||||
entities::{
|
||||
action::Action,
|
||||
config::core::CoreConfig,
|
||||
permission::PermissionLevel,
|
||||
update::Update,
|
||||
user::{action_user, User},
|
||||
},
|
||||
};
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::to_document};
|
||||
use resolver_api::Resolve;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
config::core_config,
|
||||
helpers::{
|
||||
interpolate::{
|
||||
add_interp_update_log,
|
||||
interpolate_variables_secrets_into_string,
|
||||
},
|
||||
query::get_variables_and_secrets,
|
||||
random_string,
|
||||
update::update_update,
|
||||
},
|
||||
resource::{self, refresh_action_state_cache},
|
||||
state::{action_states, db_client, State},
|
||||
};
|
||||
|
||||
impl Resolve<RunAction, (User, Update)> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunAction { action }: RunAction,
|
||||
(user, mut update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let mut action = resource::get_check_permissions::<Action>(
|
||||
&action,
|
||||
&user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// get the action state for the action (or insert default).
|
||||
let action_state = action_states()
|
||||
.action
|
||||
.get_or_insert_default(&action.id)
|
||||
.await;
|
||||
|
||||
// This will set action state back to default when dropped.
|
||||
// Will also check to ensure action not already busy before updating.
|
||||
let _action_guard =
|
||||
action_state.update(|state| state.running = true)?;
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let CreateApiKeyResponse { key, secret } = State
|
||||
.resolve(
|
||||
CreateApiKey {
|
||||
name: update.id.clone(),
|
||||
expires: 0,
|
||||
},
|
||||
action_user().to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let contents = &mut action.config.file_contents;
|
||||
|
||||
// Wrap the file contents in the execution context.
|
||||
*contents = full_contents(contents, &key, &secret);
|
||||
|
||||
let replacers =
|
||||
interpolate(contents, &mut update, key.clone(), secret.clone())
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let path = core_config()
|
||||
.action_directory
|
||||
.join(format!("{}.ts", random_string(10)));
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = fs::create_dir_all(parent).await;
|
||||
}
|
||||
|
||||
fs::write(&path, contents).await.with_context(|| {
|
||||
format!("Faild to write action file to {path:?}")
|
||||
})?;
|
||||
|
||||
let mut res = run_komodo_command(
|
||||
// Keep this stage name as is, the UI will find the latest update log by matching the stage name
|
||||
"Execute Action",
|
||||
None,
|
||||
format!("deno run --allow-read --allow-net --allow-import {}", path.display()),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
res.stdout = svi::replace_in_string(&res.stdout, &replacers)
|
||||
.replace(&key, "<ACTION_API_KEY>");
|
||||
res.stderr = svi::replace_in_string(&res.stderr, &replacers)
|
||||
.replace(&secret, "<ACTION_API_SECRET>");
|
||||
|
||||
if let Err(e) = fs::remove_file(path).await {
|
||||
warn!(
|
||||
"Failed to delete action file after action execution | {e:#}"
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = State
|
||||
.resolve(DeleteApiKey { key }, action_user().to_owned())
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Failed to delete API key after action execution | {e:#}"
|
||||
);
|
||||
};
|
||||
|
||||
update.logs.push(res);
|
||||
update.finalize();
|
||||
|
||||
// Need to manually update the update before cache refresh,
|
||||
// and before broadcast with update_update.
|
||||
// The Err case of to_document should be unreachable,
|
||||
// but will fail to update cache in that case.
|
||||
if let Ok(update_doc) = to_document(&update) {
|
||||
let _ = update_one_by_id(
|
||||
&db_client().updates,
|
||||
&update.id,
|
||||
mungos::update::Update::Set(update_doc),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
refresh_action_state_cache().await;
|
||||
}
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
async fn interpolate(
|
||||
contents: &mut String,
|
||||
update: &mut Update,
|
||||
key: String,
|
||||
secret: String,
|
||||
) -> anyhow::Result<HashSet<(String, String)>> {
|
||||
let mut vars_and_secrets = get_variables_and_secrets().await?;
|
||||
|
||||
vars_and_secrets
|
||||
.secrets
|
||||
.insert(String::from("ACTION_API_KEY"), key);
|
||||
vars_and_secrets
|
||||
.secrets
|
||||
.insert(String::from("ACTION_API_SECRET"), secret);
|
||||
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
|
||||
interpolate_variables_secrets_into_string(
|
||||
&vars_and_secrets,
|
||||
contents,
|
||||
&mut global_replacers,
|
||||
&mut secret_replacers,
|
||||
)?;
|
||||
|
||||
add_interp_update_log(update, &global_replacers, &secret_replacers);
|
||||
|
||||
Ok(secret_replacers)
|
||||
}
|
||||
|
||||
fn full_contents(contents: &str, key: &str, secret: &str) -> String {
|
||||
let CoreConfig {
|
||||
port, ssl_enabled, ..
|
||||
} = core_config();
|
||||
let protocol = if *ssl_enabled { "https" } else { "http" };
|
||||
let base_url = format!("{protocol}://localhost:{port}");
|
||||
format!(
|
||||
"import {{ KomodoClient }} from '{base_url}/client/lib.js';
|
||||
|
||||
const komodo = KomodoClient('{base_url}', {{
|
||||
type: 'api-key',
|
||||
params: {{ key: '{key}', secret: '{secret}' }}
|
||||
}});
|
||||
|
||||
async function main() {{{contents}}}
|
||||
|
||||
main().catch(error => {{
|
||||
console.error('🚨 Action exited early with errors 🚨')
|
||||
if (error.status !== undefined && error.result !== undefined) {{
|
||||
console.error('Status:', error.status);
|
||||
console.error(JSON.stringify(error.result, null, 2));
|
||||
}} else {{
|
||||
console.error(JSON.stringify(error, null, 2));
|
||||
}}
|
||||
Deno.exit(1)
|
||||
}}).then(() => console.log('🦎 Action completed successfully 🦎'));"
|
||||
)
|
||||
}
|
||||
@@ -24,6 +24,7 @@ use crate::{
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
mod action;
|
||||
mod build;
|
||||
mod deployment;
|
||||
mod procedure;
|
||||
@@ -97,6 +98,9 @@ pub enum ExecuteRequest {
|
||||
// ==== PROCEDURE ====
|
||||
RunProcedure(RunProcedure),
|
||||
|
||||
// ==== ACTION ====
|
||||
RunAction(RunAction),
|
||||
|
||||
// ==== SERVER TEMPLATE ====
|
||||
LaunchServer(LaunchServer),
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use komodo_client::{
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
entities::{
|
||||
self,
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
@@ -126,6 +127,10 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
.procedures
|
||||
.get(&name_or_id)
|
||||
.map(|p| p.name.clone()),
|
||||
ResourceTargetVariant::Action => all_resources
|
||||
.actions
|
||||
.get(&name_or_id)
|
||||
.map(|p| p.name.clone()),
|
||||
ResourceTargetVariant::Repo => all_resources
|
||||
.repos
|
||||
.get(&name_or_id)
|
||||
@@ -270,6 +275,17 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
&sync.config.match_tags,
|
||||
)
|
||||
.await?;
|
||||
let (actions_to_create, actions_to_update, actions_to_delete) =
|
||||
get_updates_for_execution::<Action>(
|
||||
resources.actions,
|
||||
delete,
|
||||
&all_resources,
|
||||
match_resource_type,
|
||||
match_resources.as_deref(),
|
||||
&id_to_tags,
|
||||
&sync.config.match_tags,
|
||||
)
|
||||
.await?;
|
||||
let (builders_to_create, builders_to_update, builders_to_delete) =
|
||||
get_updates_for_execution::<Builder>(
|
||||
resources.builders,
|
||||
@@ -388,6 +404,9 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
&& procedures_to_create.is_empty()
|
||||
&& procedures_to_update.is_empty()
|
||||
&& procedures_to_delete.is_empty()
|
||||
&& actions_to_create.is_empty()
|
||||
&& actions_to_update.is_empty()
|
||||
&& actions_to_delete.is_empty()
|
||||
&& user_groups_to_create.is_empty()
|
||||
&& user_groups_to_update.is_empty()
|
||||
&& user_groups_to_delete.is_empty()
|
||||
@@ -464,6 +483,15 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
)
|
||||
.await,
|
||||
);
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Action::execute_sync_updates(
|
||||
actions_to_create,
|
||||
actions_to_update,
|
||||
actions_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
// Dependent on server
|
||||
maybe_extend(
|
||||
|
||||
132
bin/core/src/api/read/action.rs
Normal file
132
bin/core/src/api/read/action.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use anyhow::Context;
|
||||
use komodo_client::{
|
||||
api::read::*,
|
||||
entities::{
|
||||
action::{
|
||||
Action, ActionActionState, ActionListItem, ActionState,
|
||||
},
|
||||
permission::PermissionLevel,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_all_tags,
|
||||
resource,
|
||||
state::{action_state_cache, action_states, State},
|
||||
};
|
||||
|
||||
impl Resolve<GetAction, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetAction { action }: GetAction,
|
||||
user: User,
|
||||
) -> anyhow::Result<Action> {
|
||||
resource::get_check_permissions::<Action>(
|
||||
&action,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListActions, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListActions { query }: ListActions,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<ActionListItem>> {
|
||||
let all_tags = if query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
resource::list_for_user::<Action>(query, &user, &all_tags).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<ListFullActions, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListFullActions { query }: ListFullActions,
|
||||
user: User,
|
||||
) -> anyhow::Result<ListFullActionsResponse> {
|
||||
let all_tags = if query.tags.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
resource::list_full_for_user::<Action>(query, &user, &all_tags)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetActionActionState, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetActionActionState { action }: GetActionActionState,
|
||||
user: User,
|
||||
) -> anyhow::Result<ActionActionState> {
|
||||
let action = resource::get_check_permissions::<Action>(
|
||||
&action,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
let action_state = action_states()
|
||||
.action
|
||||
.get(&action.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?;
|
||||
Ok(action_state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<GetActionsSummary, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
GetActionsSummary {}: GetActionsSummary,
|
||||
user: User,
|
||||
) -> anyhow::Result<GetActionsSummaryResponse> {
|
||||
let actions = resource::list_full_for_user::<Action>(
|
||||
Default::default(),
|
||||
&user,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.context("failed to get actions from db")?;
|
||||
|
||||
let mut res = GetActionsSummaryResponse::default();
|
||||
|
||||
let cache = action_state_cache();
|
||||
let action_states = action_states();
|
||||
|
||||
for action in actions {
|
||||
res.total += 1;
|
||||
|
||||
match (
|
||||
cache.get(&action.id).await.unwrap_or_default(),
|
||||
action_states
|
||||
.action
|
||||
.get(&action.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.get()?,
|
||||
) {
|
||||
(_, action_states) if action_states.running => {
|
||||
res.running += 1;
|
||||
}
|
||||
(ActionState::Ok, _) => res.ok += 1,
|
||||
(ActionState::Failed, _) => res.failed += 1,
|
||||
(ActionState::Unknown, _) => res.unknown += 1,
|
||||
// will never come off the cache in the running state, since that comes from action states
|
||||
(ActionState::Running, _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ use crate::{
|
||||
resource, state::State,
|
||||
};
|
||||
|
||||
mod action;
|
||||
mod alert;
|
||||
mod alerter;
|
||||
mod build;
|
||||
@@ -88,6 +89,13 @@ enum ReadRequest {
|
||||
ListProcedures(ListProcedures),
|
||||
ListFullProcedures(ListFullProcedures),
|
||||
|
||||
// ==== ACTION ====
|
||||
GetActionsSummary(GetActionsSummary),
|
||||
GetAction(GetAction),
|
||||
GetActionActionState(GetActionActionState),
|
||||
ListActions(ListActions),
|
||||
ListFullActions(ListFullActions),
|
||||
|
||||
// ==== SERVER TEMPLATE ====
|
||||
GetServerTemplate(GetServerTemplate),
|
||||
GetServerTemplatesSummary(GetServerTemplatesSummary),
|
||||
|
||||
@@ -6,7 +6,7 @@ use komodo_client::{
|
||||
ListUserGroups,
|
||||
},
|
||||
entities::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
action::Action, alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
procedure::Procedure, repo::Repo, resource::ResourceQuery,
|
||||
server::Server, server_template::ServerTemplate, stack::Stack,
|
||||
@@ -124,6 +124,16 @@ impl Resolve<ExportAllResourcesToToml, User> for State {
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Procedure(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<Action>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::Action(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<ServerTemplate>(
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
@@ -339,6 +349,21 @@ impl Resolve<ExportResourcesToToml, User> for State {
|
||||
&id_to_tags,
|
||||
));
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
let mut action = resource::get_check_permissions::<Action>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
Action::replace_ids(&mut action, &all);
|
||||
res.actions.push(convert_resource::<Action>(
|
||||
action,
|
||||
false,
|
||||
vec![],
|
||||
&id_to_tags,
|
||||
));
|
||||
}
|
||||
ResourceTarget::System(_) => continue,
|
||||
};
|
||||
}
|
||||
@@ -442,6 +467,14 @@ fn serialize_resources_toml(
|
||||
Procedure::push_to_toml_string(procedure, &mut toml)?;
|
||||
}
|
||||
|
||||
for action in resources.actions {
|
||||
if !toml.is_empty() {
|
||||
toml.push_str("\n\n##\n\n");
|
||||
}
|
||||
toml.push_str("[[action]]\n");
|
||||
Action::push_to_toml_string(action, &mut toml)?;
|
||||
}
|
||||
|
||||
for alerter in resources.alerters {
|
||||
if !toml.is_empty() {
|
||||
toml.push_str("\n\n##\n\n");
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Context};
|
||||
use komodo_client::{
|
||||
api::read::{GetUpdate, ListUpdates, ListUpdatesResponse},
|
||||
entities::{
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
@@ -104,15 +105,15 @@ impl Resolve<ListUpdates, User> for State {
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
|
||||
|
||||
// let action_query =
|
||||
// resource::get_resource_ids_for_user::<Action>(&user)
|
||||
// .await?
|
||||
// .map(|ids| {
|
||||
// doc! {
|
||||
// "target.type": "Action", "target.id": { "$in": ids }
|
||||
// }
|
||||
// })
|
||||
// .unwrap_or_else(|| doc! { "target.type": "Action" });
|
||||
let action_query =
|
||||
resource::get_resource_ids_for_user::<Action>(&user)
|
||||
.await?
|
||||
.map(|ids| {
|
||||
doc! {
|
||||
"target.type": "Action", "target.id": { "$in": ids }
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| doc! { "target.type": "Action" });
|
||||
|
||||
let builder_query =
|
||||
resource::get_resource_ids_for_user::<Builder>(&user)
|
||||
@@ -165,7 +166,7 @@ impl Resolve<ListUpdates, User> for State {
|
||||
build_query,
|
||||
repo_query,
|
||||
procedure_query,
|
||||
// action_query,
|
||||
action_query,
|
||||
alerter_query,
|
||||
builder_query,
|
||||
server_template_query,
|
||||
@@ -303,6 +304,14 @@ impl Resolve<GetUpdate, User> for State {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
resource::get_check_permissions::<Action>(
|
||||
id,
|
||||
&user,
|
||||
PermissionLevel::Read,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
resource::get_check_permissions::<ServerTemplate>(
|
||||
id,
|
||||
|
||||
59
bin/core/src/api/write/action.rs
Normal file
59
bin/core/src/api/write/action.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use komodo_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
action::Action, permission::PermissionLevel, user::User,
|
||||
},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource, state::State};
|
||||
|
||||
impl Resolve<CreateAction, User> for State {
|
||||
#[instrument(name = "CreateAction", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateAction { name, config }: CreateAction,
|
||||
user: User,
|
||||
) -> anyhow::Result<Action> {
|
||||
resource::create::<Action>(&name, config, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CopyAction, User> for State {
|
||||
#[instrument(name = "CopyAction", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CopyAction { name, id }: CopyAction,
|
||||
user: User,
|
||||
) -> anyhow::Result<Action> {
|
||||
let Action { config, .. } = resource::get_check_permissions::<
|
||||
Action,
|
||||
>(
|
||||
&id, &user, PermissionLevel::Write
|
||||
)
|
||||
.await?;
|
||||
resource::create::<Action>(&name, config.into(), &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<UpdateAction, User> for State {
|
||||
#[instrument(name = "UpdateAction", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
UpdateAction { id, config }: UpdateAction,
|
||||
user: User,
|
||||
) -> anyhow::Result<Action> {
|
||||
resource::update::<Action>(&id, config, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteAction, User> for State {
|
||||
#[instrument(name = "DeleteAction", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteAction { id }: DeleteAction,
|
||||
user: User,
|
||||
) -> anyhow::Result<Action> {
|
||||
resource::delete::<Action>(&id, &user).await
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use anyhow::anyhow;
|
||||
use komodo_client::{
|
||||
api::write::{UpdateDescription, UpdateDescriptionResponse},
|
||||
entities::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
action::Action, alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, server_template::ServerTemplate, stack::Stack,
|
||||
sync::ResourceSync, user::User, ResourceTarget,
|
||||
@@ -84,6 +84,14 @@ impl Resolve<UpdateDescription, User> for State {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
resource::update_description::<Action>(
|
||||
&id,
|
||||
&description,
|
||||
&user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
resource::update_description::<ServerTemplate>(
|
||||
&id,
|
||||
|
||||
@@ -13,6 +13,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{auth::auth_request, state::State};
|
||||
|
||||
mod action;
|
||||
mod alerter;
|
||||
mod build;
|
||||
mod builder;
|
||||
@@ -125,6 +126,12 @@ pub enum WriteRequest {
|
||||
DeleteProcedure(DeleteProcedure),
|
||||
UpdateProcedure(UpdateProcedure),
|
||||
|
||||
// ==== ACTION ====
|
||||
CreateAction(CreateAction),
|
||||
CopyAction(CopyAction),
|
||||
DeleteAction(DeleteAction),
|
||||
UpdateAction(UpdateAction),
|
||||
|
||||
// ==== SYNC ====
|
||||
CreateResourceSync(CreateResourceSync),
|
||||
CopyResourceSync(CopyResourceSync),
|
||||
|
||||
@@ -387,6 +387,20 @@ async fn extract_resource_target_with_validation(
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Procedure, id))
|
||||
}
|
||||
ResourceTarget::Action(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
Err(_) => doc! { "name": ident },
|
||||
};
|
||||
let id = db_client()
|
||||
.actions
|
||||
.find_one(filter)
|
||||
.await
|
||||
.context("failed to query db for actions")?
|
||||
.context("no matching action found")?
|
||||
.id;
|
||||
Ok((ResourceTargetVariant::Action, id))
|
||||
}
|
||||
ResourceTarget::ServerTemplate(ident) => {
|
||||
let filter = match ObjectId::from_str(ident) {
|
||||
Ok(id) => doc! { "_id": id },
|
||||
|
||||
@@ -5,28 +5,9 @@ use formatting::format_serror;
|
||||
use komodo_client::{
|
||||
api::{read::ExportAllResourcesToToml, write::*},
|
||||
entities::{
|
||||
self,
|
||||
alert::{Alert, AlertData, SeverityLevel},
|
||||
alerter::Alerter,
|
||||
all_logs_success,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
config::core::CoreConfig,
|
||||
deployment::Deployment,
|
||||
komodo_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_template::ServerTemplate,
|
||||
stack::Stack,
|
||||
sync::{
|
||||
self, action::Action, alert::{Alert, AlertData, SeverityLevel}, alerter::Alerter, all_logs_success, build::Build, builder::Builder, config::core::CoreConfig, deployment::Deployment, komodo_timestamp, permission::PermissionLevel, procedure::Procedure, repo::Repo, server::Server, server_template::ServerTemplate, stack::Stack, sync::{
|
||||
PartialResourceSyncConfig, ResourceSync, ResourceSyncInfo,
|
||||
},
|
||||
to_komodo_name,
|
||||
update::{Log, Update},
|
||||
user::{sync_user, User},
|
||||
CloneArgs, NoData, Operation, ResourceTarget,
|
||||
}, to_komodo_name, update::{Log, Update}, user::{sync_user, User}, CloneArgs, NoData, Operation, ResourceTarget
|
||||
},
|
||||
};
|
||||
use mungos::{
|
||||
@@ -535,6 +516,17 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
&mut diffs,
|
||||
)
|
||||
.await?;
|
||||
push_updates_for_view::<Action>(
|
||||
resources.actions,
|
||||
delete,
|
||||
&all_resources,
|
||||
None,
|
||||
None,
|
||||
&id_to_tags,
|
||||
&sync.config.match_tags,
|
||||
&mut diffs,
|
||||
)
|
||||
.await?;
|
||||
push_updates_for_view::<Builder>(
|
||||
resources.builders,
|
||||
delete,
|
||||
|
||||
@@ -7,7 +7,7 @@ use komodo_client::{
|
||||
UpdateTagsOnResourceResponse,
|
||||
},
|
||||
entities::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
action::Action, alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, permission::PermissionLevel,
|
||||
procedure::Procedure, repo::Repo, server::Server,
|
||||
server_template::ServerTemplate, stack::Stack,
|
||||
@@ -182,6 +182,15 @@ impl Resolve<UpdateTagsOnResource, User> for State {
|
||||
.await?;
|
||||
resource::update_tags::<Procedure>(&id, tags, user).await?
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
resource::get_check_permissions::<Action>(
|
||||
&id,
|
||||
&user,
|
||||
PermissionLevel::Write,
|
||||
)
|
||||
.await?;
|
||||
resource::update_tags::<Action>(&id, tags, user).await?
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
resource::get_check_permissions::<ServerTemplate>(
|
||||
&id,
|
||||
|
||||
@@ -81,7 +81,7 @@ impl Resolve<UpdateVariableValue, User> for State {
|
||||
let variable = get_variable(&name).await?;
|
||||
|
||||
if value == variable.value {
|
||||
return Err(anyhow!("no change"));
|
||||
return Ok(variable);
|
||||
}
|
||||
|
||||
db_client()
|
||||
|
||||
@@ -150,6 +150,9 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
repo_directory: env
|
||||
.komodo_repo_directory
|
||||
.unwrap_or(config.repo_directory),
|
||||
action_directory: env
|
||||
.komodo_action_directory
|
||||
.unwrap_or(config.action_directory),
|
||||
resource_poll_interval: env
|
||||
.komodo_resource_poll_interval
|
||||
.unwrap_or(config.resource_poll_interval),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use komodo_client::entities::{
|
||||
action::Action,
|
||||
alert::Alert,
|
||||
alerter::Alerter,
|
||||
api_key::ApiKey,
|
||||
@@ -47,6 +48,7 @@ pub struct DbClient {
|
||||
pub builders: Collection<Builder>,
|
||||
pub repos: Collection<Repo>,
|
||||
pub procedures: Collection<Procedure>,
|
||||
pub actions: Collection<Action>,
|
||||
pub alerters: Collection<Alerter>,
|
||||
pub server_templates: Collection<ServerTemplate>,
|
||||
pub resource_syncs: Collection<ResourceSync>,
|
||||
@@ -115,6 +117,7 @@ impl DbClient {
|
||||
repos: resource_collection(&db, "Repo").await?,
|
||||
alerters: resource_collection(&db, "Alerter").await?,
|
||||
procedures: resource_collection(&db, "Procedure").await?,
|
||||
actions: resource_collection(&db, "Action").await?,
|
||||
server_templates: resource_collection(&db, "ServerTemplate")
|
||||
.await?,
|
||||
resource_syncs: resource_collection(&db, "ResourceSync")
|
||||
|
||||
@@ -4,7 +4,8 @@ use anyhow::anyhow;
|
||||
use komodo_client::{
|
||||
busy::Busy,
|
||||
entities::{
|
||||
build::BuildActionState, deployment::DeploymentActionState,
|
||||
action::ActionActionState, build::BuildActionState,
|
||||
deployment::DeploymentActionState,
|
||||
procedure::ProcedureActionState, repo::RepoActionState,
|
||||
server::ServerActionState, stack::StackActionState,
|
||||
sync::ResourceSyncActionState,
|
||||
@@ -22,6 +23,7 @@ pub struct ActionStates {
|
||||
pub repo: Cache<String, Arc<ActionState<RepoActionState>>>,
|
||||
pub procedure:
|
||||
Cache<String, Arc<ActionState<ProcedureActionState>>>,
|
||||
pub action: Cache<String, Arc<ActionState<ActionActionState>>>,
|
||||
pub resource_sync:
|
||||
Cache<String, Arc<ActionState<ResourceSyncActionState>>>,
|
||||
pub stack: Cache<String, Arc<ActionState<StackActionState>>>,
|
||||
|
||||
@@ -146,6 +146,22 @@ async fn execute_execution(
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::RunAction(req) => {
|
||||
let req = ExecuteRequest::RunAction(req);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
let ExecuteRequest::RunAction(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
let update_id = update.id.clone();
|
||||
handle_resolve_result(
|
||||
State
|
||||
.resolve(req, (user, update))
|
||||
.await
|
||||
.context("Failed at RunAction"),
|
||||
&update_id,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Execution::RunBuild(req) => {
|
||||
let req = ExecuteRequest::RunBuild(req);
|
||||
let update = init_execution_update(&req, &user).await?;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use komodo_client::entities::{
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
@@ -291,6 +292,9 @@ pub async fn get_user_permission_on_target(
|
||||
ResourceTarget::Procedure(id) => {
|
||||
get_user_permission_on_resource::<Procedure>(user, id).await
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
get_user_permission_on_resource::<Action>(user, id).await
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
get_user_permission_on_resource::<ServerTemplate>(user, id)
|
||||
.await
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
action::Action,
|
||||
build::Build,
|
||||
deployment::Deployment,
|
||||
komodo_timestamp,
|
||||
@@ -345,6 +346,14 @@ pub async fn init_execution_update(
|
||||
),
|
||||
),
|
||||
|
||||
// Action
|
||||
ExecuteRequest::RunAction(data) => (
|
||||
Operation::RunAction,
|
||||
ResourceTarget::Action(
|
||||
resource::get::<Action>(&data.action).await?.id,
|
||||
),
|
||||
),
|
||||
|
||||
// Server template
|
||||
ExecuteRequest::LaunchServer(data) => (
|
||||
Operation::LaunchServer,
|
||||
|
||||
@@ -57,6 +57,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
resource::spawn_build_state_refresh_loop();
|
||||
resource::spawn_repo_state_refresh_loop();
|
||||
resource::spawn_procedure_state_refresh_loop();
|
||||
resource::spawn_action_state_refresh_loop();
|
||||
resource::spawn_resource_sync_state_refresh_loop();
|
||||
helpers::prune::spawn_prune_loop();
|
||||
|
||||
|
||||
214
bin/core/src/resource/action.rs
Normal file
214
bin/core/src/resource/action.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
action::{
|
||||
Action, ActionConfig, ActionConfigDiff, ActionInfo,
|
||||
ActionListItem, ActionListItemInfo, ActionQuerySpecifics,
|
||||
ActionState, PartialActionConfig,
|
||||
},
|
||||
resource::Resource,
|
||||
update::Update,
|
||||
user::User,
|
||||
Operation, ResourceTargetVariant,
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::{bson::doc, options::FindOneOptions, Collection},
|
||||
};
|
||||
|
||||
use crate::state::{action_state_cache, action_states, db_client};
|
||||
|
||||
impl super::KomodoResource for Action {
|
||||
type Config = ActionConfig;
|
||||
type PartialConfig = PartialActionConfig;
|
||||
type ConfigDiff = ActionConfigDiff;
|
||||
type Info = ActionInfo;
|
||||
type ListItem = ActionListItem;
|
||||
type QuerySpecifics = ActionQuerySpecifics;
|
||||
|
||||
fn resource_type() -> ResourceTargetVariant {
|
||||
ResourceTargetVariant::Action
|
||||
}
|
||||
|
||||
async fn coll(
|
||||
) -> &'static Collection<Resource<Self::Config, Self::Info>> {
|
||||
&db_client().actions
|
||||
}
|
||||
|
||||
async fn to_list_item(
|
||||
action: Resource<Self::Config, Self::Info>,
|
||||
) -> Self::ListItem {
|
||||
let state = get_action_state(&action.id).await;
|
||||
ActionListItem {
|
||||
name: action.name,
|
||||
id: action.id,
|
||||
tags: action.tags,
|
||||
resource_type: ResourceTargetVariant::Action,
|
||||
info: ActionListItemInfo {
|
||||
state,
|
||||
last_run_at: action.info.last_run_at,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn busy(id: &String) -> anyhow::Result<bool> {
|
||||
action_states()
|
||||
.action
|
||||
.get(id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.busy()
|
||||
}
|
||||
|
||||
// CREATE
|
||||
|
||||
fn create_operation() -> Operation {
|
||||
Operation::CreateAction
|
||||
}
|
||||
|
||||
fn user_can_create(user: &User) -> bool {
|
||||
user.admin
|
||||
}
|
||||
|
||||
async fn validate_create_config(
|
||||
config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
if config.file_contents.is_none() {
|
||||
config.file_contents =
|
||||
Some(DEFAULT_ACTION_FILE_CONTENTS.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_create(
|
||||
_created: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
refresh_action_state_cache().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
|
||||
fn update_operation() -> Operation {
|
||||
Operation::UpdateAction
|
||||
}
|
||||
|
||||
async fn validate_update_config(
|
||||
_id: &str,
|
||||
_config: &mut Self::PartialConfig,
|
||||
_user: &User,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_update(
|
||||
_updated: &Self,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
refresh_action_state_cache().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
fn delete_operation() -> Operation {
|
||||
Operation::DeleteAction
|
||||
}
|
||||
|
||||
async fn pre_delete(
|
||||
_resource: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_delete(
|
||||
_resource: &Resource<Self::Config, Self::Info>,
|
||||
_update: &mut Update,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_action_state_refresh_loop() {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
refresh_action_state_cache().await;
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn refresh_action_state_cache() {
|
||||
let _ = async {
|
||||
let actions = find_collect(&db_client().actions, None, None)
|
||||
.await
|
||||
.context("Failed to get Actions from db")?;
|
||||
let cache = action_state_cache();
|
||||
for action in actions {
|
||||
let state = get_action_state_from_db(&action.id).await;
|
||||
cache.insert(action.id, state).await;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!("Failed to refresh Action state cache | {e:#}")
|
||||
});
|
||||
}
|
||||
|
||||
async fn get_action_state(id: &String) -> ActionState {
|
||||
if action_states()
|
||||
.action
|
||||
.get(id)
|
||||
.await
|
||||
.map(|s| s.get().map(|s| s.running))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return ActionState::Running;
|
||||
}
|
||||
action_state_cache().get(id).await.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn get_action_state_from_db(id: &str) -> ActionState {
|
||||
async {
|
||||
let state = db_client()
|
||||
.updates
|
||||
.find_one(doc! {
|
||||
"target.type": "Action",
|
||||
"target.id": id,
|
||||
"operation": "RunAction"
|
||||
})
|
||||
.with_options(
|
||||
FindOneOptions::builder()
|
||||
.sort(doc! { "start_ts": -1 })
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
.map(|u| {
|
||||
if u.success {
|
||||
ActionState::Ok
|
||||
} else {
|
||||
ActionState::Failed
|
||||
}
|
||||
})
|
||||
.unwrap_or(ActionState::Ok);
|
||||
anyhow::Ok(state)
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("Failed to get Action state for {id} | {e:#}")
|
||||
})
|
||||
.unwrap_or(ActionState::Unknown)
|
||||
}
|
||||
|
||||
const DEFAULT_ACTION_FILE_CONTENTS: &str =
|
||||
"// Run actions using the pre initialized 'komodo' client.
|
||||
const version: Types.GetVersionResponse = await komodo.read('GetVersion', {});
|
||||
console.log('🦎 Komodo version:', version.version, '🦎\\n');";
|
||||
@@ -45,6 +45,7 @@ use crate::{
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
mod action;
|
||||
mod alerter;
|
||||
mod build;
|
||||
mod builder;
|
||||
@@ -57,6 +58,9 @@ mod server_template;
|
||||
mod stack;
|
||||
mod sync;
|
||||
|
||||
pub use action::{
|
||||
refresh_action_state_cache, spawn_action_state_refresh_loop,
|
||||
};
|
||||
pub use build::{
|
||||
refresh_build_state_cache, spawn_build_state_refresh_loop,
|
||||
};
|
||||
@@ -619,7 +623,7 @@ pub async fn update<T: KomodoResource>(
|
||||
let diff = resource.config.partial_diff(config);
|
||||
|
||||
if diff.is_none() {
|
||||
return Err(anyhow!("update has no changes"));
|
||||
return Ok(resource);
|
||||
}
|
||||
|
||||
let mut diff_log = String::from("diff");
|
||||
@@ -687,6 +691,7 @@ fn resource_target<T: KomodoResource>(id: String) -> ResourceTarget {
|
||||
ResourceTarget::ResourceSync(id)
|
||||
}
|
||||
ResourceTargetVariant::Stack => ResourceTarget::Stack(id),
|
||||
ResourceTargetVariant::Action => ResourceTarget::Action(id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,6 +865,7 @@ where
|
||||
ResourceTarget::Build(id) => ("recents.Build", id),
|
||||
ResourceTarget::Repo(id) => ("recents.Repo", id),
|
||||
ResourceTarget::Procedure(id) => ("recents.Procedure", id),
|
||||
ResourceTarget::Action(id) => ("recents.Action", id),
|
||||
ResourceTarget::Stack(id) => ("recents.Stack", id),
|
||||
ResourceTarget::Builder(id) => ("recents.Builder", id),
|
||||
ResourceTarget::Alerter(id) => ("recents.Alerter", id),
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Context};
|
||||
use komodo_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
action::Action,
|
||||
build::Build,
|
||||
deployment::Deployment,
|
||||
permission::PermissionLevel,
|
||||
@@ -172,6 +173,15 @@ async fn validate_config(
|
||||
}
|
||||
params.procedure = procedure.id;
|
||||
}
|
||||
Execution::RunAction(params) => {
|
||||
let action = super::get_check_permissions::<Action>(
|
||||
¶ms.action,
|
||||
user,
|
||||
PermissionLevel::Execute,
|
||||
)
|
||||
.await?;
|
||||
params.action = action.id;
|
||||
}
|
||||
Execution::RunBuild(params) => {
|
||||
let build = super::get_check_permissions::<Build>(
|
||||
¶ms.build,
|
||||
@@ -598,7 +608,7 @@ pub async fn refresh_procedure_state_cache() {
|
||||
let procedures =
|
||||
find_collect(&db_client().procedures, None, None)
|
||||
.await
|
||||
.context("failed to get procedures from db")?;
|
||||
.context("Failed to get Procedures from db")?;
|
||||
let cache = procedure_state_cache();
|
||||
for procedure in procedures {
|
||||
let state = get_procedure_state_from_db(&procedure.id).await;
|
||||
@@ -608,7 +618,7 @@ pub async fn refresh_procedure_state_cache() {
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!("failed to refresh build state cache | {e:#}")
|
||||
error!("Failed to refresh Procedure state cache | {e:#}")
|
||||
});
|
||||
}
|
||||
|
||||
@@ -655,7 +665,7 @@ async fn get_procedure_state_from_db(id: &str) -> ProcedureState {
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to get procedure state for {id} | {e:#}")
|
||||
warn!("Failed to get Procedure state for {id} | {e:#}")
|
||||
})
|
||||
.unwrap_or(ProcedureState::Unknown)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::{
|
||||
|
||||
use anyhow::Context;
|
||||
use komodo_client::entities::{
|
||||
action::ActionState,
|
||||
build::BuildState,
|
||||
config::core::{CoreConfig, GithubWebhookAppConfig},
|
||||
deployment::DeploymentState,
|
||||
@@ -191,6 +192,14 @@ pub fn procedure_state_cache() -> &'static ProcedureStateCache {
|
||||
PROCEDURE_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub type ActionStateCache = Cache<String, ActionState>;
|
||||
|
||||
pub fn action_state_cache() -> &'static ActionStateCache {
|
||||
static ACTION_STATE_CACHE: OnceLock<ActionStateCache> =
|
||||
OnceLock::new();
|
||||
ACTION_STATE_CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub type ResourceSyncStateCache = Cache<String, ResourceSyncState>;
|
||||
|
||||
pub fn resource_sync_state_cache() -> &'static ResourceSyncStateCache
|
||||
|
||||
@@ -262,6 +262,9 @@ pub fn extend_resources(
|
||||
resources
|
||||
.procedures
|
||||
.extend(filter_by_tag(more.procedures, match_tags));
|
||||
resources
|
||||
.actions
|
||||
.extend(filter_by_tag(more.actions, match_tags));
|
||||
resources
|
||||
.alerters
|
||||
.extend(filter_by_tag(more.alerters, match_tags));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use komodo_client::entities::{
|
||||
alerter::Alerter, build::Build, builder::Builder,
|
||||
action::Action, alerter::Alerter, build::Build, builder::Builder,
|
||||
deployment::Deployment, procedure::Procedure, repo::Repo,
|
||||
server::Server, server_template::ServerTemplate, stack::Stack,
|
||||
sync::ResourceSync, tag::Tag, toml::ResourceToml, ResourceTarget,
|
||||
@@ -147,6 +147,7 @@ pub struct AllResourcesById {
|
||||
pub builds: HashMap<String, Build>,
|
||||
pub repos: HashMap<String, Repo>,
|
||||
pub procedures: HashMap<String, Procedure>,
|
||||
pub actions: HashMap<String, Action>,
|
||||
pub builders: HashMap<String, Builder>,
|
||||
pub alerters: HashMap<String, Alerter>,
|
||||
pub templates: HashMap<String, ServerTemplate>,
|
||||
@@ -181,6 +182,11 @@ impl AllResourcesById {
|
||||
id_to_tags, match_tags,
|
||||
)
|
||||
.await?,
|
||||
actions:
|
||||
crate::resource::get_id_to_resource_map::<Action>(
|
||||
id_to_tags, match_tags,
|
||||
)
|
||||
.await?,
|
||||
builders: crate::resource::get_id_to_resource_map::<Builder>(
|
||||
id_to_tags, match_tags,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ use formatting::{bold, colored, muted, Color};
|
||||
use komodo_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
@@ -233,6 +234,22 @@ impl ResourceSyncTrait for ServerTemplate {
|
||||
|
||||
impl ExecuteResourceSync for ServerTemplate {}
|
||||
|
||||
impl ResourceSyncTrait for Action {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Action(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
_resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecuteResourceSync for Action {}
|
||||
|
||||
impl ResourceSyncTrait for ResourceSync {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::ResourceSync(id)
|
||||
@@ -343,6 +360,13 @@ impl ResourceSyncTrait for Procedure {
|
||||
.map(|p| p.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::RunAction(config) => {
|
||||
config.action = resources
|
||||
.actions
|
||||
.get(&config.action)
|
||||
.map(|p| p.name.clone())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Execution::RunBuild(config) => {
|
||||
config.build = resources
|
||||
.builds
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::Context;
|
||||
use komodo_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
action::Action,
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig, PartialBuilderConfig},
|
||||
@@ -164,6 +165,7 @@ pub fn convert_resource<R: KomodoResource>(
|
||||
impl ToToml for Alerter {}
|
||||
impl ToToml for Server {}
|
||||
impl ToToml for ResourceSync {}
|
||||
impl ToToml for Action {}
|
||||
|
||||
impl ToToml for Stack {
|
||||
fn replace_ids(
|
||||
@@ -412,6 +414,13 @@ impl ToToml for Procedure {
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
),
|
||||
Execution::RunAction(exec) => exec.action.clone_from(
|
||||
all
|
||||
.actions
|
||||
.get(&exec.action)
|
||||
.map(|r| &r.name)
|
||||
.unwrap_or(&String::new()),
|
||||
),
|
||||
Execution::RunBuild(exec) => exec.build.clone_from(
|
||||
all
|
||||
.builds
|
||||
|
||||
@@ -275,6 +275,13 @@ pub async fn get_updates_for_execution(
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
*id = all_resources
|
||||
.actions
|
||||
.get(id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = all_resources
|
||||
.templates
|
||||
@@ -716,6 +723,17 @@ async fn expand_user_group_permissions(
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::Action => {
|
||||
let permissions = all_resources
|
||||
.actions
|
||||
.values()
|
||||
.filter(|resource| regex.is_match(&resource.name))
|
||||
.map(|resource| PermissionToml {
|
||||
target: ResourceTarget::Action(resource.name.clone()),
|
||||
level: permission.level,
|
||||
});
|
||||
expanded.extend(permissions);
|
||||
}
|
||||
ResourceTargetVariant::ServerTemplate => {
|
||||
let permissions = all_resources
|
||||
.templates
|
||||
@@ -875,6 +893,13 @@ pub async fn convert_user_groups(
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::Action(id) => {
|
||||
*id = all
|
||||
.actions
|
||||
.get(id)
|
||||
.map(|r| r.name.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
ResourceTarget::ServerTemplate(id) => {
|
||||
*id = all
|
||||
.templates
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
extract::{
|
||||
ws::{Message, WebSocket},
|
||||
@@ -15,7 +15,6 @@ use komodo_client::{
|
||||
},
|
||||
ws::WsLoginMessage,
|
||||
};
|
||||
use mungos::by_id::find_one_by_id;
|
||||
use serde_json::json;
|
||||
use serror::serialize_error;
|
||||
use tokio::select;
|
||||
@@ -23,11 +22,10 @@ use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
auth::{auth_api_key_check_enabled, auth_jwt_check_enabled},
|
||||
db::DbClient,
|
||||
helpers::{
|
||||
channel::update_channel, query::get_user_permission_on_target,
|
||||
channel::update_channel,
|
||||
query::{get_user, get_user_permission_on_target},
|
||||
},
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
pub fn router() -> Router {
|
||||
@@ -51,7 +49,6 @@ async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
|
||||
let cancel_clone = cancel.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let db_client = db_client();
|
||||
loop {
|
||||
// poll for updates off the receiver / await cancel.
|
||||
let update = select! {
|
||||
@@ -61,7 +58,7 @@ async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
|
||||
|
||||
// before sending every update, verify user is still valid.
|
||||
// kill the connection is user if found to be invalid.
|
||||
let user = check_user_valid(db_client, &user.id).await;
|
||||
let user = check_user_valid(&user.id).await;
|
||||
let user = match user {
|
||||
Err(e) => {
|
||||
let _ = ws_sender
|
||||
@@ -183,15 +180,9 @@ enum LoginMessage {
|
||||
Err(String),
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db_client))]
|
||||
async fn check_user_valid(
|
||||
db_client: &DbClient,
|
||||
user_id: &str,
|
||||
) -> anyhow::Result<User> {
|
||||
let user = find_one_by_id(&db_client.users, user_id)
|
||||
.await
|
||||
.context("failed to query mongo for users")?
|
||||
.context("user not found")?;
|
||||
#[instrument(level = "debug")]
|
||||
async fn check_user_valid(user_id: &str) -> anyhow::Result<User> {
|
||||
let user = get_user(user_id).await?;
|
||||
if !user.enabled {
|
||||
return Err(anyhow!("user not enabled"));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
## and may negatively affect runtime performance.
|
||||
|
||||
# Build Periphery
|
||||
FROM rust:1.81.0-alpine AS builder
|
||||
FROM rust:1.82.0-alpine AS builder
|
||||
WORKDIR /builder
|
||||
COPY . .
|
||||
RUN apk update && apk --no-cache add musl-dev openssl-dev openssl-libs-static
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build Periphery
|
||||
FROM rust:1.81.0-bullseye AS builder
|
||||
FROM rust:1.82.0-bullseye AS builder
|
||||
WORKDIR /builder
|
||||
COPY . .
|
||||
RUN cargo build -p komodo_periphery --release
|
||||
|
||||
@@ -126,6 +126,7 @@ impl Resolve<build::Build> for State {
|
||||
"docker build",
|
||||
build_dir.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
logs.push(build_log);
|
||||
@@ -146,6 +147,7 @@ impl Resolve<build::Build> for State {
|
||||
"docker build",
|
||||
build_dir.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
build_log.command =
|
||||
@@ -244,7 +246,10 @@ impl Resolve<PruneBuilders> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker builder prune -a -f");
|
||||
Ok(run_komodo_command("prune builders", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune builders", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +263,6 @@ impl Resolve<PruneBuildx> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker buildx prune -a -f");
|
||||
Ok(run_komodo_command("prune buildx", None, command).await)
|
||||
Ok(run_komodo_command("prune buildx", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ impl Resolve<ListComposeProjects, ()> for State {
|
||||
"list projects",
|
||||
None,
|
||||
format!("{docker_compose} ls --all --format json"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -104,7 +105,9 @@ impl Resolve<GetComposeServiceLog> for State {
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project} logs {service} --tail {tail}{timestamps}"
|
||||
);
|
||||
Ok(run_komodo_command("get stack log", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("get stack log", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +134,10 @@ impl Resolve<GetComposeServiceLogSearch> for State {
|
||||
let timestamps =
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let command = format!("{docker_compose} -p {project} logs {service} --tail 5000{timestamps} 2>&1 | {grep}");
|
||||
Ok(run_komodo_command("get stack log grep", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("get stack log grep", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +384,7 @@ impl Resolve<ComposeExecution> for State {
|
||||
"compose command",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} {command}"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
Ok(log)
|
||||
|
||||
@@ -53,7 +53,10 @@ impl Resolve<GetContainerLog> for State {
|
||||
timestamps.then_some(" --timestamps").unwrap_or_default();
|
||||
let command =
|
||||
format!("docker logs {name} --tail {tail}{timestamps}");
|
||||
Ok(run_komodo_command("get container log", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("get container log", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +86,13 @@ impl Resolve<GetContainerLogSearch> for State {
|
||||
"docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}"
|
||||
);
|
||||
Ok(
|
||||
run_komodo_command("get container log grep", None, command)
|
||||
.await,
|
||||
run_komodo_command(
|
||||
"get container log grep",
|
||||
None,
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -142,6 +150,7 @@ impl Resolve<StartContainer> for State {
|
||||
"docker start",
|
||||
None,
|
||||
format!("docker start {name}"),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -162,6 +171,7 @@ impl Resolve<RestartContainer> for State {
|
||||
"docker restart",
|
||||
None,
|
||||
format!("docker restart {name}"),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -182,6 +192,7 @@ impl Resolve<PauseContainer> for State {
|
||||
"docker pause",
|
||||
None,
|
||||
format!("docker pause {name}"),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -200,6 +211,7 @@ impl Resolve<UnpauseContainer> for State {
|
||||
"docker unpause",
|
||||
None,
|
||||
format!("docker unpause {name}"),
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -216,11 +228,12 @@ impl Resolve<StopContainer> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = stop_container_command(&name, signal, time);
|
||||
let log = run_komodo_command("docker stop", None, command).await;
|
||||
let log =
|
||||
run_komodo_command("docker stop", None, command, false).await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let command = stop_container_command(&name, None, time);
|
||||
let mut log =
|
||||
run_komodo_command("docker stop", None, command).await;
|
||||
run_komodo_command("docker stop", None, command, false).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
@@ -248,15 +261,19 @@ impl Resolve<RemoveContainer> for State {
|
||||
let stop_command = stop_container_command(&name, signal, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let log =
|
||||
run_komodo_command("docker stop and remove", None, command)
|
||||
.await;
|
||||
let log = run_komodo_command(
|
||||
"docker stop and remove",
|
||||
None,
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
if log.stderr.contains("unknown flag: --signal") {
|
||||
let stop_command = stop_container_command(&name, None, time);
|
||||
let command =
|
||||
format!("{stop_command} && docker container rm {name}");
|
||||
let mut log =
|
||||
run_komodo_command("docker stop", None, command).await;
|
||||
run_komodo_command("docker stop", None, command, false).await;
|
||||
log.stderr = format!(
|
||||
"old docker version: unable to use --signal flag{}",
|
||||
if !log.stderr.is_empty() {
|
||||
@@ -286,7 +303,9 @@ impl Resolve<RenameContainer> for State {
|
||||
) -> anyhow::Result<Log> {
|
||||
let new = to_komodo_name(&new_name);
|
||||
let command = format!("docker rename {curr_name} {new}");
|
||||
Ok(run_komodo_command("docker rename", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("docker rename", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +319,10 @@ impl Resolve<PruneContainers> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker container prune -f");
|
||||
Ok(run_komodo_command("prune containers", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune containers", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +346,8 @@ impl Resolve<StartAllContainers> for State {
|
||||
}
|
||||
let command = format!("docker start {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -352,7 +375,8 @@ impl Resolve<RestartAllContainers> for State {
|
||||
}
|
||||
let command = format!("docker restart {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -380,7 +404,8 @@ impl Resolve<PauseAllContainers> for State {
|
||||
}
|
||||
let command = format!("docker pause {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -408,7 +433,8 @@ impl Resolve<UnpauseAllContainers> for State {
|
||||
}
|
||||
let command = format!("docker unpause {name}");
|
||||
Some(async move {
|
||||
run_komodo_command(&command.clone(), None, command).await
|
||||
run_komodo_command(&command.clone(), None, command, false)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -439,6 +465,7 @@ impl Resolve<StopAllContainers> for State {
|
||||
&format!("docker stop {name}"),
|
||||
None,
|
||||
stop_container_command(name, None, None),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Resolve<Deploy> for State {
|
||||
debug!("docker run command: {command}");
|
||||
|
||||
if deployment.config.skip_secret_interp {
|
||||
Ok(run_komodo_command("docker run", None, command).await)
|
||||
Ok(run_komodo_command("docker run", None, command, false).await)
|
||||
} else {
|
||||
let command = svi::interpolate_variables(
|
||||
&command,
|
||||
@@ -108,7 +108,7 @@ impl Resolve<Deploy> for State {
|
||||
|
||||
replacers.extend(core_replacers);
|
||||
let mut log =
|
||||
run_komodo_command("docker run", None, command).await;
|
||||
run_komodo_command("docker run", None, command, false).await;
|
||||
log.command = svi::replace_in_string(&log.command, &replacers);
|
||||
log.stdout = svi::replace_in_string(&log.stdout, &replacers);
|
||||
log.stderr = svi::replace_in_string(&log.stderr, &replacers);
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Resolve<DeleteImage> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = format!("docker image rm {name}");
|
||||
Ok(run_komodo_command("delete image", None, command).await)
|
||||
Ok(run_komodo_command("delete image", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,6 @@ impl Resolve<PruneImages> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker image prune -a -f");
|
||||
Ok(run_komodo_command("prune images", None, command).await)
|
||||
Ok(run_komodo_command("prune images", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ impl Resolve<RunCommand> for State {
|
||||
} else {
|
||||
format!("cd {path} && {command}")
|
||||
};
|
||||
run_komodo_command("run command", None, command).await
|
||||
run_komodo_command("run command", None, command, false).await
|
||||
})
|
||||
.await
|
||||
.context("failure in spawned task")
|
||||
@@ -271,6 +271,6 @@ impl Resolve<PruneSystem> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker system prune -a -f --volumes");
|
||||
Ok(run_komodo_command("prune system", None, command).await)
|
||||
Ok(run_komodo_command("prune system", None, command, false).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ impl Resolve<CreateNetwork> for State {
|
||||
None => String::new(),
|
||||
};
|
||||
let command = format!("docker network create{driver} {name}");
|
||||
Ok(run_komodo_command("create network", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("create network", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +51,10 @@ impl Resolve<DeleteNetwork> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = format!("docker network rm {name}");
|
||||
Ok(run_komodo_command("delete network", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("delete network", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +68,9 @@ impl Resolve<PruneNetworks> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker network prune -f");
|
||||
Ok(run_komodo_command("prune networks", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune networks", None, command, false)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ impl Resolve<DeleteVolume> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = format!("docker volume rm {name}");
|
||||
Ok(run_komodo_command("delete volume", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("delete volume", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +44,8 @@ impl Resolve<PruneVolumes> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
let command = String::from("docker volume prune -a -f");
|
||||
Ok(run_komodo_command("prune volumes", None, command).await)
|
||||
Ok(
|
||||
run_komodo_command("prune volumes", None, command, false).await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,19 +141,27 @@ pub async fn compose_up(
|
||||
.map(|path| format!(" --env-file {path}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
let additional_env_files = stack
|
||||
.config
|
||||
.additional_env_files
|
||||
.iter()
|
||||
.map(|file| format!(" --env-file {file}"))
|
||||
.collect::<String>();
|
||||
|
||||
// Build images before destroying to minimize downtime.
|
||||
// If this fails, do not continue.
|
||||
if stack.config.run_build {
|
||||
let build_extra_args =
|
||||
parse_extra_args(&stack.config.build_extra_args);
|
||||
let command = format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file} build{build_extra_args}{service_arg}",
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} build{build_extra_args}{service_arg}",
|
||||
);
|
||||
if stack.config.skip_secret_interp {
|
||||
let log = run_komodo_command(
|
||||
"compose build",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
res.logs.push(log);
|
||||
@@ -170,6 +178,7 @@ pub async fn compose_up(
|
||||
"compose build",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -197,6 +206,7 @@ pub async fn compose_up(
|
||||
format!(
|
||||
"{docker_compose} -p {project_name} -f {file_args}{env_file} pull{service_arg}",
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -223,6 +233,7 @@ pub async fn compose_up(
|
||||
"pre deploy",
|
||||
pre_deploy_path.as_ref(),
|
||||
&full_command,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -245,6 +256,7 @@ pub async fn compose_up(
|
||||
"pre deploy",
|
||||
pre_deploy_path.as_ref(),
|
||||
&stack.config.pre_deploy.command,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
tracing::debug!(
|
||||
@@ -279,8 +291,13 @@ pub async fn compose_up(
|
||||
);
|
||||
|
||||
let log = if stack.config.skip_secret_interp {
|
||||
run_komodo_command("compose up", run_directory.as_ref(), command)
|
||||
.await
|
||||
run_komodo_command(
|
||||
"compose up",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
@@ -294,6 +311,7 @@ pub async fn compose_up(
|
||||
"compose up",
|
||||
run_directory.as_ref(),
|
||||
command,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -545,6 +563,7 @@ async fn compose_down(
|
||||
"compose down",
|
||||
None,
|
||||
format!("{docker_compose} -p {project} down{service_arg}"),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
let success = log.success;
|
||||
|
||||
@@ -937,7 +937,7 @@ pub async fn docker_login(
|
||||
#[instrument]
|
||||
pub async fn pull_image(image: &str) -> Log {
|
||||
let command = format!("docker pull {image}");
|
||||
run_komodo_command("docker pull", None, command).await
|
||||
run_komodo_command("docker pull", None, command, false).await
|
||||
}
|
||||
|
||||
pub fn stop_container_command(
|
||||
|
||||
28
client/core/rs/src/api/execute/action.rs
Normal file
28
client/core/rs/src/api/execute/action.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use clap::Parser;
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::update::Update;
|
||||
|
||||
use super::KomodoExecuteRequest;
|
||||
|
||||
/// Runs the target Action. Response: [Update]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Request,
|
||||
EmptyTraits,
|
||||
Parser,
|
||||
)]
|
||||
#[empty_traits(KomodoExecuteRequest)]
|
||||
#[response(Update)]
|
||||
pub struct RunAction {
|
||||
/// Id or name
|
||||
pub action: String,
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
mod action;
|
||||
mod build;
|
||||
mod deployment;
|
||||
mod procedure;
|
||||
@@ -14,6 +15,7 @@ mod server_template;
|
||||
mod stack;
|
||||
mod sync;
|
||||
|
||||
pub use action::*;
|
||||
pub use build::*;
|
||||
pub use deployment::*;
|
||||
pub use procedure::*;
|
||||
@@ -55,6 +57,9 @@ pub enum Execution {
|
||||
/// The "null" execution. Does nothing.
|
||||
None(NoData),
|
||||
|
||||
// ACTION
|
||||
RunAction(RunAction),
|
||||
|
||||
// PROCEDURE
|
||||
RunProcedure(RunProcedure),
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::entities::update::Update;
|
||||
|
||||
use super::KomodoExecuteRequest;
|
||||
|
||||
/// Runs the target procedure. Response: [Update]
|
||||
/// Runs the target Procedure. Response: [Update]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug,
|
||||
|
||||
@@ -38,10 +38,10 @@
|
||||
//!
|
||||
//! ```text
|
||||
//! curl --header "Content-Type: application/json" \
|
||||
//! --header "X-Api-Key: your_api_key" \
|
||||
//! --header "X-Api-Secret: your_api_secret" \
|
||||
//! --data '{ "type": "UpdateBuild", "params": { "id": "67076689ed600cfdd52ac637", "config": { "version": "1.15.9" } } }' \
|
||||
//! https://komodo.example.com/write
|
||||
//! --header "X-Api-Key: your_api_key" \
|
||||
//! --header "X-Api-Secret: your_api_secret" \
|
||||
//! --data '{ "type": "UpdateBuild", "params": { "id": "67076689ed600cfdd52ac637", "config": { "version": "1.15.9" } } }' \
|
||||
//! https://komodo.example.com/write
|
||||
//! ```
|
||||
//!
|
||||
//! ## Modules
|
||||
|
||||
110
client/core/rs/src/api/read/action.rs
Normal file
110
client/core/rs/src/api/read/action.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::action::{
|
||||
Action, ActionActionState, ActionListItem, ActionQuery,
|
||||
};
|
||||
|
||||
use super::KomodoReadRequest;
|
||||
|
||||
//
|
||||
|
||||
/// Get a specific action. Response: [Action].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(GetActionResponse)]
|
||||
pub struct GetAction {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type GetActionResponse = Action;
|
||||
|
||||
//
|
||||
|
||||
/// List actions matching optional query. Response: [ListActionsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(ListActionsResponse)]
|
||||
pub struct ListActions {
|
||||
/// optional structured query to filter actions.
|
||||
#[serde(default)]
|
||||
pub query: ActionQuery,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListActionsResponse = Vec<ActionListItem>;
|
||||
|
||||
//
|
||||
|
||||
/// List actions matching optional query. Response: [ListFullActionsResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(ListFullActionsResponse)]
|
||||
pub struct ListFullActions {
|
||||
/// optional structured query to filter actions.
|
||||
#[serde(default)]
|
||||
pub query: ActionQuery,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListFullActionsResponse = Vec<Action>;
|
||||
|
||||
//
|
||||
|
||||
/// Get current action state for the action. Response: [ActionActionState].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(GetActionActionStateResponse)]
|
||||
pub struct GetActionActionState {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type GetActionActionStateResponse = ActionActionState;
|
||||
|
||||
//
|
||||
|
||||
/// Gets a summary of data relating to all actions.
|
||||
/// Response: [GetActionsSummaryResponse].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoReadRequest)]
|
||||
#[response(GetActionsSummaryResponse)]
|
||||
pub struct GetActionsSummary {}
|
||||
|
||||
/// Response for [GetActionsSummary].
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct GetActionsSummaryResponse {
|
||||
/// The total number of actions.
|
||||
pub total: u32,
|
||||
/// The number of actions with Ok state.
|
||||
pub ok: u32,
|
||||
/// The number of actions currently running.
|
||||
pub running: u32,
|
||||
/// The number of actions with failed state.
|
||||
pub failed: u32,
|
||||
/// The number of actions with unknown state.
|
||||
pub unknown: u32,
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use resolver_api::{derive::Request, HasResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
mod action;
|
||||
mod alert;
|
||||
mod alerter;
|
||||
mod build;
|
||||
@@ -24,6 +25,7 @@ mod user;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
|
||||
pub use action::*;
|
||||
pub use alert::*;
|
||||
pub use alerter::*;
|
||||
pub use build::*;
|
||||
|
||||
118
client/core/rs/src/api/write/action.rs
Normal file
118
client/core/rs/src/api/write/action.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{
|
||||
action::{Action, _PartialActionConfig},
|
||||
NoData,
|
||||
};
|
||||
|
||||
use super::KomodoWriteRequest;
|
||||
|
||||
//
|
||||
|
||||
/// Create a action. Response: [Action].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Action)]
|
||||
pub struct CreateAction {
|
||||
/// The name given to newly created action.
|
||||
pub name: String,
|
||||
/// Optional partial config to initialize the action with.
|
||||
pub config: _PartialActionConfig,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Creates a new action with given `name` and the configuration
|
||||
/// of the action at the given `id`. Response: [Action].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Action)]
|
||||
pub struct CopyAction {
|
||||
/// The name of the new action.
|
||||
pub name: String,
|
||||
/// The id of the action to copy.
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Deletes the action at the given id, and returns the deleted action.
|
||||
/// Response: [Action]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Action)]
|
||||
pub struct DeleteAction {
|
||||
/// The id or name of the action to delete.
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Update the action at the given id, and return the updated action.
|
||||
/// Response: [Action].
|
||||
///
|
||||
/// Note. This method updates only the fields which are set in the [_PartialActionConfig],
|
||||
/// effectively merging diffs into the final document.
|
||||
/// This is helpful when multiple users are using
|
||||
/// the same resources concurrently by ensuring no unintentional
|
||||
/// field changes occur from out of date local state.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(Action)]
|
||||
pub struct UpdateAction {
|
||||
/// The id of the action to update.
|
||||
pub id: String,
|
||||
/// The partial config update to apply.
|
||||
pub config: _PartialActionConfig,
|
||||
}
|
||||
|
||||
/// Create a webhook on the github action attached to the Action resource.
|
||||
/// passed in request. Response: [CreateActionWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(CreateActionWebhookResponse)]
|
||||
pub struct CreateActionWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type CreateActionWebhookResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Delete the webhook on the github action attached to the Action resource.
|
||||
/// passed in request. Response: [DeleteActionWebhookResponse]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(DeleteActionWebhookResponse)]
|
||||
pub struct DeleteActionWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type DeleteActionWebhookResponse = NoData;
|
||||
@@ -1,3 +1,4 @@
|
||||
mod action;
|
||||
mod alerter;
|
||||
mod api_key;
|
||||
mod build;
|
||||
@@ -17,6 +18,7 @@ mod user;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
|
||||
pub use action::*;
|
||||
pub use alerter::*;
|
||||
pub use api_key::*;
|
||||
pub use build::*;
|
||||
|
||||
@@ -121,7 +121,7 @@ pub struct CreateRepoWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub repo: String,
|
||||
/// "Clone" or "Pull"
|
||||
/// "Clone" or "Pull" or "Build"
|
||||
pub action: RepoWebhookAction,
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ pub struct DeleteRepoWebhook {
|
||||
/// Id or name
|
||||
#[serde(alias = "id", alias = "name")]
|
||||
pub repo: String,
|
||||
/// "Clone" or "Pull"
|
||||
/// "Clone" or "Pull" or "Build"
|
||||
pub action: RepoWebhookAction,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::entities::{
|
||||
build::BuildActionState, deployment::DeploymentActionState,
|
||||
procedure::ProcedureActionState, repo::RepoActionState,
|
||||
server::ServerActionState, stack::StackActionState,
|
||||
sync::ResourceSyncActionState,
|
||||
action::ActionActionState, build::BuildActionState,
|
||||
deployment::DeploymentActionState, procedure::ProcedureActionState,
|
||||
repo::RepoActionState, server::ServerActionState,
|
||||
stack::StackActionState, sync::ResourceSyncActionState,
|
||||
};
|
||||
|
||||
pub trait Busy {
|
||||
@@ -66,6 +66,12 @@ impl Busy for ProcedureActionState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Busy for ActionActionState {
|
||||
fn busy(&self) -> bool {
|
||||
self.running
|
||||
}
|
||||
}
|
||||
|
||||
impl Busy for ResourceSyncActionState {
|
||||
fn busy(&self) -> bool {
|
||||
self.syncing
|
||||
|
||||
108
client/core/rs/src/deserializers/conversion.rs
Normal file
108
client/core/rs/src/deserializers/conversion.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
|
||||
use crate::entities::deployment::Conversion;
|
||||
|
||||
pub fn conversions_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(ConversionVisitor)
|
||||
}
|
||||
|
||||
pub fn option_conversions_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionConversionVisitor)
|
||||
}
|
||||
|
||||
struct ConversionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ConversionVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<Conversion>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let res = Vec::<Conversion>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?;
|
||||
let res = res
|
||||
.iter()
|
||||
.map(|Conversion { local, container }| {
|
||||
format!(" {local}: {container}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if res.is_empty() { "" } else { "\n" };
|
||||
Ok(res + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionConversionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionConversionVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<Conversion>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
ConversionVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
ConversionVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
108
client/core/rs/src/deserializers/environment.rs
Normal file
108
client/core/rs/src/deserializers/environment.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
|
||||
use crate::entities::EnvironmentVar;
|
||||
|
||||
pub fn env_vars_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(EnvironmentVarVisitor)
|
||||
}
|
||||
|
||||
pub fn option_env_vars_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionEnvVarVisitor)
|
||||
}
|
||||
|
||||
struct EnvironmentVarVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for EnvironmentVarVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let vars = Vec::<EnvironmentVar>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?;
|
||||
let vars = vars
|
||||
.iter()
|
||||
.map(|EnvironmentVar { variable, value }| {
|
||||
format!(" {variable} = {value}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if vars.is_empty() { "" } else { "\n" };
|
||||
Ok(vars + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionEnvVarVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionEnvVarVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
EnvironmentVarVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
EnvironmentVarVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
80
client/core/rs/src/deserializers/file_contents.rs
Normal file
80
client/core/rs/src/deserializers/file_contents.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use serde::{de::Visitor, Deserializer};
|
||||
|
||||
/// Using this ensures the file contents end with trailing '\n'
|
||||
pub fn file_contents_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(FileContentsVisitor)
|
||||
}
|
||||
|
||||
/// Using this ensures the file contents end with trailing '\n'
|
||||
pub fn option_file_contents_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionFileContentsVisitor)
|
||||
}
|
||||
|
||||
struct FileContentsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for FileContentsVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.trim_end().to_string();
|
||||
if out.is_empty() {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionFileContentsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionFileContentsVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
FileContentsVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
108
client/core/rs/src/deserializers/labels.rs
Normal file
108
client/core/rs/src/deserializers/labels.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
|
||||
use crate::entities::EnvironmentVar;
|
||||
|
||||
pub fn labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(LabelVisitor)
|
||||
}
|
||||
|
||||
pub fn option_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionLabelVisitor)
|
||||
}
|
||||
|
||||
struct LabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for LabelVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let vars = Vec::<EnvironmentVar>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?;
|
||||
let vars = vars
|
||||
.iter()
|
||||
.map(|EnvironmentVar { variable, value }| {
|
||||
format!(" {variable}: {value}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if vars.is_empty() { "" } else { "\n" };
|
||||
Ok(vars + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionLabelVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
LabelVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
LabelVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
15
client/core/rs/src/deserializers/mod.rs
Normal file
15
client/core/rs/src/deserializers/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
//! Deserializers for custom behavior and backward compatibility.
|
||||
|
||||
mod conversion;
|
||||
mod environment;
|
||||
mod file_contents;
|
||||
mod labels;
|
||||
mod string_list;
|
||||
mod term_signal_labels;
|
||||
|
||||
pub use conversion::*;
|
||||
pub use environment::*;
|
||||
pub use file_contents::*;
|
||||
pub use labels::*;
|
||||
pub use string_list::*;
|
||||
pub use term_signal_labels::*;
|
||||
92
client/core/rs/src/deserializers/string_list.rs
Normal file
92
client/core/rs/src/deserializers/string_list.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, SeqAccess, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
|
||||
use crate::parsers::parse_string_list;
|
||||
|
||||
pub fn string_list_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(StringListVisitor)
|
||||
}
|
||||
|
||||
pub fn option_string_list_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<String>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionStringListVisitor)
|
||||
}
|
||||
|
||||
struct StringListVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for StringListVisitor {
|
||||
type Value = Vec<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<String>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(parse_string_list(v))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
Vec::<String>::deserialize(SeqAccessDeserializer::new(seq))
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionStringListVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionStringListVisitor {
|
||||
type Value = Option<Vec<String>>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<String>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
StringListVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
StringListVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
107
client/core/rs/src/deserializers/term_signal_labels.rs
Normal file
107
client/core/rs/src/deserializers/term_signal_labels.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer,
|
||||
};
|
||||
|
||||
use crate::entities::deployment::TerminationSignalLabel;
|
||||
|
||||
pub fn term_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(TermSignalLabelVisitor)
|
||||
}
|
||||
|
||||
pub fn option_term_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionTermSignalLabelVisitor)
|
||||
}
|
||||
|
||||
struct TermSignalLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for TermSignalLabelVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<TerminationSignalLabel>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let res = Vec::<TerminationSignalLabel>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?
|
||||
.into_iter()
|
||||
.map(|TerminationSignalLabel { signal, label }| {
|
||||
format!(" {signal}: {label}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if res.is_empty() { "" } else { "\n" };
|
||||
Ok(res + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionTermSignalLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionTermSignalLabelVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<TerminationSignalLabel>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
TermSignalLabelVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
TermSignalLabelVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
107
client/core/rs/src/entities/action.rs
Normal file
107
client/core/rs/src/entities/action.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use bson::{doc, Document};
|
||||
use derive_builder::Builder;
|
||||
use derive_default_builder::DefaultBuilder;
|
||||
use partial_derive2::Partial;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{
|
||||
deserializers::{
|
||||
file_contents_deserializer, option_file_contents_deserializer,
|
||||
},
|
||||
entities::I64,
|
||||
};
|
||||
|
||||
use super::resource::{Resource, ResourceListItem, ResourceQuery};
|
||||
|
||||
#[typeshare]
|
||||
pub type ActionListItem = ResourceListItem<ActionListItemInfo>;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct ActionListItemInfo {
|
||||
/// Action last run timestamp in ms.
|
||||
pub last_run_at: I64,
|
||||
/// Whether last action run successful
|
||||
pub state: ActionState,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, Serialize, Deserialize, Display,
|
||||
)]
|
||||
pub enum ActionState {
|
||||
/// Unknown case
|
||||
#[default]
|
||||
Unknown,
|
||||
/// Last clone / pull successful (or never cloned)
|
||||
Ok,
|
||||
/// Last clone / pull failed
|
||||
Failed,
|
||||
/// Currently running
|
||||
Running,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type Action = Resource<ActionConfig, ActionInfo>;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct ActionInfo {
|
||||
/// When action was last run
|
||||
#[serde(default)]
|
||||
pub last_run_at: I64,
|
||||
}
|
||||
|
||||
#[typeshare(serialized_as = "Partial<ActionConfig>")]
|
||||
pub type _PartialActionConfig = PartialActionConfig;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)]
|
||||
#[partial_derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[partial(skip_serializing_none, from, diff)]
|
||||
pub struct ActionConfig {
|
||||
/// Typescript file contents using pre-initialized `komodo` client.
|
||||
#[serde(default, deserialize_with = "file_contents_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_file_contents_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub file_contents: String,
|
||||
}
|
||||
|
||||
impl ActionConfig {
|
||||
pub fn builder() -> ActionConfigBuilder {
|
||||
ActionConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ActionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file_contents: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
|
||||
pub struct ActionActionState {
|
||||
/// Whether the action is currently running.
|
||||
pub running: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ActionQuery = ResourceQuery<ActionQuerySpecifics>;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder,
|
||||
)]
|
||||
pub struct ActionQuerySpecifics {}
|
||||
|
||||
impl super::resource::AddFilters for ActionQuerySpecifics {
|
||||
fn add_filters(&self, _filters: &mut Document) {}
|
||||
}
|
||||
@@ -9,7 +9,14 @@ use serde::{
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::I64;
|
||||
use crate::{
|
||||
deserializers::{
|
||||
env_vars_deserializer, labels_deserializer,
|
||||
option_env_vars_deserializer, option_labels_deserializer,
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
},
|
||||
entities::I64,
|
||||
};
|
||||
|
||||
use super::{
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
@@ -126,7 +133,11 @@ pub struct BuildConfig {
|
||||
pub image_tag: String,
|
||||
|
||||
/// Configure quick links that are displayed in the resource header
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub links: Vec<String>,
|
||||
|
||||
@@ -219,20 +230,21 @@ pub struct BuildConfig {
|
||||
pub use_buildx: bool,
|
||||
|
||||
/// Any extra docker cli arguments to be included in the build command
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// Docker build arguments.
|
||||
///
|
||||
/// These values are visible in the final image by running `docker inspect`.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
deserialize_with = "option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub build_args: String,
|
||||
@@ -247,22 +259,19 @@ pub struct BuildConfig {
|
||||
/// RUN --mount=type=secret,id=SECRET_KEY \
|
||||
/// SECRET_KEY=$(cat /run/secrets/SECRET_KEY) ...
|
||||
/// ```
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
deserialize_with = "option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub secret_args: String,
|
||||
|
||||
/// Docker labels
|
||||
#[serde(default, deserialize_with = "super::labels_deserializer")]
|
||||
#[serde(default, deserialize_with = "labels_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_labels_deserializer"
|
||||
deserialize_with = "option_labels_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub labels: String,
|
||||
|
||||
@@ -5,6 +5,10 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::deserializers::{
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
};
|
||||
|
||||
use super::{
|
||||
config::{DockerRegistry, GitProvider},
|
||||
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
|
||||
@@ -330,7 +334,11 @@ pub struct AwsBuilderConfig {
|
||||
pub use_public_ip: bool,
|
||||
/// The security group ids to attach to the instance.
|
||||
/// This should include a security group to allow core inbound access to the periphery port.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub security_group_ids: Vec<String>,
|
||||
/// The user data to deploy the instance with.
|
||||
@@ -347,7 +355,11 @@ pub struct AwsBuilderConfig {
|
||||
#[builder(default)]
|
||||
pub docker_registries: Vec<DockerRegistry>,
|
||||
/// Which secrets are available on the AMI.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub secrets: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ pub struct Env {
|
||||
pub komodo_sync_directory: Option<PathBuf>,
|
||||
/// Override `repo_directory`
|
||||
pub komodo_repo_directory: Option<PathBuf>,
|
||||
/// Override `action_directory`
|
||||
pub komodo_action_directory: Option<PathBuf>,
|
||||
/// Override `resource_poll_interval`
|
||||
pub komodo_resource_poll_interval: Option<Timelength>,
|
||||
/// Override `monitoring_interval`
|
||||
@@ -491,6 +493,11 @@ pub struct CoreConfig {
|
||||
/// Default: `/repo-cache`
|
||||
#[serde(default = "default_repo_directory")]
|
||||
pub repo_directory: PathBuf,
|
||||
|
||||
/// Specify the directory used to temporarily write typescript files used with actions.
|
||||
/// Default: `/action-cache`
|
||||
#[serde(default = "default_action_directory")]
|
||||
pub action_directory: PathBuf,
|
||||
}
|
||||
|
||||
fn default_title() -> String {
|
||||
@@ -509,14 +516,19 @@ fn default_jwt_ttl() -> Timelength {
|
||||
Timelength::OneDay
|
||||
}
|
||||
|
||||
fn default_sync_directory() -> PathBuf {
|
||||
// unwrap ok: `/syncs` will always be valid path
|
||||
PathBuf::from_str("/syncs").unwrap()
|
||||
}
|
||||
|
||||
fn default_repo_directory() -> PathBuf {
|
||||
// unwrap ok: `/repo-cache` will always be valid path
|
||||
PathBuf::from_str("/repo-cache").unwrap()
|
||||
}
|
||||
|
||||
fn default_sync_directory() -> PathBuf {
|
||||
// unwrap ok: `/syncs` will always be valid path
|
||||
PathBuf::from_str("/syncs").unwrap()
|
||||
fn default_action_directory() -> PathBuf {
|
||||
// unwrap ok: `/action-cache` will always be valid path
|
||||
PathBuf::from_str("/action-cache").unwrap()
|
||||
}
|
||||
|
||||
fn default_prune_days() -> u64 {
|
||||
@@ -552,6 +564,7 @@ impl CoreConfig {
|
||||
jwt_secret: empty_or_redacted(&config.jwt_secret),
|
||||
jwt_ttl: config.jwt_ttl,
|
||||
repo_directory: config.repo_directory,
|
||||
action_directory: config.action_directory,
|
||||
sync_directory: config.sync_directory,
|
||||
resource_poll_interval: config.resource_poll_interval,
|
||||
monitoring_interval: config.monitoring_interval,
|
||||
|
||||
@@ -4,14 +4,20 @@ use derive_builder::Builder;
|
||||
use derive_default_builder::DefaultBuilder;
|
||||
use derive_variants::EnumVariants;
|
||||
use partial_derive2::Partial;
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::parser::parse_key_value_list;
|
||||
use crate::{
|
||||
deserializers::{
|
||||
conversions_deserializer, env_vars_deserializer,
|
||||
labels_deserializer, option_conversions_deserializer,
|
||||
option_env_vars_deserializer, option_labels_deserializer,
|
||||
option_string_list_deserializer, option_term_labels_deserializer,
|
||||
string_list_deserializer, term_labels_deserializer,
|
||||
},
|
||||
parsers::parse_key_value_list,
|
||||
};
|
||||
|
||||
use super::{
|
||||
docker::container::ContainerStateStatusEnum,
|
||||
@@ -125,7 +131,11 @@ pub struct DeploymentConfig {
|
||||
|
||||
/// Extra args which are interpolated into the `docker run` command,
|
||||
/// and affect the container configuration.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
@@ -161,25 +171,19 @@ pub struct DeploymentConfig {
|
||||
pub volumes: String,
|
||||
|
||||
/// The environment variables passed to the container.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
deserialize_with = "option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub environment: String,
|
||||
|
||||
/// The docker labels given to the container.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::labels_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "labels_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_labels_deserializer"
|
||||
deserialize_with = "option_labels_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub labels: String,
|
||||
@@ -294,108 +298,6 @@ pub fn conversions_from_str(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn conversions_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(ConversionVisitor)
|
||||
}
|
||||
|
||||
pub fn option_conversions_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionConversionVisitor)
|
||||
}
|
||||
|
||||
struct ConversionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ConversionVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<Conversion>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let res = Vec::<Conversion>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?;
|
||||
let res = res
|
||||
.iter()
|
||||
.map(|Conversion { local, container }| {
|
||||
format!(" {local}: {container}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if res.is_empty() { "" } else { "\n" };
|
||||
Ok(res + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionConversionVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionConversionVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<Conversion>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
ConversionVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
ConversionVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Variants de/serialized from/to snake_case.
|
||||
///
|
||||
/// Eg.
|
||||
@@ -512,107 +414,6 @@ pub fn term_signal_labels_from_str(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn term_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(TermSignalLabelVisitor)
|
||||
}
|
||||
|
||||
pub fn option_term_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionTermSignalLabelVisitor)
|
||||
}
|
||||
|
||||
struct TermSignalLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for TermSignalLabelVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<TerminationSignalLabel>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let res = Vec::<TerminationSignalLabel>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?
|
||||
.into_iter()
|
||||
.map(|TerminationSignalLabel { signal, label }| {
|
||||
format!(" {signal}: {label}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if res.is_empty() { "" } else { "\n" };
|
||||
Ok(res + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionTermSignalLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionTermSignalLabelVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<TerminationSignalLabel>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
TermSignalLabelVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
TermSignalLabelVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct DeploymentActionState {
|
||||
|
||||
@@ -10,18 +10,20 @@ use clap::Parser;
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use derive_variants::{EnumVariants, ExtractVariant};
|
||||
use serde::{
|
||||
de::{
|
||||
value::{MapAccessDeserializer, SeqAccessDeserializer},
|
||||
Visitor,
|
||||
},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
de::{value::MapAccessDeserializer, Visitor},
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
use serror::Serror;
|
||||
use strum::{AsRefStr, Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::parser::parse_key_value_list;
|
||||
use crate::{
|
||||
deserializers::file_contents_deserializer,
|
||||
parsers::parse_key_value_list,
|
||||
};
|
||||
|
||||
/// Subtypes of [Action][action::Action].
|
||||
pub mod action;
|
||||
/// Subtypes of [Alert][alert::Alert].
|
||||
pub mod alert;
|
||||
/// Subtypes of [Alerter][alerter::Alerter].
|
||||
@@ -365,210 +367,6 @@ pub fn environment_vars_from_str(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn env_vars_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(EnvironmentVarVisitor)
|
||||
}
|
||||
|
||||
pub fn option_env_vars_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionEnvVarVisitor)
|
||||
}
|
||||
|
||||
struct EnvironmentVarVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for EnvironmentVarVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let vars = Vec::<EnvironmentVar>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?;
|
||||
let vars = vars
|
||||
.iter()
|
||||
.map(|EnvironmentVar { variable, value }| {
|
||||
format!(" {variable} = {value}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if vars.is_empty() { "" } else { "\n" };
|
||||
Ok(vars + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionEnvVarVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionEnvVarVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
EnvironmentVarVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
EnvironmentVarVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(LabelVisitor)
|
||||
}
|
||||
|
||||
pub fn option_labels_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionLabelVisitor)
|
||||
}
|
||||
|
||||
struct LabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for LabelVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.to_string();
|
||||
if out.is_empty() || out.ends_with('\n') {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let vars = Vec::<EnvironmentVar>::deserialize(
|
||||
SeqAccessDeserializer::new(seq),
|
||||
)?;
|
||||
let vars = vars
|
||||
.iter()
|
||||
.map(|EnvironmentVar { variable, value }| {
|
||||
format!(" {variable}: {value}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let extra = if vars.is_empty() { "" } else { "\n" };
|
||||
Ok(vars + extra)
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionLabelVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionLabelVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string or Vec<EnvironmentVar>")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
LabelVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
LabelVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LatestCommit {
|
||||
@@ -929,6 +727,12 @@ pub enum Operation {
|
||||
DeleteProcedure,
|
||||
RunProcedure,
|
||||
|
||||
// action
|
||||
CreateAction,
|
||||
UpdateAction,
|
||||
DeleteAction,
|
||||
RunAction,
|
||||
|
||||
// builder
|
||||
CreateBuilder,
|
||||
UpdateBuilder,
|
||||
@@ -1053,6 +857,7 @@ pub enum ResourceTarget {
|
||||
Build(String),
|
||||
Repo(String),
|
||||
Procedure(String),
|
||||
Action(String),
|
||||
Builder(String),
|
||||
Alerter(String),
|
||||
ServerTemplate(String),
|
||||
@@ -1073,6 +878,7 @@ impl ResourceTarget {
|
||||
ResourceTarget::Repo(id) => id,
|
||||
ResourceTarget::Alerter(id) => id,
|
||||
ResourceTarget::Procedure(id) => id,
|
||||
ResourceTarget::Action(id) => id,
|
||||
ResourceTarget::ServerTemplate(id) => id,
|
||||
ResourceTarget::ResourceSync(id) => id,
|
||||
};
|
||||
@@ -1145,8 +951,14 @@ impl From<&sync::ResourceSync> for ResourceTarget {
|
||||
}
|
||||
|
||||
impl From<&stack::Stack> for ResourceTarget {
|
||||
fn from(resource_sync: &stack::Stack) -> Self {
|
||||
Self::Stack(resource_sync.id.clone())
|
||||
fn from(stack: &stack::Stack) -> Self {
|
||||
Self::Stack(stack.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&action::Action> for ResourceTarget {
|
||||
fn from(action: &action::Action) -> Self {
|
||||
Self::Action(action.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1165,85 +977,7 @@ impl ResourceTargetVariant {
|
||||
ResourceTargetVariant::ServerTemplate => "server_template",
|
||||
ResourceTargetVariant::ResourceSync => "resource_sync",
|
||||
ResourceTargetVariant::Stack => "stack",
|
||||
ResourceTargetVariant::Action => "action",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Using this ensures the file contents end with trailing '\n'
|
||||
pub fn file_contents_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(FileContentsVisitor)
|
||||
}
|
||||
|
||||
/// Using this ensures the file contents end with trailing '\n'
|
||||
pub fn option_file_contents_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionFileContentsVisitor)
|
||||
}
|
||||
|
||||
struct FileContentsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for FileContentsVisitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let out = v.trim_end().to_string();
|
||||
if out.is_empty() {
|
||||
Ok(out)
|
||||
} else {
|
||||
Ok(out + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionFileContentsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionFileContentsVisitor {
|
||||
type Value = Option<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
FileContentsVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::I64;
|
||||
use crate::{
|
||||
deserializers::{
|
||||
env_vars_deserializer, option_env_vars_deserializer,
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
},
|
||||
entities::I64,
|
||||
};
|
||||
|
||||
use super::{
|
||||
environment_vars_from_str,
|
||||
@@ -178,7 +184,11 @@ pub struct RepoConfig {
|
||||
pub on_pull: SystemCommand,
|
||||
|
||||
/// Configure quick links that are displayed in the resource header
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub links: Vec<String>,
|
||||
|
||||
@@ -187,13 +197,10 @@ pub struct RepoConfig {
|
||||
/// which is given relative to the run directory.
|
||||
///
|
||||
/// If it is empty, no file will be written.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
deserialize_with = "option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub environment: String,
|
||||
|
||||
@@ -4,7 +4,10 @@ use derive_default_builder::DefaultBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{MongoId, I64};
|
||||
use crate::{
|
||||
deserializers::string_list_deserializer,
|
||||
entities::{MongoId, I64},
|
||||
};
|
||||
|
||||
use super::{permission::PermissionLevel, ResourceTargetVariant};
|
||||
|
||||
@@ -38,7 +41,7 @@ pub struct Resource<Config: Default, Info: Default = ()> {
|
||||
pub updated_at: I64,
|
||||
|
||||
/// Tag Ids
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[builder(default)]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
@@ -84,7 +87,7 @@ pub struct ResourceQuery<T: Default> {
|
||||
#[serde(default)]
|
||||
pub names: Vec<String>,
|
||||
/// Pass Vec of tag ids or tag names
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
pub tags: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub tag_behavior: TagBehavior,
|
||||
|
||||
@@ -5,6 +5,10 @@ use partial_derive2::Partial;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::deserializers::{
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
};
|
||||
|
||||
use super::{
|
||||
alert::SeverityLevel,
|
||||
resource::{AddFilters, Resource, ResourceListItem, ResourceQuery},
|
||||
@@ -65,7 +69,11 @@ pub struct ServerConfig {
|
||||
|
||||
/// Sometimes the system stats reports a mount path that is not desired.
|
||||
/// Use this field to filter it out from the report.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub ignore_mounts: Vec<String>,
|
||||
|
||||
@@ -84,7 +92,11 @@ pub struct ServerConfig {
|
||||
pub auto_prune: bool,
|
||||
|
||||
/// Configure quick links that are displayed in the resource header
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub links: Vec<String>,
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, Display};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::builder::AwsBuilderConfig;
|
||||
use crate::{
|
||||
deserializers::{
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
},
|
||||
entities::builder::AwsBuilderConfig,
|
||||
};
|
||||
|
||||
#[typeshare(serialized_as = "Partial<AwsServerTemplateConfig>")]
|
||||
pub type _PartialAwsServerTemplateConfig =
|
||||
@@ -56,7 +61,11 @@ pub struct AwsServerTemplateConfig {
|
||||
#[partial_default(default_use_https())]
|
||||
pub use_https: bool,
|
||||
/// The security groups to give to the instance.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub security_group_ids: Vec<String>,
|
||||
/// Specify the EBS volumes to attach.
|
||||
|
||||
@@ -6,7 +6,12 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::AsRefStr;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::I64;
|
||||
use crate::{
|
||||
deserializers::{
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
},
|
||||
entities::I64,
|
||||
};
|
||||
|
||||
#[typeshare(serialized_as = "Partial<HetznerServerTemplateConfig>")]
|
||||
pub type _PartialHetznerServerTemplateConfig =
|
||||
@@ -37,7 +42,11 @@ pub struct HetznerServerTemplateConfig {
|
||||
#[builder(default)]
|
||||
pub server_type: HetznerServerType,
|
||||
/// SSH key IDs ( integer ) or names ( string ) which should be injected into the Server at creation time
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub ssh_keys: Vec<String>,
|
||||
/// Network IDs which should be attached to the Server private network interface at the creation time
|
||||
|
||||
@@ -8,6 +8,12 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::deserializers::{
|
||||
env_vars_deserializer, file_contents_deserializer,
|
||||
option_env_vars_deserializer, option_file_contents_deserializer,
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
};
|
||||
|
||||
use super::{
|
||||
docker::container::ContainerListItem,
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
@@ -186,7 +192,11 @@ pub struct StackConfig {
|
||||
pub server_id: String,
|
||||
|
||||
/// Configure quick links that are displayed in the resource header
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub links: Vec<String>,
|
||||
|
||||
@@ -238,18 +248,32 @@ pub struct StackConfig {
|
||||
|
||||
/// Add paths to compose files, relative to the run path.
|
||||
/// If this is empty, will use file `compose.yaml`.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub file_paths: Vec<String>,
|
||||
|
||||
/// The name of the written environment file before `docker compose up`.
|
||||
/// Relative to the repo root.
|
||||
/// Relative to the run directory root.
|
||||
/// Default: .env
|
||||
#[serde(default = "default_env_file_path")]
|
||||
#[builder(default = "default_env_file_path()")]
|
||||
#[partial_default(default_env_file_path())]
|
||||
pub env_file_path: String,
|
||||
|
||||
/// Add additional env files to attach with `--env-file`.
|
||||
/// Relative to the run directory root.
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub additional_env_files: Vec<String>,
|
||||
|
||||
/// The git provider domain. Default: github.com
|
||||
#[serde(default = "default_git_provider")]
|
||||
#[builder(default = "default_git_provider()")]
|
||||
@@ -336,34 +360,43 @@ pub struct StackConfig {
|
||||
|
||||
/// The extra arguments to pass after `docker compose up -d`.
|
||||
/// If empty, no extra arguments will be passed.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// The extra arguments to pass after `docker compose build`.
|
||||
/// If empty, no extra build arguments will be passed.
|
||||
/// Only used if `run_build: true`
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub build_extra_args: Vec<String>,
|
||||
|
||||
/// Ignore certain services declared in the compose file when checking
|
||||
/// the stack status. For example, an init service might be exited, but the
|
||||
/// stack should be healthy. This init service should be in `ignore_services`
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub ignore_services: Vec<String>,
|
||||
|
||||
/// The contents of the file directly, for management in the UI.
|
||||
/// If this is empty, it will fall back to checking git config for
|
||||
/// repo based compose file.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::file_contents_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "file_contents_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_file_contents_deserializer"
|
||||
deserialize_with = "option_file_contents_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub file_contents: String,
|
||||
@@ -373,13 +406,10 @@ pub struct StackConfig {
|
||||
/// which is given relative to the run directory.
|
||||
///
|
||||
/// If it is empty, no file will be written.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::env_vars_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "env_vars_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_env_vars_deserializer"
|
||||
deserialize_with = "option_env_vars_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub environment: String,
|
||||
@@ -436,6 +466,7 @@ impl Default for StackConfig {
|
||||
extra_args: Default::default(),
|
||||
environment: Default::default(),
|
||||
env_file_path: default_env_file_path(),
|
||||
additional_env_files: Default::default(),
|
||||
run_build: Default::default(),
|
||||
destroy_before_deploy: Default::default(),
|
||||
build_extra_args: Default::default(),
|
||||
|
||||
@@ -2,13 +2,15 @@ use bson::{doc, Document};
|
||||
use derive_builder::Builder;
|
||||
use derive_default_builder::DefaultBuilder;
|
||||
use partial_derive2::Partial;
|
||||
use serde::{
|
||||
de::{value::SeqAccessDeserializer, Visitor},
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::deserializers::{
|
||||
file_contents_deserializer, option_file_contents_deserializer,
|
||||
option_string_list_deserializer, string_list_deserializer,
|
||||
};
|
||||
|
||||
use super::{
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
ResourceTarget, I64,
|
||||
@@ -219,10 +221,10 @@ pub struct ResourceSyncConfig {
|
||||
/// - If Git Repo based, this is relative to the root of the repo.
|
||||
/// Can be a specific file, or a directory containing multiple files / folders.
|
||||
/// See [https://komo.do/docs/sync-resources](https://komo.do/docs/sync-resources) for more information.
|
||||
#[serde(default, deserialize_with = "resource_path_deserializer")]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_resource_path_deserializer"
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub resource_path: Vec<String>,
|
||||
@@ -244,18 +246,19 @@ pub struct ResourceSyncConfig {
|
||||
|
||||
/// When using `managed` resource sync, will only export resources
|
||||
/// matching all of the given tags. If none, will match all resources.
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "option_string_list_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub match_tags: Vec<String>,
|
||||
|
||||
/// Manage the file contents in the UI.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::file_contents_deserializer"
|
||||
)]
|
||||
#[serde(default, deserialize_with = "file_contents_deserializer")]
|
||||
#[partial_attr(serde(
|
||||
default,
|
||||
deserialize_with = "super::option_file_contents_deserializer"
|
||||
deserialize_with = "option_file_contents_deserializer"
|
||||
))]
|
||||
#[builder(default)]
|
||||
pub file_contents: String,
|
||||
@@ -327,94 +330,6 @@ pub struct SyncFileContents {
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
/// Using this ensures the resource path deserialized to Vec<String>
|
||||
pub fn resource_path_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(ResourcePathVisitor)
|
||||
}
|
||||
|
||||
/// Using this ensures the resource path deserialized to Vec<String>
|
||||
pub fn option_resource_path_deserializer<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<String>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(OptionResourcePathVisitor)
|
||||
}
|
||||
|
||||
struct ResourcePathVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ResourcePathVisitor {
|
||||
type Value = Vec<String>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(vec![v.to_string()])
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
Vec::deserialize(SeqAccessDeserializer::new(seq))
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionResourcePathVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionResourcePathVisitor {
|
||||
type Value = Option<Vec<String>>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
write!(formatter, "null or string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
ResourcePathVisitor.visit_str(v).map(Some)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
ResourcePathVisitor.visit_seq(seq).map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
|
||||
pub struct ResourceSyncActionState {
|
||||
|
||||
@@ -3,10 +3,11 @@ use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
alerter::PartialAlerterConfig, build::PartialBuildConfig,
|
||||
builder::PartialBuilderConfig, deployment::PartialDeploymentConfig,
|
||||
permission::PermissionLevel, procedure::PartialProcedureConfig,
|
||||
repo::PartialRepoConfig, server::PartialServerConfig,
|
||||
action::PartialActionConfig, alerter::PartialAlerterConfig,
|
||||
build::PartialBuildConfig, builder::PartialBuilderConfig,
|
||||
deployment::PartialDeploymentConfig, permission::PermissionLevel,
|
||||
procedure::PartialProcedureConfig, repo::PartialRepoConfig,
|
||||
server::PartialServerConfig,
|
||||
server_template::PartialServerTemplateConfig,
|
||||
stack::PartialStackConfig, sync::PartialResourceSyncConfig,
|
||||
variable::Variable, ResourceTarget, ResourceTargetVariant,
|
||||
@@ -57,6 +58,13 @@ pub struct ResourcesToml {
|
||||
)]
|
||||
pub procedures: Vec<ResourceToml<PartialProcedureConfig>>,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
rename = "action",
|
||||
skip_serializing_if = "Vec::is_empty"
|
||||
)]
|
||||
pub actions: Vec<ResourceToml<PartialActionConfig>>,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
rename = "alerter",
|
||||
|
||||
@@ -87,30 +87,56 @@ impl User {
|
||||
matches!(
|
||||
user_id,
|
||||
"System"
|
||||
| "000000000000000000000000"
|
||||
| "Procedure"
|
||||
| "Github" // Github can be removed later, just keeping for backward compat.
|
||||
| "000000000000000000000001"
|
||||
| "Action"
|
||||
| "000000000000000000000002"
|
||||
| "Git Webhook"
|
||||
| "000000000000000000000003"
|
||||
| "Auto Redeploy"
|
||||
| "000000000000000000000004"
|
||||
| "Resource Sync"
|
||||
| "000000000000000000000005"
|
||||
| "Stack Wizard"
|
||||
| "000000000000000000000006"
|
||||
| "Build Manager"
|
||||
| "000000000000000000000007"
|
||||
| "Repo Manager"
|
||||
| "000000000000000000000008"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn admin_service_user(user_id: &str) -> Option<User> {
|
||||
match user_id {
|
||||
"System" => system_user().to_owned().into(),
|
||||
"Procedure" => procedure_user().to_owned().into(),
|
||||
// Github should be removed later, replaced by Git Webhook, just keeping for backward compat.
|
||||
"Github" => git_webhook_user().to_owned().into(),
|
||||
"Git Webhook" => git_webhook_user().to_owned().into(),
|
||||
"Auto Redeploy" => auto_redeploy_user().to_owned().into(),
|
||||
"Resource Sync" => sync_user().to_owned().into(),
|
||||
"Stack Wizard" => stack_user().to_owned().into(),
|
||||
"Build Manager" => build_user().to_owned().into(),
|
||||
"Repo Manager" => repo_user().to_owned().into(),
|
||||
"000000000000000000000000" | "System" => {
|
||||
system_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000001" | "Procedure" => {
|
||||
procedure_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000002" | "Action" => {
|
||||
action_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000003" | "Git Webhook" => {
|
||||
git_webhook_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000004" | "Auto Redeploy" => {
|
||||
auto_redeploy_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000005" | "Resource Sync" => {
|
||||
sync_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000006" | "Stack Wizard" => {
|
||||
stack_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000007" | "Build Manager" => {
|
||||
build_user().to_owned().into()
|
||||
}
|
||||
"000000000000000000000008" | "Repo Manager" => {
|
||||
repo_user().to_owned().into()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -120,8 +146,9 @@ pub fn system_user() -> &'static User {
|
||||
SYSTEM_USER.get_or_init(|| {
|
||||
let id_name = String::from("System");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000000".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -133,8 +160,23 @@ pub fn procedure_user() -> &'static User {
|
||||
PROCEDURE_USER.get_or_init(|| {
|
||||
let id_name = String::from("Procedure");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000001".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn action_user() -> &'static User {
|
||||
static ACTION_USER: OnceLock<User> = OnceLock::new();
|
||||
ACTION_USER.get_or_init(|| {
|
||||
let id_name = String::from("Action");
|
||||
User {
|
||||
id: "000000000000000000000002".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -146,8 +188,9 @@ pub fn git_webhook_user() -> &'static User {
|
||||
GIT_WEBHOOK_USER.get_or_init(|| {
|
||||
let id_name = String::from("Git Webhook");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000003".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -159,8 +202,9 @@ pub fn auto_redeploy_user() -> &'static User {
|
||||
AUTO_REDEPLOY_USER.get_or_init(|| {
|
||||
let id_name = String::from("Auto Redeploy");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000004".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -172,8 +216,9 @@ pub fn sync_user() -> &'static User {
|
||||
SYNC_USER.get_or_init(|| {
|
||||
let id_name = String::from("Resource Sync");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000005".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -185,8 +230,9 @@ pub fn stack_user() -> &'static User {
|
||||
STACK_USER.get_or_init(|| {
|
||||
let id_name = String::from("Stack Wizard");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000006".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -198,8 +244,9 @@ pub fn build_user() -> &'static User {
|
||||
BUILD_USER.get_or_init(|| {
|
||||
let id_name = String::from("Build Manager");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000007".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -211,8 +258,9 @@ pub fn repo_user() -> &'static User {
|
||||
REPO_USER.get_or_init(|| {
|
||||
let id_name = String::from("Repo Manager");
|
||||
User {
|
||||
id: id_name.clone(),
|
||||
id: "000000000000000000000008".to_string(),
|
||||
username: id_name,
|
||||
enabled: true,
|
||||
admin: true,
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::deserializers::string_list_deserializer;
|
||||
|
||||
use super::{
|
||||
permission::PermissionLevel, MongoId, ResourceTargetVariant, I64,
|
||||
};
|
||||
@@ -37,6 +39,7 @@ pub struct UserGroup {
|
||||
|
||||
/// User ids of group members
|
||||
#[cfg_attr(feature = "mongo", index)]
|
||||
#[serde(default, deserialize_with = "string_list_deserializer")]
|
||||
pub users: Vec<String>,
|
||||
|
||||
/// Give the user group elevated permissions on all resources of a certain type
|
||||
|
||||
@@ -35,8 +35,9 @@ use serde::Deserialize;
|
||||
pub mod api;
|
||||
pub mod busy;
|
||||
pub mod entities;
|
||||
pub mod parser;
|
||||
pub mod parsers;
|
||||
pub mod ws;
|
||||
pub mod deserializers;
|
||||
|
||||
mod request;
|
||||
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
use anyhow::Context;
|
||||
|
||||
/// Parses a list of key value pairs from a multiline string
|
||||
///
|
||||
/// Example source:
|
||||
/// ```text
|
||||
/// # Supports comments
|
||||
/// KEY_1 = value_1 # end of line comments
|
||||
///
|
||||
/// # Supports string wrapped values
|
||||
/// KEY_2="value_2"
|
||||
/// 'KEY_3 = value_3'
|
||||
///
|
||||
/// # Also supports yaml list formats
|
||||
/// - KEY_4: 'value_4'
|
||||
/// - "KEY_5=value_5"
|
||||
/// ```
|
||||
///
|
||||
/// Returns:
|
||||
/// ```text
|
||||
/// [("KEY_1", "value_1"), ("KEY_2", "value_2"), ("KEY_3", "value_3"), ("KEY_4", "value_4"), ("KEY_5", "value_5")]
|
||||
/// ```
|
||||
pub fn parse_key_value_list(
|
||||
input: &str,
|
||||
) -> anyhow::Result<Vec<(String, String)>> {
|
||||
@@ -103,3 +123,33 @@ pub fn parse_multiline_command(command: impl AsRef<str>) -> String {
|
||||
.collect::<Vec<_>>()
|
||||
.join(" && ")
|
||||
}
|
||||
|
||||
/// Parses a list of strings from a comment seperated and multiline string
|
||||
///
|
||||
/// Example source:
|
||||
/// ```text
|
||||
/// # supports comments
|
||||
/// path/to/file1 # comment1
|
||||
/// path/to/file2
|
||||
///
|
||||
/// # also supports comma seperated values
|
||||
/// path/to/file3,path/to/file4
|
||||
/// ```
|
||||
///
|
||||
/// Returns:
|
||||
/// ```text
|
||||
/// ["path/to/file1", "path/to/file2", "path/to/file3", "path/to/file4"]
|
||||
/// ```
|
||||
pub fn parse_string_list(source: impl AsRef<str>) -> Vec<String> {
|
||||
source
|
||||
.as_ref()
|
||||
.split('\n')
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
||||
.filter_map(|line| line.split(" #").next())
|
||||
.flat_map(|line| line.split(','))
|
||||
.map(str::trim)
|
||||
.filter(|entry| !entry.is_empty())
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
@@ -24,14 +24,14 @@ const komodo = KomodoClient("https://demo.komo.do", {
|
||||
});
|
||||
|
||||
const stacks: Types.StackListItem[] = await komodo.read({
|
||||
type: "ListStacks",
|
||||
params: {},
|
||||
type: "ListStacks",
|
||||
params: {},
|
||||
});
|
||||
|
||||
const stack: Types.Stack = await komodo.read({
|
||||
type: "GetStack",
|
||||
params: {
|
||||
stack: stacks[0].name,
|
||||
}
|
||||
type: "GetStack",
|
||||
params: {
|
||||
stack: stacks[0].name,
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "komodo_client",
|
||||
"version": "1.15.9",
|
||||
"version": "1.15.15",
|
||||
"description": "Komodo client package",
|
||||
"homepage": "https://komo.do",
|
||||
"main": "dist/lib.js",
|
||||
@@ -11,9 +11,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import axios from "axios";
|
||||
import {
|
||||
AuthResponses,
|
||||
ExecuteResponses,
|
||||
@@ -20,6 +19,7 @@ type InitOptions =
|
||||
| { type: "jwt"; params: { jwt: string } }
|
||||
| { type: "api-key"; params: { key: string; secret: string } };
|
||||
|
||||
/** Initialize a new client for Komodo */
|
||||
export function KomodoClient(url: string, options: InitOptions) {
|
||||
const state = {
|
||||
jwt: options.type === "jwt" ? options.params.jwt : undefined,
|
||||
@@ -27,31 +27,190 @@ export function KomodoClient(url: string, options: InitOptions) {
|
||||
secret: options.type === "api-key" ? options.params.secret : undefined,
|
||||
};
|
||||
|
||||
const request = async <Req, Res>(path: string, request: Req) =>
|
||||
await axios
|
||||
.post<Res>(url + path, request, {
|
||||
headers: {
|
||||
Authorization: state.jwt,
|
||||
"X-API-KEY": state.key,
|
||||
"X-API-SECRET": state.secret,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => data);
|
||||
const request = async <Req, Res>(
|
||||
path: "/auth" | "/user" | "/read" | "/execute" | "/write",
|
||||
request: Req
|
||||
): Promise<Res> =>
|
||||
new Promise(async (res, rej) => {
|
||||
try {
|
||||
let response = await fetch(url + path, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(request),
|
||||
headers: {
|
||||
...(state.jwt
|
||||
? {
|
||||
authorization: state.jwt,
|
||||
}
|
||||
: state.key && state.secret
|
||||
? {
|
||||
"x-api-key": state.key,
|
||||
"x-api-secret": state.secret,
|
||||
}
|
||||
: {}),
|
||||
"content-type": "application/json",
|
||||
},
|
||||
});
|
||||
if (response.status === 200) {
|
||||
const body: Res = await response.json();
|
||||
res(body);
|
||||
} else {
|
||||
try {
|
||||
const result = await response.json();
|
||||
rej({ status: response.status, result });
|
||||
} catch (error) {
|
||||
rej({
|
||||
status: response.status,
|
||||
result: {
|
||||
error: "Failed to get response body",
|
||||
trace: [JSON.stringify(error)],
|
||||
},
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
rej({
|
||||
status: 1,
|
||||
result: {
|
||||
error: "Request failed with error",
|
||||
trace: [JSON.stringify(error)],
|
||||
},
|
||||
error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const auth = async <Req extends AuthRequest>(req: Req) =>
|
||||
await request<Req, AuthResponses[Req["type"]]>("/auth", req);
|
||||
const auth = async <
|
||||
T extends AuthRequest["type"],
|
||||
Req extends Extract<AuthRequest, { type: T }>
|
||||
>(
|
||||
type: T,
|
||||
params: Req["params"]
|
||||
) =>
|
||||
await request<
|
||||
{ type: T; params: Req["params"] },
|
||||
AuthResponses[Req["type"]]
|
||||
>("/auth", {
|
||||
type,
|
||||
params,
|
||||
});
|
||||
|
||||
const user = async <Req extends UserRequest>(req: Req) =>
|
||||
await request<Req, UserResponses[Req["type"]]>("/user", req);
|
||||
const user = async <
|
||||
T extends UserRequest["type"],
|
||||
Req extends Extract<UserRequest, { type: T }>
|
||||
>(
|
||||
type: T,
|
||||
params: Req["params"]
|
||||
) =>
|
||||
await request<
|
||||
{ type: T; params: Req["params"] },
|
||||
UserResponses[Req["type"]]
|
||||
>("/user", { type, params });
|
||||
|
||||
const read = async <Req extends ReadRequest>(req: Req) =>
|
||||
await request<Req, ReadResponses[Req["type"]]>("/read", req);
|
||||
|
||||
const read = async <
|
||||
T extends ReadRequest["type"],
|
||||
Req extends Extract<ReadRequest, { type: T }>
|
||||
>(
|
||||
type: T,
|
||||
params: Req["params"]
|
||||
) =>
|
||||
await request<
|
||||
{ type: T; params: Req["params"] },
|
||||
ReadResponses[Req["type"]]
|
||||
>("/read", { type, params });
|
||||
|
||||
const write = async <Req extends WriteRequest>(req: Req) =>
|
||||
await request<Req, WriteResponses[Req["type"]]>("/write", req);
|
||||
|
||||
const write = async <
|
||||
T extends WriteRequest["type"],
|
||||
Req extends Extract<WriteRequest, { type: T }>
|
||||
>(
|
||||
type: T,
|
||||
params: Req["params"]
|
||||
) =>
|
||||
await request<
|
||||
{ type: T; params: Req["params"] },
|
||||
WriteResponses[Req["type"]]
|
||||
>("/write", { type, params });
|
||||
|
||||
const execute = async <Req extends ExecuteRequest>(req: Req) =>
|
||||
await request<Req, ExecuteResponses[Req["type"]]>("/execute", req);
|
||||
|
||||
const execute = async <
|
||||
T extends ExecuteRequest["type"],
|
||||
Req extends Extract<ExecuteRequest, { type: T }>
|
||||
>(
|
||||
type: T,
|
||||
params: Req["params"]
|
||||
) =>
|
||||
await request<
|
||||
{ type: T; params: Req["params"] },
|
||||
ExecuteResponses[Req["type"]]
|
||||
>("/execute", { type, params });
|
||||
|
||||
return { request, auth, user, read, write, execute };
|
||||
const core_version = () => read("GetVersion", {}).then((res) => res.version);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Call the `/auth` api.
|
||||
*
|
||||
* ```
|
||||
* const login_options = await komodo.auth("GetLoginOptions", {});
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/auth/index.html
|
||||
*/
|
||||
auth,
|
||||
/**
|
||||
* Call the `/user` api.
|
||||
*
|
||||
* ```
|
||||
* const { key, secret } = await komodo.user("CreateApiKey", {
|
||||
* name: "my-api-key"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/user/index.html
|
||||
*/
|
||||
user,
|
||||
/**
|
||||
* Call the `/read` api.
|
||||
*
|
||||
* ```
|
||||
* const stack = await komodo.read("GetStack", {
|
||||
* stack: "my-stack"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/read/index.html
|
||||
*/
|
||||
read,
|
||||
/**
|
||||
* Call the `/write` api.
|
||||
*
|
||||
* ```
|
||||
* const build = await komodo.write("UpdateBuild", {
|
||||
* id: "my-build",
|
||||
* config: {
|
||||
* version: "1.0.4"
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/write/index.html
|
||||
*/
|
||||
write,
|
||||
/**
|
||||
* Call the `/execute` api.
|
||||
*
|
||||
* ```
|
||||
* const update = await komodo.execute("DeployStack", {
|
||||
* stack: "my-stack"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/execute/index.html
|
||||
*/
|
||||
execute,
|
||||
/** Returns the version of Komodo Core the client is calling to. */
|
||||
core_version,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,6 +46,13 @@ export type ReadResponses = {
|
||||
ListProcedures: Types.ListProceduresResponse;
|
||||
ListFullProcedures: Types.ListFullProceduresResponse;
|
||||
|
||||
// ==== ACTION ====
|
||||
GetActionsSummary: Types.GetActionsSummaryResponse;
|
||||
GetAction: Types.GetActionResponse;
|
||||
GetActionActionState: Types.GetActionActionStateResponse;
|
||||
ListActions: Types.ListActionsResponse;
|
||||
ListFullActions: Types.ListFullActionsResponse;
|
||||
|
||||
// ==== SERVER TEMPLATE ====
|
||||
GetServerTemplate: Types.GetServerTemplateResponse;
|
||||
GetServerTemplatesSummary: Types.GetServerTemplatesSummaryResponse;
|
||||
@@ -258,6 +265,12 @@ export type WriteResponses = {
|
||||
DeleteProcedure: Types.Procedure;
|
||||
UpdateProcedure: Types.Procedure;
|
||||
|
||||
// ==== ACTION ====
|
||||
CreateAction: Types.Action;
|
||||
CopyAction: Types.Action;
|
||||
DeleteAction: Types.Action;
|
||||
UpdateAction: Types.Action;
|
||||
|
||||
// ==== SYNC ====
|
||||
CreateResourceSync: Types.ResourceSync;
|
||||
CopyResourceSync: Types.ResourceSync;
|
||||
@@ -347,6 +360,9 @@ export type ExecuteResponses = {
|
||||
|
||||
// ==== PROCEDURE ====
|
||||
RunProcedure: Types.Update;
|
||||
|
||||
// ==== ACTION ====
|
||||
RunAction: Types.Update;
|
||||
|
||||
// ==== SERVER TEMPLATE ====
|
||||
LaunchServer: Types.Update;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,63 +2,6 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
axios@^1.7.7:
|
||||
version "1.7.7"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
|
||||
integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
follow-redirects@^1.15.6:
|
||||
version "1.15.9"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.12:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
typescript@^5.6.3:
|
||||
version "5.6.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b"
|
||||
|
||||
@@ -71,6 +71,12 @@ sync_directory = "/syncs"
|
||||
## Default: /repo-cache
|
||||
repo_directory = "/repo-cache"
|
||||
|
||||
## Configure the action directory (inside the container).
|
||||
## There shouldn't be a need to change this, or even mount a volume.
|
||||
## Env: KOMODO_ACTION_DIRECTORY
|
||||
## Default: /action-cache
|
||||
action_directory = "/action-cache"
|
||||
|
||||
################
|
||||
# AUTH / LOGIN #
|
||||
################
|
||||
|
||||
@@ -12,55 +12,56 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||
"@radix-ui/react-hover-card": "^1.1.2",
|
||||
"@radix-ui/react-icons": "1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.2",
|
||||
"@radix-ui/react-progress": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@tanstack/react-query": "5.51.23",
|
||||
"@tanstack/react-table": "8.20.1",
|
||||
"@tanstack/react-query": "5.59.15",
|
||||
"@tanstack/react-table": "8.20.5",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"class-variance-authority": "0.7.0",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"jotai": "2.9.2",
|
||||
"lucide-react": "0.437.0",
|
||||
"jotai": "2.10.1",
|
||||
"lucide-react": "0.453.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"prettier": "3.3.3",
|
||||
"react": "18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-dom": "18.3.1",
|
||||
"react-minimal-pie-chart": "8.4.0",
|
||||
"react-router-dom": "6.26.0",
|
||||
"sanitize-html": "2.13.0",
|
||||
"tailwind-merge": "2.4.0",
|
||||
"react-router-dom": "6.27.0",
|
||||
"sanitize-html": "2.13.1",
|
||||
"tailwind-merge": "2.5.4",
|
||||
"tailwindcss-animate": "1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/sanitize-html": "2.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.0.1",
|
||||
"@types/react": "18.3.11",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/sanitize-html": "2.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.10.0",
|
||||
"@typescript-eslint/parser": "8.0.1",
|
||||
"@vitejs/plugin-react": "4.3.1",
|
||||
"@vitejs/plugin-react": "4.3.3",
|
||||
"autoprefixer": "10.4.20",
|
||||
"eslint": "9.9.0",
|
||||
"eslint-plugin-react-hooks": "4.6.2",
|
||||
"eslint-plugin-react-refresh": "0.4.9",
|
||||
"postcss": "8.4.41",
|
||||
"tailwindcss": "3.4.9",
|
||||
"typescript": "5.5.4",
|
||||
"vite": "5.4.0",
|
||||
"eslint": "9.13.0",
|
||||
"eslint-plugin-react-hooks": "5.0.0",
|
||||
"eslint-plugin-react-refresh": "0.4.13",
|
||||
"postcss": "8.4.47",
|
||||
"tailwindcss": "3.4.14",
|
||||
"typescript": "5.6.3",
|
||||
"vite": "5.4.9",
|
||||
"vite-tsconfig-paths": "5.0.1"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
|
||||
91
frontend/public/client/lib.d.ts
vendored
Normal file
91
frontend/public/client/lib.d.ts
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
import { AuthResponses, ExecuteResponses, ReadResponses, UserResponses, WriteResponses } from "./responses.js";
|
||||
import { AuthRequest, ExecuteRequest, ReadRequest, UserRequest, WriteRequest } from "./types.js";
|
||||
export * as Types from "./types.js";
|
||||
type InitOptions = {
|
||||
type: "jwt";
|
||||
params: {
|
||||
jwt: string;
|
||||
};
|
||||
} | {
|
||||
type: "api-key";
|
||||
params: {
|
||||
key: string;
|
||||
secret: string;
|
||||
};
|
||||
};
|
||||
/** Initialize a new client for Komodo */
|
||||
export declare function KomodoClient(url: string, options: InitOptions): {
|
||||
/**
|
||||
* Call the `/auth` api.
|
||||
*
|
||||
* ```
|
||||
* const login_options = await komodo.auth("GetLoginOptions", {});
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/auth/index.html
|
||||
*/
|
||||
auth: <T extends AuthRequest["type"], Req extends Extract<AuthRequest, {
|
||||
type: T;
|
||||
}>>(type: T, params: Req["params"]) => Promise<AuthResponses[Req["type"]]>;
|
||||
/**
|
||||
* Call the `/user` api.
|
||||
*
|
||||
* ```
|
||||
* const { key, secret } = await komodo.user("CreateApiKey", {
|
||||
* name: "my-api-key"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/user/index.html
|
||||
*/
|
||||
user: <T extends UserRequest["type"], Req extends Extract<UserRequest, {
|
||||
type: T;
|
||||
}>>(type: T, params: Req["params"]) => Promise<UserResponses[Req["type"]]>;
|
||||
/**
|
||||
* Call the `/read` api.
|
||||
*
|
||||
* ```
|
||||
* const stack = await komodo.read("GetStack", {
|
||||
* stack: "my-stack"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/read/index.html
|
||||
*/
|
||||
read: <T extends ReadRequest["type"], Req extends Extract<ReadRequest, {
|
||||
type: T;
|
||||
}>>(type: T, params: Req["params"]) => Promise<ReadResponses[Req["type"]]>;
|
||||
/**
|
||||
* Call the `/write` api.
|
||||
*
|
||||
* ```
|
||||
* const build = await komodo.write("UpdateBuild", {
|
||||
* id: "my-build",
|
||||
* config: {
|
||||
* version: "1.0.4"
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/write/index.html
|
||||
*/
|
||||
write: <T extends WriteRequest["type"], Req extends Extract<WriteRequest, {
|
||||
type: T;
|
||||
}>>(type: T, params: Req["params"]) => Promise<WriteResponses[Req["type"]]>;
|
||||
/**
|
||||
* Call the `/execute` api.
|
||||
*
|
||||
* ```
|
||||
* const update = await komodo.execute("DeployStack", {
|
||||
* stack: "my-stack"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/execute/index.html
|
||||
*/
|
||||
execute: <T extends ExecuteRequest["type"], Req extends Extract<ExecuteRequest, {
|
||||
type: T;
|
||||
}>>(type: T, params: Req["params"]) => Promise<ExecuteResponses[Req["type"]]>;
|
||||
/** Returns the version of Komodo Core the client is calling to. */
|
||||
core_version: () => Promise<string>;
|
||||
};
|
||||
134
frontend/public/client/lib.js
Normal file
134
frontend/public/client/lib.js
Normal file
@@ -0,0 +1,134 @@
|
||||
export * as Types from "./types.js";
|
||||
/** Initialize a new client for Komodo */
|
||||
export function KomodoClient(url, options) {
|
||||
const state = {
|
||||
jwt: options.type === "jwt" ? options.params.jwt : undefined,
|
||||
key: options.type === "api-key" ? options.params.key : undefined,
|
||||
secret: options.type === "api-key" ? options.params.secret : undefined,
|
||||
};
|
||||
const request = async (path, request) => new Promise(async (res, rej) => {
|
||||
try {
|
||||
let response = await fetch(url + path, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(request),
|
||||
headers: {
|
||||
...(state.jwt
|
||||
? {
|
||||
authorization: state.jwt,
|
||||
}
|
||||
: state.key && state.secret
|
||||
? {
|
||||
"x-api-key": state.key,
|
||||
"x-api-secret": state.secret,
|
||||
}
|
||||
: {}),
|
||||
"content-type": "application/json",
|
||||
},
|
||||
});
|
||||
if (response.status === 200) {
|
||||
const body = await response.json();
|
||||
res(body);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const result = await response.json();
|
||||
rej({ status: response.status, result });
|
||||
}
|
||||
catch (error) {
|
||||
rej({
|
||||
status: response.status,
|
||||
result: {
|
||||
error: "Failed to get response body",
|
||||
trace: [JSON.stringify(error)],
|
||||
},
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
rej({
|
||||
status: 1,
|
||||
result: {
|
||||
error: "Request failed with error",
|
||||
trace: [JSON.stringify(error)],
|
||||
},
|
||||
error,
|
||||
});
|
||||
}
|
||||
});
|
||||
const auth = async (type, params) => await request("/auth", {
|
||||
type,
|
||||
params,
|
||||
});
|
||||
const user = async (type, params) => await request("/user", { type, params });
|
||||
const read = async (type, params) => await request("/read", { type, params });
|
||||
const write = async (type, params) => await request("/write", { type, params });
|
||||
const execute = async (type, params) => await request("/execute", { type, params });
|
||||
const core_version = () => read("GetVersion", {}).then((res) => res.version);
|
||||
return {
|
||||
/**
|
||||
* Call the `/auth` api.
|
||||
*
|
||||
* ```
|
||||
* const login_options = await komodo.auth("GetLoginOptions", {});
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/auth/index.html
|
||||
*/
|
||||
auth,
|
||||
/**
|
||||
* Call the `/user` api.
|
||||
*
|
||||
* ```
|
||||
* const { key, secret } = await komodo.user("CreateApiKey", {
|
||||
* name: "my-api-key"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/user/index.html
|
||||
*/
|
||||
user,
|
||||
/**
|
||||
* Call the `/read` api.
|
||||
*
|
||||
* ```
|
||||
* const stack = await komodo.read("GetStack", {
|
||||
* stack: "my-stack"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/read/index.html
|
||||
*/
|
||||
read,
|
||||
/**
|
||||
* Call the `/write` api.
|
||||
*
|
||||
* ```
|
||||
* const build = await komodo.write("UpdateBuild", {
|
||||
* id: "my-build",
|
||||
* config: {
|
||||
* version: "1.0.4"
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/write/index.html
|
||||
*/
|
||||
write,
|
||||
/**
|
||||
* Call the `/execute` api.
|
||||
*
|
||||
* ```
|
||||
* const update = await komodo.execute("DeployStack", {
|
||||
* stack: "my-stack"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* https://docs.rs/komodo_client/latest/komodo_client/api/execute/index.html
|
||||
*/
|
||||
execute,
|
||||
/** Returns the version of Komodo Core the client is calling to. */
|
||||
core_version,
|
||||
};
|
||||
}
|
||||
288
frontend/public/client/responses.d.ts
vendored
Normal file
288
frontend/public/client/responses.d.ts
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
import * as Types from "./types.js";
|
||||
export type AuthResponses = {
|
||||
GetLoginOptions: Types.GetLoginOptionsResponse;
|
||||
CreateLocalUser: Types.CreateLocalUserResponse;
|
||||
LoginLocalUser: Types.LoginLocalUserResponse;
|
||||
ExchangeForJwt: Types.ExchangeForJwtResponse;
|
||||
GetUser: Types.GetUserResponse;
|
||||
};
|
||||
export type UserResponses = {
|
||||
PushRecentlyViewed: Types.PushRecentlyViewedResponse;
|
||||
SetLastSeenUpdate: Types.SetLastSeenUpdateResponse;
|
||||
CreateApiKey: Types.CreateApiKeyResponse;
|
||||
DeleteApiKey: Types.DeleteApiKeyResponse;
|
||||
};
|
||||
export type ReadResponses = {
|
||||
GetVersion: Types.GetVersionResponse;
|
||||
GetCoreInfo: Types.GetCoreInfoResponse;
|
||||
ListSecrets: Types.ListSecretsResponse;
|
||||
ListGitProvidersFromConfig: Types.ListGitProvidersFromConfigResponse;
|
||||
ListDockerRegistriesFromConfig: Types.ListDockerRegistriesFromConfigResponse;
|
||||
GetUsername: Types.GetUsernameResponse;
|
||||
GetPermissionLevel: Types.GetPermissionLevelResponse;
|
||||
FindUser: Types.FindUserResponse;
|
||||
ListUsers: Types.ListUsersResponse;
|
||||
ListApiKeys: Types.ListApiKeysResponse;
|
||||
ListApiKeysForServiceUser: Types.ListApiKeysForServiceUserResponse;
|
||||
ListPermissions: Types.ListPermissionsResponse;
|
||||
ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse;
|
||||
GetUserGroup: Types.GetUserGroupResponse;
|
||||
ListUserGroups: Types.ListUserGroupsResponse;
|
||||
FindResources: Types.FindResourcesResponse;
|
||||
GetProceduresSummary: Types.GetProceduresSummaryResponse;
|
||||
GetProcedure: Types.GetProcedureResponse;
|
||||
GetProcedureActionState: Types.GetProcedureActionStateResponse;
|
||||
ListProcedures: Types.ListProceduresResponse;
|
||||
ListFullProcedures: Types.ListFullProceduresResponse;
|
||||
GetActionsSummary: Types.GetActionsSummaryResponse;
|
||||
GetAction: Types.GetActionResponse;
|
||||
GetActionActionState: Types.GetActionActionStateResponse;
|
||||
ListActions: Types.ListActionsResponse;
|
||||
ListFullActions: Types.ListFullActionsResponse;
|
||||
GetServerTemplate: Types.GetServerTemplateResponse;
|
||||
GetServerTemplatesSummary: Types.GetServerTemplatesSummaryResponse;
|
||||
ListServerTemplates: Types.ListServerTemplatesResponse;
|
||||
ListFullServerTemplates: Types.ListFullServerTemplatesResponse;
|
||||
GetServersSummary: Types.GetServersSummaryResponse;
|
||||
GetServer: Types.GetServerResponse;
|
||||
GetServerState: Types.GetServerStateResponse;
|
||||
GetPeripheryVersion: Types.GetPeripheryVersionResponse;
|
||||
ListDockerContainers: Types.ListDockerContainersResponse;
|
||||
ListAllDockerContainers: Types.ListAllDockerContainersResponse;
|
||||
InspectDockerContainer: Types.InspectDockerContainerResponse;
|
||||
GetResourceMatchingContainer: Types.GetResourceMatchingContainerResponse;
|
||||
GetContainerLog: Types.GetContainerLogResponse;
|
||||
SearchContainerLog: Types.SearchContainerLogResponse;
|
||||
ListDockerNetworks: Types.ListDockerNetworksResponse;
|
||||
InspectDockerNetwork: Types.InspectDockerNetworkResponse;
|
||||
ListDockerImages: Types.ListDockerImagesResponse;
|
||||
InspectDockerImage: Types.InspectDockerImageResponse;
|
||||
ListDockerImageHistory: Types.ListDockerImageHistoryResponse;
|
||||
ListDockerVolumes: Types.ListDockerVolumesResponse;
|
||||
InspectDockerVolume: Types.InspectDockerVolumeResponse;
|
||||
ListComposeProjects: Types.ListComposeProjectsResponse;
|
||||
GetServerActionState: Types.GetServerActionStateResponse;
|
||||
GetHistoricalServerStats: Types.GetHistoricalServerStatsResponse;
|
||||
ListServers: Types.ListServersResponse;
|
||||
ListFullServers: Types.ListFullServersResponse;
|
||||
GetDeploymentsSummary: Types.GetDeploymentsSummaryResponse;
|
||||
GetDeployment: Types.GetDeploymentResponse;
|
||||
GetDeploymentContainer: Types.GetDeploymentContainerResponse;
|
||||
GetDeploymentActionState: Types.GetDeploymentActionStateResponse;
|
||||
GetDeploymentStats: Types.GetDeploymentStatsResponse;
|
||||
GetDeploymentLog: Types.GetDeploymentLogResponse;
|
||||
SearchDeploymentLog: Types.SearchDeploymentLogResponse;
|
||||
ListDeployments: Types.ListDeploymentsResponse;
|
||||
ListFullDeployments: Types.ListFullDeploymentsResponse;
|
||||
ListCommonDeploymentExtraArgs: Types.ListCommonDeploymentExtraArgsResponse;
|
||||
GetBuildsSummary: Types.GetBuildsSummaryResponse;
|
||||
GetBuild: Types.GetBuildResponse;
|
||||
GetBuildActionState: Types.GetBuildActionStateResponse;
|
||||
GetBuildMonthlyStats: Types.GetBuildMonthlyStatsResponse;
|
||||
GetBuildWebhookEnabled: Types.GetBuildWebhookEnabledResponse;
|
||||
ListBuilds: Types.ListBuildsResponse;
|
||||
ListFullBuilds: Types.ListFullBuildsResponse;
|
||||
ListBuildVersions: Types.ListBuildVersionsResponse;
|
||||
ListCommonBuildExtraArgs: Types.ListCommonBuildExtraArgsResponse;
|
||||
GetReposSummary: Types.GetReposSummaryResponse;
|
||||
GetRepo: Types.GetRepoResponse;
|
||||
GetRepoActionState: Types.GetRepoActionStateResponse;
|
||||
GetRepoWebhooksEnabled: Types.GetRepoWebhooksEnabledResponse;
|
||||
ListRepos: Types.ListReposResponse;
|
||||
ListFullRepos: Types.ListFullReposResponse;
|
||||
GetResourceSyncsSummary: Types.GetResourceSyncsSummaryResponse;
|
||||
GetResourceSync: Types.GetResourceSyncResponse;
|
||||
GetResourceSyncActionState: Types.GetResourceSyncActionStateResponse;
|
||||
GetSyncWebhooksEnabled: Types.GetSyncWebhooksEnabledResponse;
|
||||
ListResourceSyncs: Types.ListResourceSyncsResponse;
|
||||
ListFullResourceSyncs: Types.ListFullResourceSyncsResponse;
|
||||
GetStacksSummary: Types.GetStacksSummaryResponse;
|
||||
GetStack: Types.GetStackResponse;
|
||||
GetStackActionState: Types.GetStackActionStateResponse;
|
||||
GetStackWebhooksEnabled: Types.GetStackWebhooksEnabledResponse;
|
||||
GetStackServiceLog: Types.GetStackServiceLogResponse;
|
||||
SearchStackServiceLog: Types.SearchStackServiceLogResponse;
|
||||
ListStacks: Types.ListStacksResponse;
|
||||
ListFullStacks: Types.ListFullStacksResponse;
|
||||
ListStackServices: Types.ListStackServicesResponse;
|
||||
ListCommonStackExtraArgs: Types.ListCommonStackExtraArgsResponse;
|
||||
ListCommonStackBuildExtraArgs: Types.ListCommonStackBuildExtraArgsResponse;
|
||||
GetBuildersSummary: Types.GetBuildersSummaryResponse;
|
||||
GetBuilder: Types.GetBuilderResponse;
|
||||
ListBuilders: Types.ListBuildersResponse;
|
||||
ListFullBuilders: Types.ListFullBuildersResponse;
|
||||
GetAlertersSummary: Types.GetAlertersSummaryResponse;
|
||||
GetAlerter: Types.GetAlerterResponse;
|
||||
ListAlerters: Types.ListAlertersResponse;
|
||||
ListFullAlerters: Types.ListFullAlertersResponse;
|
||||
ExportAllResourcesToToml: Types.ExportAllResourcesToTomlResponse;
|
||||
ExportResourcesToToml: Types.ExportResourcesToTomlResponse;
|
||||
GetTag: Types.GetTagResponse;
|
||||
ListTags: Types.ListTagsResponse;
|
||||
GetUpdate: Types.GetUpdateResponse;
|
||||
ListUpdates: Types.ListUpdatesResponse;
|
||||
ListAlerts: Types.ListAlertsResponse;
|
||||
GetAlert: Types.GetAlertResponse;
|
||||
GetSystemInformation: Types.GetSystemInformationResponse;
|
||||
GetSystemStats: Types.GetSystemStatsResponse;
|
||||
ListSystemProcesses: Types.ListSystemProcessesResponse;
|
||||
GetVariable: Types.GetVariableResponse;
|
||||
ListVariables: Types.ListVariablesResponse;
|
||||
GetGitProviderAccount: Types.GetGitProviderAccountResponse;
|
||||
ListGitProviderAccounts: Types.ListGitProviderAccountsResponse;
|
||||
GetDockerRegistryAccount: Types.GetDockerRegistryAccountResponse;
|
||||
ListDockerRegistryAccounts: Types.ListDockerRegistryAccountsResponse;
|
||||
};
|
||||
export type WriteResponses = {
|
||||
UpdateUserUsername: Types.UpdateUserUsername;
|
||||
UpdateUserPassword: Types.UpdateUserPassword;
|
||||
DeleteUser: Types.DeleteUser;
|
||||
CreateServiceUser: Types.CreateServiceUserResponse;
|
||||
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
|
||||
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
|
||||
DeleteApiKeyForServiceUser: Types.DeleteApiKeyForServiceUserResponse;
|
||||
CreateUserGroup: Types.UserGroup;
|
||||
RenameUserGroup: Types.UserGroup;
|
||||
DeleteUserGroup: Types.UserGroup;
|
||||
AddUserToUserGroup: Types.UserGroup;
|
||||
RemoveUserFromUserGroup: Types.UserGroup;
|
||||
SetUsersInUserGroup: Types.UserGroup;
|
||||
UpdateUserAdmin: Types.UpdateUserAdminResponse;
|
||||
UpdateUserBasePermissions: Types.UpdateUserBasePermissionsResponse;
|
||||
UpdatePermissionOnResourceType: Types.UpdatePermissionOnResourceTypeResponse;
|
||||
UpdatePermissionOnTarget: Types.UpdatePermissionOnTargetResponse;
|
||||
UpdateDescription: Types.UpdateDescriptionResponse;
|
||||
LaunchServer: Types.Update;
|
||||
CreateServer: Types.Server;
|
||||
DeleteServer: Types.Server;
|
||||
UpdateServer: Types.Server;
|
||||
RenameServer: Types.Update;
|
||||
CreateNetwork: Types.Update;
|
||||
CreateDeployment: Types.Deployment;
|
||||
CopyDeployment: Types.Deployment;
|
||||
DeleteDeployment: Types.Deployment;
|
||||
UpdateDeployment: Types.Deployment;
|
||||
RenameDeployment: Types.Update;
|
||||
CreateBuild: Types.Build;
|
||||
CopyBuild: Types.Build;
|
||||
DeleteBuild: Types.Build;
|
||||
UpdateBuild: Types.Build;
|
||||
RefreshBuildCache: Types.NoData;
|
||||
CreateBuildWebhook: Types.CreateBuildWebhookResponse;
|
||||
DeleteBuildWebhook: Types.DeleteBuildWebhookResponse;
|
||||
CreateBuilder: Types.Builder;
|
||||
CopyBuilder: Types.Builder;
|
||||
DeleteBuilder: Types.Builder;
|
||||
UpdateBuilder: Types.Builder;
|
||||
CreateServerTemplate: Types.ServerTemplate;
|
||||
CopyServerTemplate: Types.ServerTemplate;
|
||||
DeleteServerTemplate: Types.ServerTemplate;
|
||||
UpdateServerTemplate: Types.ServerTemplate;
|
||||
CreateRepo: Types.Repo;
|
||||
CopyRepo: Types.Repo;
|
||||
DeleteRepo: Types.Repo;
|
||||
UpdateRepo: Types.Repo;
|
||||
RefreshRepoCache: Types.NoData;
|
||||
CreateRepoWebhook: Types.CreateRepoWebhookResponse;
|
||||
DeleteRepoWebhook: Types.DeleteRepoWebhookResponse;
|
||||
CreateAlerter: Types.Alerter;
|
||||
CopyAlerter: Types.Alerter;
|
||||
DeleteAlerter: Types.Alerter;
|
||||
UpdateAlerter: Types.Alerter;
|
||||
CreateProcedure: Types.Procedure;
|
||||
CopyProcedure: Types.Procedure;
|
||||
DeleteProcedure: Types.Procedure;
|
||||
UpdateProcedure: Types.Procedure;
|
||||
CreateAction: Types.Action;
|
||||
CopyAction: Types.Action;
|
||||
DeleteAction: Types.Action;
|
||||
UpdateAction: Types.Action;
|
||||
CreateResourceSync: Types.ResourceSync;
|
||||
CopyResourceSync: Types.ResourceSync;
|
||||
DeleteResourceSync: Types.ResourceSync;
|
||||
UpdateResourceSync: Types.ResourceSync;
|
||||
CommitSync: Types.ResourceSync;
|
||||
WriteSyncFileContents: Types.Update;
|
||||
RefreshResourceSyncPending: Types.ResourceSync;
|
||||
CreateSyncWebhook: Types.CreateSyncWebhookResponse;
|
||||
DeleteSyncWebhook: Types.DeleteSyncWebhookResponse;
|
||||
CreateStack: Types.Stack;
|
||||
CopyStack: Types.Stack;
|
||||
DeleteStack: Types.Stack;
|
||||
UpdateStack: Types.Stack;
|
||||
RenameStack: Types.Update;
|
||||
WriteStackFileContents: Types.Update;
|
||||
RefreshStackCache: Types.NoData;
|
||||
CreateStackWebhook: Types.CreateStackWebhookResponse;
|
||||
DeleteStackWebhook: Types.DeleteStackWebhookResponse;
|
||||
CreateTag: Types.Tag;
|
||||
DeleteTag: Types.Tag;
|
||||
RenameTag: Types.Tag;
|
||||
UpdateTagsOnResource: Types.UpdateTagsOnResourceResponse;
|
||||
CreateVariable: Types.CreateVariableResponse;
|
||||
UpdateVariableValue: Types.UpdateVariableValueResponse;
|
||||
UpdateVariableDescription: Types.UpdateVariableDescriptionResponse;
|
||||
UpdateVariableIsSecret: Types.UpdateVariableIsSecretResponse;
|
||||
DeleteVariable: Types.DeleteVariableResponse;
|
||||
CreateGitProviderAccount: Types.CreateGitProviderAccountResponse;
|
||||
UpdateGitProviderAccount: Types.UpdateGitProviderAccountResponse;
|
||||
DeleteGitProviderAccount: Types.DeleteGitProviderAccountResponse;
|
||||
CreateDockerRegistryAccount: Types.CreateDockerRegistryAccountResponse;
|
||||
UpdateDockerRegistryAccount: Types.UpdateDockerRegistryAccountResponse;
|
||||
DeleteDockerRegistryAccount: Types.DeleteDockerRegistryAccountResponse;
|
||||
};
|
||||
export type ExecuteResponses = {
|
||||
StartContainer: Types.Update;
|
||||
RestartContainer: Types.Update;
|
||||
PauseContainer: Types.Update;
|
||||
UnpauseContainer: Types.Update;
|
||||
StopContainer: Types.Update;
|
||||
DestroyContainer: Types.Update;
|
||||
StartAllContainers: Types.Update;
|
||||
RestartAllContainers: Types.Update;
|
||||
PauseAllContainers: Types.Update;
|
||||
UnpauseAllContainers: Types.Update;
|
||||
StopAllContainers: Types.Update;
|
||||
PruneContainers: Types.Update;
|
||||
DeleteNetwork: Types.Update;
|
||||
PruneNetworks: Types.Update;
|
||||
DeleteImage: Types.Update;
|
||||
PruneImages: Types.Update;
|
||||
DeleteVolume: Types.Update;
|
||||
PruneVolumes: Types.Update;
|
||||
PruneDockerBuilders: Types.Update;
|
||||
PruneBuildx: Types.Update;
|
||||
PruneSystem: Types.Update;
|
||||
Deploy: Types.Update;
|
||||
StartDeployment: Types.Update;
|
||||
RestartDeployment: Types.Update;
|
||||
PauseDeployment: Types.Update;
|
||||
UnpauseDeployment: Types.Update;
|
||||
StopDeployment: Types.Update;
|
||||
DestroyDeployment: Types.Update;
|
||||
RunBuild: Types.Update;
|
||||
CancelBuild: Types.Update;
|
||||
CloneRepo: Types.Update;
|
||||
PullRepo: Types.Update;
|
||||
BuildRepo: Types.Update;
|
||||
CancelRepoBuild: Types.Update;
|
||||
RunProcedure: Types.Update;
|
||||
RunAction: Types.Update;
|
||||
LaunchServer: Types.Update;
|
||||
RunSync: Types.Update;
|
||||
DeployStack: Types.Update;
|
||||
DeployStackIfChanged: Types.Update;
|
||||
StartStack: Types.Update;
|
||||
RestartStack: Types.Update;
|
||||
StopStack: Types.Update;
|
||||
PauseStack: Types.Update;
|
||||
UnpauseStack: Types.Update;
|
||||
DestroyStack: Types.Update;
|
||||
DeployStackService: Types.Update;
|
||||
StartStackService: Types.Update;
|
||||
RestartStackService: Types.Update;
|
||||
StopStackService: Types.Update;
|
||||
PauseStackService: Types.Update;
|
||||
UnpauseStackService: Types.Update;
|
||||
DestroyStackService: Types.Update;
|
||||
};
|
||||
1
frontend/public/client/responses.js
Normal file
1
frontend/public/client/responses.js
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
7173
frontend/public/client/types.d.ts
vendored
Normal file
7173
frontend/public/client/types.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
481
frontend/public/client/types.js
Normal file
481
frontend/public/client/types.js
Normal file
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
Generated by typeshare 1.11.0
|
||||
*/
|
||||
/** The levels of permission that a User or UserGroup can have on a resource. */
|
||||
export var PermissionLevel;
|
||||
(function (PermissionLevel) {
|
||||
/** No permissions. */
|
||||
PermissionLevel["None"] = "None";
|
||||
/** Can see the rousource */
|
||||
PermissionLevel["Read"] = "Read";
|
||||
/** Can execute actions on the resource */
|
||||
PermissionLevel["Execute"] = "Execute";
|
||||
/** Can update the resource configuration */
|
||||
PermissionLevel["Write"] = "Write";
|
||||
})(PermissionLevel || (PermissionLevel = {}));
|
||||
export var ActionState;
|
||||
(function (ActionState) {
|
||||
/** Unknown case */
|
||||
ActionState["Unknown"] = "Unknown";
|
||||
/** Last clone / pull successful (or never cloned) */
|
||||
ActionState["Ok"] = "Ok";
|
||||
/** Last clone / pull failed */
|
||||
ActionState["Failed"] = "Failed";
|
||||
/** Currently running */
|
||||
ActionState["Running"] = "Running";
|
||||
})(ActionState || (ActionState = {}));
|
||||
export var TagBehavior;
|
||||
(function (TagBehavior) {
|
||||
/** Returns resources which have strictly all the tags */
|
||||
TagBehavior["All"] = "All";
|
||||
/** Returns resources which have one or more of the tags */
|
||||
TagBehavior["Any"] = "Any";
|
||||
})(TagBehavior || (TagBehavior = {}));
|
||||
export var BuildState;
|
||||
(function (BuildState) {
|
||||
/** Last build successful (or never built) */
|
||||
BuildState["Ok"] = "Ok";
|
||||
/** Last build failed */
|
||||
BuildState["Failed"] = "Failed";
|
||||
/** Currently building */
|
||||
BuildState["Building"] = "Building";
|
||||
/** Other case */
|
||||
BuildState["Unknown"] = "Unknown";
|
||||
})(BuildState || (BuildState = {}));
|
||||
export var RestartMode;
|
||||
(function (RestartMode) {
|
||||
RestartMode["NoRestart"] = "no";
|
||||
RestartMode["OnFailure"] = "on-failure";
|
||||
RestartMode["Always"] = "always";
|
||||
RestartMode["UnlessStopped"] = "unless-stopped";
|
||||
})(RestartMode || (RestartMode = {}));
|
||||
export var TerminationSignal;
|
||||
(function (TerminationSignal) {
|
||||
TerminationSignal["SigHup"] = "SIGHUP";
|
||||
TerminationSignal["SigInt"] = "SIGINT";
|
||||
TerminationSignal["SigQuit"] = "SIGQUIT";
|
||||
TerminationSignal["SigTerm"] = "SIGTERM";
|
||||
})(TerminationSignal || (TerminationSignal = {}));
|
||||
/**
|
||||
* Variants de/serialized from/to snake_case.
|
||||
*
|
||||
* Eg.
|
||||
* - NotDeployed -> not_deployed
|
||||
* - Restarting -> restarting
|
||||
* - Running -> running.
|
||||
*/
|
||||
export var DeploymentState;
|
||||
(function (DeploymentState) {
|
||||
DeploymentState["Unknown"] = "unknown";
|
||||
DeploymentState["NotDeployed"] = "not_deployed";
|
||||
DeploymentState["Created"] = "created";
|
||||
DeploymentState["Restarting"] = "restarting";
|
||||
DeploymentState["Running"] = "running";
|
||||
DeploymentState["Removing"] = "removing";
|
||||
DeploymentState["Paused"] = "paused";
|
||||
DeploymentState["Exited"] = "exited";
|
||||
DeploymentState["Dead"] = "dead";
|
||||
})(DeploymentState || (DeploymentState = {}));
|
||||
/** Severity level of problem. */
|
||||
export var SeverityLevel;
|
||||
(function (SeverityLevel) {
|
||||
/** No problem. */
|
||||
SeverityLevel["Ok"] = "OK";
|
||||
/** Problem is imminent. */
|
||||
SeverityLevel["Warning"] = "WARNING";
|
||||
/** Problem fully realized. */
|
||||
SeverityLevel["Critical"] = "CRITICAL";
|
||||
})(SeverityLevel || (SeverityLevel = {}));
|
||||
export var Timelength;
|
||||
(function (Timelength) {
|
||||
Timelength["OneSecond"] = "1-sec";
|
||||
Timelength["FiveSeconds"] = "5-sec";
|
||||
Timelength["TenSeconds"] = "10-sec";
|
||||
Timelength["FifteenSeconds"] = "15-sec";
|
||||
Timelength["ThirtySeconds"] = "30-sec";
|
||||
Timelength["OneMinute"] = "1-min";
|
||||
Timelength["TwoMinutes"] = "2-min";
|
||||
Timelength["FiveMinutes"] = "5-min";
|
||||
Timelength["TenMinutes"] = "10-min";
|
||||
Timelength["FifteenMinutes"] = "15-min";
|
||||
Timelength["ThirtyMinutes"] = "30-min";
|
||||
Timelength["OneHour"] = "1-hr";
|
||||
Timelength["TwoHours"] = "2-hr";
|
||||
Timelength["SixHours"] = "6-hr";
|
||||
Timelength["EightHours"] = "8-hr";
|
||||
Timelength["TwelveHours"] = "12-hr";
|
||||
Timelength["OneDay"] = "1-day";
|
||||
Timelength["ThreeDay"] = "3-day";
|
||||
Timelength["OneWeek"] = "1-wk";
|
||||
Timelength["TwoWeeks"] = "2-wk";
|
||||
Timelength["ThirtyDays"] = "30-day";
|
||||
})(Timelength || (Timelength = {}));
|
||||
export var Operation;
|
||||
(function (Operation) {
|
||||
Operation["None"] = "None";
|
||||
Operation["CreateServer"] = "CreateServer";
|
||||
Operation["UpdateServer"] = "UpdateServer";
|
||||
Operation["DeleteServer"] = "DeleteServer";
|
||||
Operation["RenameServer"] = "RenameServer";
|
||||
Operation["StartContainer"] = "StartContainer";
|
||||
Operation["RestartContainer"] = "RestartContainer";
|
||||
Operation["PauseContainer"] = "PauseContainer";
|
||||
Operation["UnpauseContainer"] = "UnpauseContainer";
|
||||
Operation["StopContainer"] = "StopContainer";
|
||||
Operation["DestroyContainer"] = "DestroyContainer";
|
||||
Operation["StartAllContainers"] = "StartAllContainers";
|
||||
Operation["RestartAllContainers"] = "RestartAllContainers";
|
||||
Operation["PauseAllContainers"] = "PauseAllContainers";
|
||||
Operation["UnpauseAllContainers"] = "UnpauseAllContainers";
|
||||
Operation["StopAllContainers"] = "StopAllContainers";
|
||||
Operation["PruneContainers"] = "PruneContainers";
|
||||
Operation["CreateNetwork"] = "CreateNetwork";
|
||||
Operation["DeleteNetwork"] = "DeleteNetwork";
|
||||
Operation["PruneNetworks"] = "PruneNetworks";
|
||||
Operation["DeleteImage"] = "DeleteImage";
|
||||
Operation["PruneImages"] = "PruneImages";
|
||||
Operation["DeleteVolume"] = "DeleteVolume";
|
||||
Operation["PruneVolumes"] = "PruneVolumes";
|
||||
Operation["PruneDockerBuilders"] = "PruneDockerBuilders";
|
||||
Operation["PruneBuildx"] = "PruneBuildx";
|
||||
Operation["PruneSystem"] = "PruneSystem";
|
||||
Operation["CreateStack"] = "CreateStack";
|
||||
Operation["UpdateStack"] = "UpdateStack";
|
||||
Operation["RenameStack"] = "RenameStack";
|
||||
Operation["DeleteStack"] = "DeleteStack";
|
||||
Operation["WriteStackContents"] = "WriteStackContents";
|
||||
Operation["RefreshStackCache"] = "RefreshStackCache";
|
||||
Operation["DeployStack"] = "DeployStack";
|
||||
Operation["StartStack"] = "StartStack";
|
||||
Operation["RestartStack"] = "RestartStack";
|
||||
Operation["PauseStack"] = "PauseStack";
|
||||
Operation["UnpauseStack"] = "UnpauseStack";
|
||||
Operation["StopStack"] = "StopStack";
|
||||
Operation["DestroyStack"] = "DestroyStack";
|
||||
Operation["StartStackService"] = "StartStackService";
|
||||
Operation["RestartStackService"] = "RestartStackService";
|
||||
Operation["PauseStackService"] = "PauseStackService";
|
||||
Operation["UnpauseStackService"] = "UnpauseStackService";
|
||||
Operation["StopStackService"] = "StopStackService";
|
||||
Operation["CreateDeployment"] = "CreateDeployment";
|
||||
Operation["UpdateDeployment"] = "UpdateDeployment";
|
||||
Operation["DeleteDeployment"] = "DeleteDeployment";
|
||||
Operation["Deploy"] = "Deploy";
|
||||
Operation["StartDeployment"] = "StartDeployment";
|
||||
Operation["RestartDeployment"] = "RestartDeployment";
|
||||
Operation["PauseDeployment"] = "PauseDeployment";
|
||||
Operation["UnpauseDeployment"] = "UnpauseDeployment";
|
||||
Operation["StopDeployment"] = "StopDeployment";
|
||||
Operation["DestroyDeployment"] = "DestroyDeployment";
|
||||
Operation["RenameDeployment"] = "RenameDeployment";
|
||||
Operation["CreateBuild"] = "CreateBuild";
|
||||
Operation["UpdateBuild"] = "UpdateBuild";
|
||||
Operation["DeleteBuild"] = "DeleteBuild";
|
||||
Operation["RunBuild"] = "RunBuild";
|
||||
Operation["CancelBuild"] = "CancelBuild";
|
||||
Operation["CreateRepo"] = "CreateRepo";
|
||||
Operation["UpdateRepo"] = "UpdateRepo";
|
||||
Operation["DeleteRepo"] = "DeleteRepo";
|
||||
Operation["CloneRepo"] = "CloneRepo";
|
||||
Operation["PullRepo"] = "PullRepo";
|
||||
Operation["BuildRepo"] = "BuildRepo";
|
||||
Operation["CancelRepoBuild"] = "CancelRepoBuild";
|
||||
Operation["CreateProcedure"] = "CreateProcedure";
|
||||
Operation["UpdateProcedure"] = "UpdateProcedure";
|
||||
Operation["DeleteProcedure"] = "DeleteProcedure";
|
||||
Operation["RunProcedure"] = "RunProcedure";
|
||||
Operation["CreateAction"] = "CreateAction";
|
||||
Operation["UpdateAction"] = "UpdateAction";
|
||||
Operation["DeleteAction"] = "DeleteAction";
|
||||
Operation["RunAction"] = "RunAction";
|
||||
Operation["CreateBuilder"] = "CreateBuilder";
|
||||
Operation["UpdateBuilder"] = "UpdateBuilder";
|
||||
Operation["DeleteBuilder"] = "DeleteBuilder";
|
||||
Operation["CreateAlerter"] = "CreateAlerter";
|
||||
Operation["UpdateAlerter"] = "UpdateAlerter";
|
||||
Operation["DeleteAlerter"] = "DeleteAlerter";
|
||||
Operation["CreateServerTemplate"] = "CreateServerTemplate";
|
||||
Operation["UpdateServerTemplate"] = "UpdateServerTemplate";
|
||||
Operation["DeleteServerTemplate"] = "DeleteServerTemplate";
|
||||
Operation["LaunchServer"] = "LaunchServer";
|
||||
Operation["CreateResourceSync"] = "CreateResourceSync";
|
||||
Operation["UpdateResourceSync"] = "UpdateResourceSync";
|
||||
Operation["DeleteResourceSync"] = "DeleteResourceSync";
|
||||
Operation["WriteSyncContents"] = "WriteSyncContents";
|
||||
Operation["CommitSync"] = "CommitSync";
|
||||
Operation["RunSync"] = "RunSync";
|
||||
Operation["CreateVariable"] = "CreateVariable";
|
||||
Operation["UpdateVariableValue"] = "UpdateVariableValue";
|
||||
Operation["DeleteVariable"] = "DeleteVariable";
|
||||
Operation["CreateGitProviderAccount"] = "CreateGitProviderAccount";
|
||||
Operation["UpdateGitProviderAccount"] = "UpdateGitProviderAccount";
|
||||
Operation["DeleteGitProviderAccount"] = "DeleteGitProviderAccount";
|
||||
Operation["CreateDockerRegistryAccount"] = "CreateDockerRegistryAccount";
|
||||
Operation["UpdateDockerRegistryAccount"] = "UpdateDockerRegistryAccount";
|
||||
Operation["DeleteDockerRegistryAccount"] = "DeleteDockerRegistryAccount";
|
||||
})(Operation || (Operation = {}));
|
||||
/** An update's status */
|
||||
export var UpdateStatus;
|
||||
(function (UpdateStatus) {
|
||||
/** The run is in the system but hasn't started yet */
|
||||
UpdateStatus["Queued"] = "Queued";
|
||||
/** The run is currently running */
|
||||
UpdateStatus["InProgress"] = "InProgress";
|
||||
/** The run is complete */
|
||||
UpdateStatus["Complete"] = "Complete";
|
||||
})(UpdateStatus || (UpdateStatus = {}));
|
||||
export var ContainerStateStatusEnum;
|
||||
(function (ContainerStateStatusEnum) {
|
||||
ContainerStateStatusEnum["Empty"] = "";
|
||||
ContainerStateStatusEnum["Created"] = "created";
|
||||
ContainerStateStatusEnum["Running"] = "running";
|
||||
ContainerStateStatusEnum["Paused"] = "paused";
|
||||
ContainerStateStatusEnum["Restarting"] = "restarting";
|
||||
ContainerStateStatusEnum["Removing"] = "removing";
|
||||
ContainerStateStatusEnum["Exited"] = "exited";
|
||||
ContainerStateStatusEnum["Dead"] = "dead";
|
||||
})(ContainerStateStatusEnum || (ContainerStateStatusEnum = {}));
|
||||
export var HealthStatusEnum;
|
||||
(function (HealthStatusEnum) {
|
||||
HealthStatusEnum["Empty"] = "";
|
||||
HealthStatusEnum["None"] = "none";
|
||||
HealthStatusEnum["Starting"] = "starting";
|
||||
HealthStatusEnum["Healthy"] = "healthy";
|
||||
HealthStatusEnum["Unhealthy"] = "unhealthy";
|
||||
})(HealthStatusEnum || (HealthStatusEnum = {}));
|
||||
export var RestartPolicyNameEnum;
|
||||
(function (RestartPolicyNameEnum) {
|
||||
RestartPolicyNameEnum["Empty"] = "";
|
||||
RestartPolicyNameEnum["No"] = "no";
|
||||
RestartPolicyNameEnum["Always"] = "always";
|
||||
RestartPolicyNameEnum["UnlessStopped"] = "unless-stopped";
|
||||
RestartPolicyNameEnum["OnFailure"] = "on-failure";
|
||||
})(RestartPolicyNameEnum || (RestartPolicyNameEnum = {}));
|
||||
export var MountTypeEnum;
|
||||
(function (MountTypeEnum) {
|
||||
MountTypeEnum["Empty"] = "";
|
||||
MountTypeEnum["Bind"] = "bind";
|
||||
MountTypeEnum["Volume"] = "volume";
|
||||
MountTypeEnum["Tmpfs"] = "tmpfs";
|
||||
MountTypeEnum["Npipe"] = "npipe";
|
||||
MountTypeEnum["Cluster"] = "cluster";
|
||||
})(MountTypeEnum || (MountTypeEnum = {}));
|
||||
export var MountBindOptionsPropagationEnum;
|
||||
(function (MountBindOptionsPropagationEnum) {
|
||||
MountBindOptionsPropagationEnum["Empty"] = "";
|
||||
MountBindOptionsPropagationEnum["Private"] = "private";
|
||||
MountBindOptionsPropagationEnum["Rprivate"] = "rprivate";
|
||||
MountBindOptionsPropagationEnum["Shared"] = "shared";
|
||||
MountBindOptionsPropagationEnum["Rshared"] = "rshared";
|
||||
MountBindOptionsPropagationEnum["Slave"] = "slave";
|
||||
MountBindOptionsPropagationEnum["Rslave"] = "rslave";
|
||||
})(MountBindOptionsPropagationEnum || (MountBindOptionsPropagationEnum = {}));
|
||||
export var HostConfigCgroupnsModeEnum;
|
||||
(function (HostConfigCgroupnsModeEnum) {
|
||||
HostConfigCgroupnsModeEnum["Empty"] = "";
|
||||
HostConfigCgroupnsModeEnum["Private"] = "private";
|
||||
HostConfigCgroupnsModeEnum["Host"] = "host";
|
||||
})(HostConfigCgroupnsModeEnum || (HostConfigCgroupnsModeEnum = {}));
|
||||
export var HostConfigIsolationEnum;
|
||||
(function (HostConfigIsolationEnum) {
|
||||
HostConfigIsolationEnum["Empty"] = "";
|
||||
HostConfigIsolationEnum["Default"] = "default";
|
||||
HostConfigIsolationEnum["Process"] = "process";
|
||||
HostConfigIsolationEnum["Hyperv"] = "hyperv";
|
||||
})(HostConfigIsolationEnum || (HostConfigIsolationEnum = {}));
|
||||
export var VolumeScopeEnum;
|
||||
(function (VolumeScopeEnum) {
|
||||
VolumeScopeEnum["Empty"] = "";
|
||||
VolumeScopeEnum["Local"] = "local";
|
||||
VolumeScopeEnum["Global"] = "global";
|
||||
})(VolumeScopeEnum || (VolumeScopeEnum = {}));
|
||||
export var ClusterVolumeSpecAccessModeScopeEnum;
|
||||
(function (ClusterVolumeSpecAccessModeScopeEnum) {
|
||||
ClusterVolumeSpecAccessModeScopeEnum["Empty"] = "";
|
||||
ClusterVolumeSpecAccessModeScopeEnum["Single"] = "single";
|
||||
ClusterVolumeSpecAccessModeScopeEnum["Multi"] = "multi";
|
||||
})(ClusterVolumeSpecAccessModeScopeEnum || (ClusterVolumeSpecAccessModeScopeEnum = {}));
|
||||
export var ClusterVolumeSpecAccessModeSharingEnum;
|
||||
(function (ClusterVolumeSpecAccessModeSharingEnum) {
|
||||
ClusterVolumeSpecAccessModeSharingEnum["Empty"] = "";
|
||||
ClusterVolumeSpecAccessModeSharingEnum["None"] = "none";
|
||||
ClusterVolumeSpecAccessModeSharingEnum["Readonly"] = "readonly";
|
||||
ClusterVolumeSpecAccessModeSharingEnum["Onewriter"] = "onewriter";
|
||||
ClusterVolumeSpecAccessModeSharingEnum["All"] = "all";
|
||||
})(ClusterVolumeSpecAccessModeSharingEnum || (ClusterVolumeSpecAccessModeSharingEnum = {}));
|
||||
export var ClusterVolumeSpecAccessModeAvailabilityEnum;
|
||||
(function (ClusterVolumeSpecAccessModeAvailabilityEnum) {
|
||||
ClusterVolumeSpecAccessModeAvailabilityEnum["Empty"] = "";
|
||||
ClusterVolumeSpecAccessModeAvailabilityEnum["Active"] = "active";
|
||||
ClusterVolumeSpecAccessModeAvailabilityEnum["Pause"] = "pause";
|
||||
ClusterVolumeSpecAccessModeAvailabilityEnum["Drain"] = "drain";
|
||||
})(ClusterVolumeSpecAccessModeAvailabilityEnum || (ClusterVolumeSpecAccessModeAvailabilityEnum = {}));
|
||||
export var ClusterVolumePublishStatusStateEnum;
|
||||
(function (ClusterVolumePublishStatusStateEnum) {
|
||||
ClusterVolumePublishStatusStateEnum["Empty"] = "";
|
||||
ClusterVolumePublishStatusStateEnum["PendingPublish"] = "pending-publish";
|
||||
ClusterVolumePublishStatusStateEnum["Published"] = "published";
|
||||
ClusterVolumePublishStatusStateEnum["PendingNodeUnpublish"] = "pending-node-unpublish";
|
||||
ClusterVolumePublishStatusStateEnum["PendingControllerUnpublish"] = "pending-controller-unpublish";
|
||||
})(ClusterVolumePublishStatusStateEnum || (ClusterVolumePublishStatusStateEnum = {}));
|
||||
export var ProcedureState;
|
||||
(function (ProcedureState) {
|
||||
/** Last run successful */
|
||||
ProcedureState["Ok"] = "Ok";
|
||||
/** Last run failed */
|
||||
ProcedureState["Failed"] = "Failed";
|
||||
/** Currently running */
|
||||
ProcedureState["Running"] = "Running";
|
||||
/** Other case (never run) */
|
||||
ProcedureState["Unknown"] = "Unknown";
|
||||
})(ProcedureState || (ProcedureState = {}));
|
||||
export var RepoState;
|
||||
(function (RepoState) {
|
||||
/** Unknown case */
|
||||
RepoState["Unknown"] = "Unknown";
|
||||
/** Last clone / pull successful (or never cloned) */
|
||||
RepoState["Ok"] = "Ok";
|
||||
/** Last clone / pull failed */
|
||||
RepoState["Failed"] = "Failed";
|
||||
/** Currently cloning */
|
||||
RepoState["Cloning"] = "Cloning";
|
||||
/** Currently pulling */
|
||||
RepoState["Pulling"] = "Pulling";
|
||||
/** Currently building */
|
||||
RepoState["Building"] = "Building";
|
||||
})(RepoState || (RepoState = {}));
|
||||
export var ResourceSyncState;
|
||||
(function (ResourceSyncState) {
|
||||
/** Last sync successful (or never synced). No Changes pending */
|
||||
ResourceSyncState["Ok"] = "Ok";
|
||||
/** Last sync failed */
|
||||
ResourceSyncState["Failed"] = "Failed";
|
||||
/** Currently syncing */
|
||||
ResourceSyncState["Syncing"] = "Syncing";
|
||||
/** Updates pending */
|
||||
ResourceSyncState["Pending"] = "Pending";
|
||||
/** Other case */
|
||||
ResourceSyncState["Unknown"] = "Unknown";
|
||||
})(ResourceSyncState || (ResourceSyncState = {}));
|
||||
export var ServerState;
|
||||
(function (ServerState) {
|
||||
/** Server is unreachable. */
|
||||
ServerState["NotOk"] = "NotOk";
|
||||
/** Server health check passing. */
|
||||
ServerState["Ok"] = "Ok";
|
||||
/** Server is disabled. */
|
||||
ServerState["Disabled"] = "Disabled";
|
||||
})(ServerState || (ServerState = {}));
|
||||
export var StackState;
|
||||
(function (StackState) {
|
||||
/** All containers are running. */
|
||||
StackState["Running"] = "running";
|
||||
/** All containers are paused */
|
||||
StackState["Paused"] = "paused";
|
||||
/** All contianers are stopped */
|
||||
StackState["Stopped"] = "stopped";
|
||||
/** All containers are created */
|
||||
StackState["Created"] = "created";
|
||||
/** All containers are restarting */
|
||||
StackState["Restarting"] = "restarting";
|
||||
/** All containers are dead */
|
||||
StackState["Dead"] = "dead";
|
||||
/** All containers are removing */
|
||||
StackState["Removing"] = "removing";
|
||||
/** The containers are in a mix of states */
|
||||
StackState["Unhealthy"] = "unhealthy";
|
||||
/** The stack is not deployed */
|
||||
StackState["Down"] = "down";
|
||||
/** Server not reachable */
|
||||
StackState["Unknown"] = "unknown";
|
||||
})(StackState || (StackState = {}));
|
||||
export var AwsVolumeType;
|
||||
(function (AwsVolumeType) {
|
||||
AwsVolumeType["Gp2"] = "gp2";
|
||||
AwsVolumeType["Gp3"] = "gp3";
|
||||
AwsVolumeType["Io1"] = "io1";
|
||||
AwsVolumeType["Io2"] = "io2";
|
||||
})(AwsVolumeType || (AwsVolumeType = {}));
|
||||
export var RepoWebhookAction;
|
||||
(function (RepoWebhookAction) {
|
||||
RepoWebhookAction["Clone"] = "Clone";
|
||||
RepoWebhookAction["Pull"] = "Pull";
|
||||
RepoWebhookAction["Build"] = "Build";
|
||||
})(RepoWebhookAction || (RepoWebhookAction = {}));
|
||||
export var StackWebhookAction;
|
||||
(function (StackWebhookAction) {
|
||||
StackWebhookAction["Refresh"] = "Refresh";
|
||||
StackWebhookAction["Deploy"] = "Deploy";
|
||||
})(StackWebhookAction || (StackWebhookAction = {}));
|
||||
export var SyncWebhookAction;
|
||||
(function (SyncWebhookAction) {
|
||||
SyncWebhookAction["Refresh"] = "Refresh";
|
||||
SyncWebhookAction["Sync"] = "Sync";
|
||||
})(SyncWebhookAction || (SyncWebhookAction = {}));
|
||||
export var HetznerDatacenter;
|
||||
(function (HetznerDatacenter) {
|
||||
HetznerDatacenter["Nuremberg1Dc3"] = "Nuremberg1Dc3";
|
||||
HetznerDatacenter["Helsinki1Dc2"] = "Helsinki1Dc2";
|
||||
HetznerDatacenter["Falkenstein1Dc14"] = "Falkenstein1Dc14";
|
||||
HetznerDatacenter["AshburnDc1"] = "AshburnDc1";
|
||||
HetznerDatacenter["HillsboroDc1"] = "HillsboroDc1";
|
||||
HetznerDatacenter["SingaporeDc1"] = "SingaporeDc1";
|
||||
})(HetznerDatacenter || (HetznerDatacenter = {}));
|
||||
export var HetznerServerType;
|
||||
(function (HetznerServerType) {
|
||||
/** CPX11 - AMD 2 Cores, 2 Gb Ram, 40 Gb disk */
|
||||
HetznerServerType["SharedAmd2Core2Ram40Disk"] = "SharedAmd2Core2Ram40Disk";
|
||||
/** CAX11 - Arm 2 Cores, 4 Gb Ram, 40 Gb disk */
|
||||
HetznerServerType["SharedArm2Core4Ram40Disk"] = "SharedArm2Core4Ram40Disk";
|
||||
/** CX22 - Intel 2 Cores, 4 Gb Ram, 40 Gb disk */
|
||||
HetznerServerType["SharedIntel2Core4Ram40Disk"] = "SharedIntel2Core4Ram40Disk";
|
||||
/** CPX21 - AMD 3 Cores, 4 Gb Ram, 80 Gb disk */
|
||||
HetznerServerType["SharedAmd3Core4Ram80Disk"] = "SharedAmd3Core4Ram80Disk";
|
||||
/** CAX21 - Arm 4 Cores, 8 Gb Ram, 80 Gb disk */
|
||||
HetznerServerType["SharedArm4Core8Ram80Disk"] = "SharedArm4Core8Ram80Disk";
|
||||
/** CX32 - Intel 4 Cores, 8 Gb Ram, 80 Gb disk */
|
||||
HetznerServerType["SharedIntel4Core8Ram80Disk"] = "SharedIntel4Core8Ram80Disk";
|
||||
/** CPX31 - AMD 4 Cores, 8 Gb Ram, 160 Gb disk */
|
||||
HetznerServerType["SharedAmd4Core8Ram160Disk"] = "SharedAmd4Core8Ram160Disk";
|
||||
/** CAX31 - Arm 8 Cores, 16 Gb Ram, 160 Gb disk */
|
||||
HetznerServerType["SharedArm8Core16Ram160Disk"] = "SharedArm8Core16Ram160Disk";
|
||||
/** CX42 - Intel 8 Cores, 16 Gb Ram, 160 Gb disk */
|
||||
HetznerServerType["SharedIntel8Core16Ram160Disk"] = "SharedIntel8Core16Ram160Disk";
|
||||
/** CPX41 - AMD 8 Cores, 16 Gb Ram, 240 Gb disk */
|
||||
HetznerServerType["SharedAmd8Core16Ram240Disk"] = "SharedAmd8Core16Ram240Disk";
|
||||
/** CAX41 - Arm 16 Cores, 32 Gb Ram, 320 Gb disk */
|
||||
HetznerServerType["SharedArm16Core32Ram320Disk"] = "SharedArm16Core32Ram320Disk";
|
||||
/** CX52 - Intel 16 Cores, 32 Gb Ram, 320 Gb disk */
|
||||
HetznerServerType["SharedIntel16Core32Ram320Disk"] = "SharedIntel16Core32Ram320Disk";
|
||||
/** CPX51 - AMD 16 Cores, 32 Gb Ram, 360 Gb disk */
|
||||
HetznerServerType["SharedAmd16Core32Ram360Disk"] = "SharedAmd16Core32Ram360Disk";
|
||||
/** CCX13 - AMD 2 Cores, 8 Gb Ram, 80 Gb disk */
|
||||
HetznerServerType["DedicatedAmd2Core8Ram80Disk"] = "DedicatedAmd2Core8Ram80Disk";
|
||||
/** CCX23 - AMD 4 Cores, 16 Gb Ram, 160 Gb disk */
|
||||
HetznerServerType["DedicatedAmd4Core16Ram160Disk"] = "DedicatedAmd4Core16Ram160Disk";
|
||||
/** CCX33 - AMD 8 Cores, 32 Gb Ram, 240 Gb disk */
|
||||
HetznerServerType["DedicatedAmd8Core32Ram240Disk"] = "DedicatedAmd8Core32Ram240Disk";
|
||||
/** CCX43 - AMD 16 Cores, 64 Gb Ram, 360 Gb disk */
|
||||
HetznerServerType["DedicatedAmd16Core64Ram360Disk"] = "DedicatedAmd16Core64Ram360Disk";
|
||||
/** CCX53 - AMD 32 Cores, 128 Gb Ram, 600 Gb disk */
|
||||
HetznerServerType["DedicatedAmd32Core128Ram600Disk"] = "DedicatedAmd32Core128Ram600Disk";
|
||||
/** CCX63 - AMD 48 Cores, 192 Gb Ram, 960 Gb disk */
|
||||
HetznerServerType["DedicatedAmd48Core192Ram960Disk"] = "DedicatedAmd48Core192Ram960Disk";
|
||||
})(HetznerServerType || (HetznerServerType = {}));
|
||||
export var HetznerVolumeFormat;
|
||||
(function (HetznerVolumeFormat) {
|
||||
HetznerVolumeFormat["Xfs"] = "Xfs";
|
||||
HetznerVolumeFormat["Ext4"] = "Ext4";
|
||||
})(HetznerVolumeFormat || (HetznerVolumeFormat = {}));
|
||||
export var PortTypeEnum;
|
||||
(function (PortTypeEnum) {
|
||||
PortTypeEnum["EMPTY"] = "";
|
||||
PortTypeEnum["TCP"] = "tcp";
|
||||
PortTypeEnum["UDP"] = "udp";
|
||||
PortTypeEnum["SCTP"] = "sctp";
|
||||
})(PortTypeEnum || (PortTypeEnum = {}));
|
||||
export var SearchCombinator;
|
||||
(function (SearchCombinator) {
|
||||
SearchCombinator["Or"] = "Or";
|
||||
SearchCombinator["And"] = "And";
|
||||
})(SearchCombinator || (SearchCombinator = {}));
|
||||
8
frontend/public/index.d.ts
vendored
Normal file
8
frontend/public/index.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { KomodoClient, Types as KomodoTypes } from "./client/lib.js";
|
||||
|
||||
declare global {
|
||||
var komodo: ReturnType<typeof KomodoClient>;
|
||||
export import Types = KomodoTypes;
|
||||
}
|
||||
|
||||
export {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user