implement update for most resources

This commit is contained in:
mbecker20
2025-08-07 01:03:25 -04:00
parent 0629744356
commit 25e957ef0c
9 changed files with 364 additions and 49 deletions

3
Cargo.lock generated
View File

@@ -2594,6 +2594,9 @@ dependencies = [
"futures-util",
"komodo_client",
"logger",
"serde",
"serde_json",
"serde_qs",
"tokio",
"tracing",
]

View File

@@ -21,10 +21,13 @@ config.workspace = true
logger.workspace = true
# external
futures-util.workspace = true
serde_json.workspace = true
serde_qs.workspace = true
tracing.workspace = true
colored.workspace = true
dotenvy.workspace = true
anyhow.workspace = true
tokio.workspace = true
serde.workspace = true
clap.workspace = true
envy.workspace = true

View File

@@ -1,10 +1,21 @@
use anyhow::Context;
use colored::Colorize;
use komodo_client::entities::optional_string;
use komodo_client::entities::{
config::cli::DatabaseCommand, optional_string,
};
use crate::config::cli_config;
pub async fn backup(yes: bool) -> anyhow::Result<()> {
pub async fn handle(command: &DatabaseCommand) -> anyhow::Result<()> {
match command {
DatabaseCommand::Backup { yes, .. } => backup(*yes).await,
DatabaseCommand::Restore { yes, .. } => restore(*yes).await,
DatabaseCommand::Prune { yes, .. } => prune(*yes).await,
DatabaseCommand::Copy { yes, .. } => copy(*yes).await,
}
}
async fn backup(yes: bool) -> anyhow::Result<()> {
let config = cli_config();
println!(
@@ -63,7 +74,7 @@ pub async fn backup(yes: bool) -> anyhow::Result<()> {
prune_inner().await
}
pub async fn restore(yes: bool) -> anyhow::Result<()> {
async fn restore(yes: bool) -> anyhow::Result<()> {
let config = cli_config();
println!(
@@ -116,7 +127,7 @@ pub async fn restore(yes: bool) -> anyhow::Result<()> {
.await
}
pub async fn prune(yes: bool) -> anyhow::Result<()> {
async fn prune(yes: bool) -> anyhow::Result<()> {
let config = cli_config();
println!(
@@ -218,7 +229,7 @@ async fn prune_inner() -> anyhow::Result<()> {
Ok(())
}
pub async fn copy(yes: bool) -> anyhow::Result<()> {
async fn copy(yes: bool) -> anyhow::Result<()> {
let config = cli_config();
println!(

View File

@@ -16,8 +16,8 @@ enum ExecutionResult {
Batch(BatchExecutionResponse),
}
pub async fn execute(
execution: Execution,
pub async fn handle(
execution: &Execution,
yes: bool,
) -> anyhow::Result<()> {
if matches!(execution, Execution::None(_)) {
@@ -28,7 +28,7 @@ pub async fn execute(
}
println!("\n{}: Execution", "Mode".dimmed());
match &execution {
match execution {
Execution::None(data) => {
println!("{}: {data:?}", "Data".dimmed())
}
@@ -229,7 +229,7 @@ pub async fn execute(
let client = super::komodo_client().await?;
let res = match execution {
let res = match execution.clone() {
Execution::RunAction(request) => client
.execute(request)
.await

View File

@@ -8,12 +8,9 @@ use tokio::sync::OnceCell;
use crate::config::cli_config;
pub mod database;
pub mod execute;
pub mod update;
mod execute;
pub use execute::execute;
async fn komodo_client() -> anyhow::Result<&'static KomodoClient> {
static KOMODO_CLIENT: OnceCell<KomodoClient> =
OnceCell::const_new();

View File

@@ -1,13 +1,66 @@
use anyhow::Context;
use colored::Colorize;
use komodo_client::api::{
read::GetVariable,
write::{
CreateVariable, UpdateVariableIsSecret, UpdateVariableValue,
use komodo_client::{
api::{
read::GetVariable,
write::{
CreateVariable, UpdateBuild, UpdateDeployment, UpdateRepo,
UpdateResourceSync, UpdateServer, UpdateStack,
UpdateVariableIsSecret, UpdateVariableValue,
},
},
entities::{
build::PartialBuildConfig, config::cli::UpdateCommand,
deployment::PartialDeploymentConfig, repo::PartialRepoConfig,
server::PartialServerConfig, stack::PartialStackConfig,
sync::PartialResourceSyncConfig,
},
};
use serde::{Serialize, de::DeserializeOwned};
pub async fn variable(
pub async fn handle(command: &UpdateCommand) -> anyhow::Result<()> {
match command {
UpdateCommand::Variable {
name,
value,
secret,
yes,
} => update_variable(name, value, *secret, *yes).await,
UpdateCommand::Build { build, update, yes } => {
update_resource::<PartialBuildConfig>(build, update, *yes).await
}
UpdateCommand::Deployment {
deployment,
update,
yes,
} => {
update_resource::<PartialDeploymentConfig>(
deployment, update, *yes,
)
.await
}
UpdateCommand::Repo { repo, update, yes } => {
update_resource::<PartialRepoConfig>(repo, update, *yes).await
}
UpdateCommand::Server {
server,
update,
yes,
} => {
update_resource::<PartialServerConfig>(server, update, *yes)
.await
}
UpdateCommand::Stack { stack, update, yes } => {
update_resource::<PartialStackConfig>(stack, update, *yes).await
}
UpdateCommand::Sync { sync, update, yes } => {
update_resource::<PartialResourceSyncConfig>(sync, update, *yes)
.await
}
}
}
async fn update_variable(
name: &str,
value: &str,
secret: Option<bool>,
@@ -68,3 +121,137 @@ pub async fn variable(
Ok(())
}
async fn update_resource<
T: std::fmt::Debug + Serialize + DeserializeOwned + ResourceUpdate,
>(
resource: &str,
update: &str,
yes: bool,
) -> anyhow::Result<()> {
println!("\n{}: Update {}\n", "Mode".dimmed(), T::resource_type());
println!(" - {}: {resource}", "Name".dimmed());
let config = serde_qs::from_str::<T>(update)
.context("Failed to deserialize config")?;
match serde_json::to_string_pretty(&config) {
Ok(config) => {
println!(" - {}: {config}", "Update".dimmed());
}
Err(_) => {
println!(" - {}: {config:#?}", "Update".dimmed());
}
}
super::wait_for_enter("update resource", yes)?;
config.apply(resource).await
}
trait ResourceUpdate {
fn resource_type() -> &'static str;
async fn apply(self, resource: &str) -> anyhow::Result<()>;
}
impl ResourceUpdate for PartialBuildConfig {
fn resource_type() -> &'static str {
"Build"
}
async fn apply(self, resource: &str) -> anyhow::Result<()> {
let client = super::komodo_client().await?;
client
.write(UpdateBuild {
id: resource.to_string(),
config: self,
})
.await
.context("Failed to update build config")?;
Ok(())
}
}
impl ResourceUpdate for PartialDeploymentConfig {
fn resource_type() -> &'static str {
"Deployment"
}
async fn apply(self, resource: &str) -> anyhow::Result<()> {
let client = super::komodo_client().await?;
client
.write(UpdateDeployment {
id: resource.to_string(),
config: self,
})
.await
.context("Failed to update deployment config")?;
Ok(())
}
}
impl ResourceUpdate for PartialRepoConfig {
fn resource_type() -> &'static str {
"Repo"
}
async fn apply(self, resource: &str) -> anyhow::Result<()> {
let client = super::komodo_client().await?;
client
.write(UpdateRepo {
id: resource.to_string(),
config: self,
})
.await
.context("Failed to update repo config")?;
Ok(())
}
}
impl ResourceUpdate for PartialServerConfig {
fn resource_type() -> &'static str {
"Server"
}
async fn apply(self, resource: &str) -> anyhow::Result<()> {
let client = super::komodo_client().await?;
client
.write(UpdateServer {
id: resource.to_string(),
config: self,
})
.await
.context("Failed to update server config")?;
Ok(())
}
}
impl ResourceUpdate for PartialStackConfig {
fn resource_type() -> &'static str {
"Stack"
}
async fn apply(self, resource: &str) -> anyhow::Result<()> {
let client = super::komodo_client().await?;
client
.write(UpdateStack {
id: resource.to_string(),
config: self,
})
.await
.context("Failed to update stack config")?;
Ok(())
}
}
impl ResourceUpdate for PartialResourceSyncConfig {
fn resource_type() -> &'static str {
"Sync"
}
async fn apply(self, resource: &str) -> anyhow::Result<()> {
let client = super::komodo_client().await?;
client
.write(UpdateResourceSync {
id: resource.to_string(),
config: self,
})
.await
.context("Failed to update sync config")?;
Ok(())
}
}

View File

@@ -28,29 +28,14 @@ async fn app() -> anyhow::Result<()> {
Ok(())
}
cli::Command::Execute { execution, yes, .. } => {
command::execute(execution.clone(), *yes).await
command::execute::handle(execution, *yes).await
}
cli::Command::Update { command } => {
command::update::handle(command).await
}
cli::Command::Database { command } => {
command::database::handle(command).await
}
cli::Command::Update {
command:
cli::UpdateCommand::Variable {
name,
value,
secret,
yes,
},
} => command::update::variable(name, value, *secret, *yes).await,
cli::Command::Database {
command: cli::DatabaseCommand::Backup { yes, .. },
} => command::database::backup(*yes).await,
cli::Command::Database {
command: cli::DatabaseCommand::Restore { yes, .. },
} => command::database::restore(*yes).await,
cli::Command::Database {
command: cli::DatabaseCommand::Prune { yes, .. },
} => command::database::prune(*yes).await,
cli::Command::Database {
command: cli::DatabaseCommand::Copy { yes, .. },
} => command::database::copy(*yes).await,
}
}

View File

@@ -60,25 +60,27 @@ pub enum Execution {
None(NoData),
// ACTION
/// Run the target action. (alias: `action`)
#[clap(alias = "action")]
/// Run the target action. (alias: `action`, `act`)
#[clap(alias = "action", alias = "act")]
RunAction(RunAction),
BatchRunAction(BatchRunAction),
// PROCEDURE
/// Run the target procedure. (alias: `procedure`)
#[clap(alias = "procedure")]
/// Run the target procedure. (alias: `procedure`, `proc`)
#[clap(alias = "procedure", alias = "proc")]
RunProcedure(RunProcedure),
BatchRunProcedure(BatchRunProcedure),
// BUILD
/// Run the target build. (alias: `build`)
#[clap(alias = "build")]
/// Run the target build. (alias: `build`, `bld`)
#[clap(alias = "build", alias = "bld")]
RunBuild(RunBuild),
BatchRunBuild(BatchRunBuild),
CancelBuild(CancelBuild),
// DEPLOYMENT
/// Deploy the target deployment. (alias: `dep`)
#[clap(alias = "dep")]
Deploy(Deploy),
BatchDeploy(BatchDeploy),
PullDeployment(PullDeployment),
@@ -91,6 +93,8 @@ pub enum Execution {
BatchDestroyDeployment(BatchDestroyDeployment),
// REPO
/// Clone the target repo
#[clap(alias = "clone")]
CloneRepo(CloneRepo),
BatchCloneRepo(BatchCloneRepo),
PullRepo(PullRepo),
@@ -125,10 +129,15 @@ pub enum Execution {
PruneSystem(PruneSystem),
// SYNC
/// Execute a Resource Sync. (alias: `sync`)
#[clap(alias = "sync")]
RunSync(RunSync),
/// Commit a Resource Sync. (alias: `commit`)
#[clap(alias = "commit")]
CommitSync(CommitSync), // This is a special case, its actually a write operation.
// STACK
#[clap(alias = "stack", alias = "stk")]
DeployStack(DeployStack),
BatchDeployStack(BatchDeployStack),
DeployStackIfChanged(DeployStackIfChanged),

View File

@@ -60,8 +60,8 @@ pub enum Command {
unsanitized: bool,
},
/// Run Komodo executions. (aliases: `x`, `run`)
#[clap(alias = "x", alias = "run")]
/// Run Komodo executions. (aliases: `x`, `run`, `deploy`, `dep`)
#[clap(alias = "x", alias = "run", alias = "deploy", alias = "dep")]
Execute {
#[command(subcommand)]
execution: Execution,
@@ -80,7 +80,7 @@ pub enum Command {
yes: bool,
},
/// Update resource properties. (alias `set`)
/// Update resource configuration. (alias `set`)
#[clap(alias = "set")]
Update {
#[command(subcommand)]
@@ -112,6 +112,126 @@ pub enum UpdateCommand {
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
/// Update a Build's configuration. (alias: `bld`)
#[clap(alias = "bld")]
Build {
/// The name / id of the Build.
build: String,
/// The update string, parsed using 'https://docs.rs/serde_qs/latest/serde_qs'.
///
/// The fields can be found here: 'https://docs.rs/komodo_client/latest/komodo_client/entities/build/struct.BuildConfig.html'
///
/// Example: `km update build example-build "version=1.13.4&branch=release"`
///
/// Note. Should be enclosed in single or double quotes.
/// Values containing complex characters (like URLs)
/// will need to be url-encoded in order to be parsed correctly.
update: String,
/// Always continue on user confirmation prompts.
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
/// Update a Deployments's configuration. (alias: `dep`)
#[clap(alias = "dep")]
Deployment {
/// The name / id of the Deployment.
deployment: String,
/// The update string, parsed using 'https://docs.rs/serde_qs/latest/serde_qs'.
///
/// The fields can be found here: 'https://docs.rs/komodo_client/latest/komodo_client/entities/deployment/struct.DeploymentConfig.html'
///
/// Example: `km update deployment example-deployment "restart=unless-stopped"`
///
/// Note. Should be enclosed in single or double quotes.
/// Values containing complex characters (like URLs)
/// will need to be url-encoded in order to be parsed correctly.
update: String,
/// Always continue on user confirmation prompts.
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
/// Update a Repos's configuration.
Repo {
/// The name / id of the Repo.
repo: String,
/// The update string, parsed using 'https://docs.rs/serde_qs/latest/serde_qs'.
///
/// The fields can be found here: 'https://docs.rs/komodo_client/latest/komodo_client/entities/repo/struct.RepoConfig.html'
///
/// Example: `km update repo example-repo "branch=testing"`
///
/// Note. Should be enclosed in single or double quotes.
/// Values containing complex characters (like URLs)
/// will need to be url-encoded in order to be parsed correctly.
update: String,
/// Always continue on user confirmation prompts.
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
/// Update a Servers's configuration. (alias: `srv`)
#[clap(alias = "srv")]
Server {
/// The name / id of the Server.
server: String,
/// The update string, parsed using 'https://docs.rs/serde_qs/latest/serde_qs'.
///
/// The fields can be found here: 'https://docs.rs/komodo_client/latest/komodo_client/entities/server/struct.ServerConfig.html'
///
/// Example: `km update server example-server "enabled=true&address=https%3A%2F%2Fmy.periphery%3A8120"`
///
/// The above includes example of url encoded address `https://my.periphery:8120`.
///
/// Note. Should be enclosed in single or double quotes.
/// Values containing complex characters (like URLs)
/// will need to be url-encoded in order to be parsed correctly.
update: String,
/// Always continue on user confirmation prompts.
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
/// Update a Stacks's configuration. (alias: `stk`)
#[clap(alias = "stk")]
Stack {
/// The name / id of the Stack.
stack: String,
/// The update string, parsed using 'https://docs.rs/serde_qs/latest/serde_qs'.
///
/// The fields can be found here: 'https://docs.rs/komodo_client/latest/komodo_client/entities/stack/struct.StackConfig.html'
///
/// Example: `km update stack example-stack "branch=testing"`
///
/// Note. Should be enclosed in single or double quotes.
/// Values containing complex characters (like URLs)
/// will need to be url-encoded in order to be parsed correctly.
update: String,
/// Always continue on user confirmation prompts.
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
/// Update a Syncs's configuration.
Sync {
/// The name / id of the Sync.
sync: String,
/// The update string, parsed using 'https://docs.rs/serde_qs/latest/serde_qs'.
///
/// The fields can be found here: 'https://docs.rs/komodo_client/latest/komodo_client/entities/sync/struct.ResourceSyncConfig.html'
///
/// Example: `km update sync example-sync "branch=testing"`
///
/// Note. Should be enclosed in single or double quotes.
/// Values containing complex characters (like URLs)
/// will need to be url-encoded in order to be parsed correctly.
update: String,
/// Always continue on user confirmation prompts.
#[arg(long, short = 'y', default_value_t = false)]
yes: bool,
},
}
#[derive(Debug, Clone, Subcommand)]