#[cfg(test)]
mod tests;
use super::EnsureOptions;
use canic_host::fleet_ensure::model::{FleetEnsurePlan, FleetEnsurePlanScope, FleetEnsureReport};
use serde::Serialize;
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum Phase {
Infrastructure,
Import,
Fleet,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum ActionKind {
Apply,
Resume,
Review,
ReviewFunding,
}
#[derive(Serialize)]
pub(super) struct NextAction {
pub kind: ActionKind,
pub requires_approval: bool,
pub executable: Option<&'static str>,
pub arguments: Vec<String>,
}
#[derive(Serialize)]
pub(super) struct OperationResult {
pub phase: Phase,
pub operation_id: Option<String>,
pub plan_sha256: Option<String>,
pub review_sha256: Option<String>,
pub phase_completed: bool,
pub fleet_completed: bool,
pub next_action: Option<NextAction>,
}
pub(super) fn command(
environment: &str,
icp: &str,
identity: Option<&str>,
subcommand: &str,
fleet: &str,
) -> Vec<String> {
let mut args = vec![
"--environment".into(),
environment.into(),
"--icp".into(),
icp.into(),
"fleet".into(),
subcommand.into(),
fleet.into(),
"--json".into(),
];
if let Some(identity) = identity {
args.extend(["--identity".into(), identity.into()]);
}
args
}
pub(super) fn action(kind: ActionKind, arguments: Vec<String>) -> NextAction {
NextAction {
kind,
requires_approval: matches!(kind, ActionKind::Apply | ActionKind::ReviewFunding),
executable: (!arguments.is_empty()).then_some("canic"),
arguments,
}
}
pub(super) fn ensure_command(options: &EnsureOptions, environment: &str) -> Vec<String> {
let mut args = command(
environment,
&options.icp,
options.identity.as_deref(),
"ensure",
&options.fleet,
);
args.extend([
"--desired".into(),
options.desired.to_string_lossy().into_owned(),
"--source".into(),
options.source.to_string_lossy().into_owned(),
"--seed".into(),
options.seed.to_string_lossy().into_owned(),
]);
args
}
pub(super) fn reinstall_review_command(options: &EnsureOptions, environment: &str) -> Vec<String> {
let mut args = ensure_command(options, environment);
args.push("--reinstall".into());
args
}
pub(super) fn ensure(
report: &FleetEnsureReport,
options: &EnsureOptions,
reinstall: bool,
) -> OperationResult {
let infrastructure = report.plan.scope == FleetEnsurePlanScope::InfrastructureBootstrap;
let fleet_completed = report.terminal && report.plan.scope == FleetEnsurePlanScope::Full;
let mut args = ensure_command(options, &report.plan.environment);
let next_action = if fleet_completed {
None
} else if report.funding_review.is_some() {
Some(action(ActionKind::ReviewFunding, Vec::new()))
} else if report.terminal {
if reinstall {
args = reinstall_review_command(options, &report.plan.environment);
}
Some(action(ActionKind::Review, args))
} else {
args.extend(["--apply".into(), report.plan.plan_sha256.clone()]);
let kind = if options.apply.as_deref() == Some(&report.plan.plan_sha256) {
ActionKind::Resume
} else {
ActionKind::Apply
};
Some(action(kind, args))
};
OperationResult {
phase: if infrastructure {
Phase::Infrastructure
} else {
Phase::Fleet
},
operation_id: Some(report.plan.operation_id.clone()),
plan_sha256: Some(report.plan.plan_sha256.clone()),
review_sha256: None,
phase_completed: report.terminal,
fleet_completed,
next_action,
}
}
pub(super) fn bootstrap(
plan: &FleetEnsurePlan,
completed: bool,
review_sha256: Option<String>,
applying: bool,
arguments: Vec<String>,
) -> OperationResult {
OperationResult {
phase: Phase::Infrastructure,
operation_id: Some(plan.operation_id.clone()),
plan_sha256: Some(plan.plan_sha256.clone()),
review_sha256,
phase_completed: completed,
fleet_completed: false,
next_action: (!completed).then(|| {
action(
if applying {
ActionKind::Resume
} else {
ActionKind::Apply
},
arguments,
)
}),
}
}
pub(super) fn import(
digest: String,
completed: bool,
applying: bool,
mut arguments: Vec<String>,
reinstall: bool,
) -> OperationResult {
let next_action = if completed {
if reinstall {
Some(action(ActionKind::Review, arguments))
} else {
None
}
} else {
arguments.extend(["--apply".into(), digest.clone()]);
Some(action(
if applying {
ActionKind::Resume
} else {
ActionKind::Apply
},
arguments,
))
};
OperationResult {
phase: Phase::Import,
operation_id: None,
plan_sha256: None,
review_sha256: Some(digest),
phase_completed: completed,
fleet_completed: false,
next_action,
}
}