Skip to main content

kanade_shared/
manifest.rs

1use serde::{Deserialize, Serialize};
2
3use crate::wire::Shell;
4
5/// YAML job manifest (spec §2.4.1, Sprint 4a covers everything except
6/// `execute.script_file` / `execute.script_object` / `on_failure`).
7#[derive(Serialize, Deserialize, Debug, Clone)]
8pub struct Manifest {
9    pub id: String,
10    pub version: String,
11    #[serde(default)]
12    pub description: Option<String>,
13    pub target: Target,
14    pub execute: Execute,
15    /// Optional wave rollout — when present, the backend publishes each
16    /// wave's group subject on its own delay schedule instead of fanning
17    /// out the `target` block at deploy time. `target` is then only used
18    /// as a fallback (e.g. `target.all: true` to mark the manifest as
19    /// fleet-wide for the audit log).
20    #[serde(default)]
21    pub rollout: Option<Rollout>,
22    #[serde(default)]
23    pub require_approval: bool,
24}
25
26#[derive(Serialize, Deserialize, Debug, Clone)]
27pub struct Rollout {
28    #[serde(default)]
29    pub strategy: RolloutStrategy,
30    pub waves: Vec<Wave>,
31}
32
33#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
34#[serde(rename_all = "lowercase")]
35pub enum RolloutStrategy {
36    #[default]
37    Wave,
38}
39
40#[derive(Serialize, Deserialize, Debug, Clone)]
41pub struct Wave {
42    pub group: String,
43    /// humantime delay measured from the deploy's publish time. wave[0]
44    /// typically has "0s"; subsequent waves use minutes / hours.
45    pub delay: String,
46}
47
48#[derive(Serialize, Deserialize, Debug, Clone, Default)]
49pub struct Target {
50    #[serde(default)]
51    pub groups: Vec<String>,
52    #[serde(default)]
53    pub pcs: Vec<String>,
54    #[serde(default)]
55    pub all: bool,
56}
57
58impl Target {
59    /// At least one of all / groups / pcs is set.
60    pub fn is_specified(&self) -> bool {
61        self.all || !self.groups.is_empty() || !self.pcs.is_empty()
62    }
63}
64
65#[derive(Serialize, Deserialize, Debug, Clone)]
66pub struct Execute {
67    pub shell: ExecuteShell,
68    pub script: String,
69    /// humantime duration string (e.g. "30s", "10m").
70    pub timeout: String,
71    /// Optional humantime jitter; agent uses it to randomise execution start.
72    #[serde(default)]
73    pub jitter: Option<String>,
74}
75
76#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
77#[serde(rename_all = "lowercase")]
78pub enum ExecuteShell {
79    Powershell,
80    Cmd,
81}
82
83impl From<ExecuteShell> for Shell {
84    fn from(s: ExecuteShell) -> Self {
85        match s {
86            ExecuteShell::Powershell => Shell::Powershell,
87            ExecuteShell::Cmd => Shell::Cmd,
88        }
89    }
90}
91
92/// Periodic schedule (spec §2.4.3). The full job [`Manifest`] is embedded
93/// so the scheduler can deploy it without a separate Git lookup; once a
94/// dedicated job-catalog API lands, `manifest` can become a `job_id`
95/// reference instead.
96#[derive(Serialize, Deserialize, Debug, Clone)]
97pub struct Schedule {
98    pub id: String,
99    /// 6-field cron expression (`sec min hour day month day-of-week`),
100    /// matching `tokio-cron-scheduler` syntax.
101    pub cron: String,
102    pub manifest: Manifest,
103    #[serde(default = "default_true")]
104    pub enabled: bool,
105}
106
107fn default_true() -> bool {
108    true
109}