use myko_macros::myko_command;
use myko::command::{CommandContext, CommandError, CommandHandler};
use serde_json::Value;
use crate::{
derive_resources, GetAllInventorys, GetPinsByIds, GetPlaybooksByIds, GetRunsByIds, OutputLine,
OutputLineId, OutputStream, Pin, PinId, PlaybookId, Run, RunId, RunStatus,
};
#[myko_command]
pub struct RunPlaybook {
pub playbook_id: PlaybookId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_vars: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<String>,
}
impl CommandHandler for RunPlaybook {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
ctx.exec_query_first(GetPlaybooksByIds {
ids: vec![self.playbook_id.clone()],
})?
.ok_or_else(|| CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: format!("Playbook {} not found", self.playbook_id.0),
})?;
let resources = match ctx.exec_query(GetAllInventorys {})?.into_iter().next() {
Some(inv) => derive_resources(self.limit.as_deref(), &inv),
None => Vec::new(),
};
let run_id = RunId::from(uuid::Uuid::new_v4().to_string());
let now = chrono::Utc::now().to_rfc3339();
let run = Run {
playbook_id: self.playbook_id,
status: RunStatus::Queued,
started_at: now,
finished_at: None,
exit_code: None,
extra_vars: self.extra_vars,
limit: self.limit,
resources,
host_stats: Default::default(),
client_id: None, id: run_id,
};
ctx.emit_set(&run)?;
Ok(())
}
}
#[myko_command]
pub struct CancelRun {
pub run_id: RunId,
}
impl CommandHandler for CancelRun {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
let current = ctx
.exec_query_first(GetRunsByIds {
ids: vec![self.run_id.clone()],
})?
.ok_or_else(|| CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: format!("Run {} not found", self.run_id.0),
})?;
if !matches!(current.status, RunStatus::Queued | RunStatus::Running) {
return Ok(());
}
let mut updated = (*current).clone();
updated.status = RunStatus::Cancelled;
ctx.emit_set(&updated)?;
Ok(())
}
}
#[myko_command]
pub struct PinPlaybook {
pub playbook_id: PlaybookId,
}
impl CommandHandler for PinPlaybook {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
ctx.exec_query_first(GetPlaybooksByIds {
ids: vec![self.playbook_id.clone()],
})?
.ok_or_else(|| CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: format!("Playbook {} not found", self.playbook_id.0),
})?;
let pin_id = PinId::from(self.playbook_id.0.clone());
if let Some(existing) = ctx.exec_query_first(GetPinsByIds {
ids: vec![pin_id.clone()],
})? {
if existing.locked {
return Ok(());
}
}
let pin = Pin {
playbook_id: self.playbook_id,
locked: false,
id: pin_id,
};
ctx.emit_set(&pin)?;
Ok(())
}
}
#[myko_command]
pub struct ReportRunOutput {
pub run_id: RunId,
pub stream: OutputStream,
pub line: String,
pub seq: u64,
}
impl CommandHandler for ReportRunOutput {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
let entity = OutputLine {
id: OutputLineId::from(format!("{}:{}", self.run_id.0, self.seq)),
run_id: self.run_id,
stream: self.stream,
line: self.line,
seq: self.seq,
};
ctx.emit_set(&entity)?;
Ok(())
}
}
#[myko_command]
pub struct ReportRunFinished {
pub run_id: RunId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exit_code: Option<i32>,
}
impl CommandHandler for ReportRunFinished {
fn execute(self, ctx: CommandContext) -> Result<(), CommandError> {
let current = ctx
.exec_query_first(GetRunsByIds {
ids: vec![self.run_id.clone()],
})?
.ok_or_else(|| CommandError {
tx: ctx.tx().to_string(),
command_id: ctx.command_id.to_string(),
message: format!("Run {} not found", self.run_id.0),
})?;
let mut updated = (*current).clone();
if updated.status != RunStatus::Cancelled {
updated.status = if self.exit_code == Some(0) {
RunStatus::Completed
} else {
RunStatus::Failed
};
}
updated.exit_code = self.exit_code;
updated.finished_at = Some(chrono::Utc::now().to_rfc3339());
ctx.emit_set(&updated)?;
Ok(())
}
}