#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Postcondition {
#[serde(default)]
pub file_exists: Option<String>,
#[serde(default)]
pub file_mode: Option<String>,
#[serde(default)]
pub command_succeeds: Option<String>,
#[serde(default)]
pub packages_absent: Vec<String>,
#[serde(default)]
pub service_active: Option<String>,
#[serde(default)]
pub user_in_group: Option<UserInGroupCheck>,
#[serde(default)]
pub env_matches: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInGroupCheck {
pub user: String,
pub group: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StepCheckpoint {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub rollback: Option<String>,
#[serde(default)]
pub state_files: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StepTiming {
#[serde(default)]
pub timeout: Option<String>,
#[serde(default)]
pub retry: Option<RetryConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryConfig {
#[serde(default)]
pub count: u32,
#[serde(default)]
pub delay: Option<String>,
#[serde(default)]
pub backoff: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StepVerification {
#[serde(default)]
pub commands: Vec<VerificationCommand>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationCommand {
pub cmd: String,
#[serde(default)]
pub expect: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailureAction {
pub action: String,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub notify: Vec<String>,
#[serde(default)]
pub preserve_state: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StepConstraints {
#[serde(default)]
pub exclusive_resource: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Action {
Script,
AptInstall,
AptRemove,
FileWrite,
Verify,
UserGroup,
Custom(String),
}
impl Action {
pub fn parse(s: &str) -> Self {
match s {
"script" => Self::Script,
"apt-install" => Self::AptInstall,
"apt-remove" => Self::AptRemove,
"file-write" => Self::FileWrite,
"verify" => Self::Verify,
"user-group" => Self::UserGroup,
other => Self::Custom(other.to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct Environment {
pub name: String,
pub default: Option<String>,
pub from_env: Option<String>,
pub required: bool,
pub validate: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_minimal_installer() {
let toml = r#"
[installer]
name = "test"
version = "1.0.0"
"#;
let spec = InstallerSpec::parse(toml).expect("Failed to parse minimal spec");
assert_eq!(spec.installer.name, "test");
assert_eq!(spec.installer.version, "1.0.0");
}
#[test]
fn test_parse_installer_with_step() {
let toml = r#"
[installer]
name = "test"
version = "1.0.0"
[[step]]
id = "hello"
name = "Hello World"
action = "script"
[step.script]
content = "echo hello"
"#;
let spec = InstallerSpec::parse(toml).expect("Failed to parse spec with step");
assert_eq!(spec.step.len(), 1);
assert_eq!(spec.step[0].id, "hello");
assert_eq!(spec.step[0].action, "script");
}
#[test]
fn test_parse_installer_with_artifact() {
let toml = r#"
[installer]
name = "test"
version = "1.0.0"
[[artifact]]
id = "myfile"
url = "https://example.com/file.tar.gz"
sha256 = "abc123"
"#;
let spec = InstallerSpec::parse(toml).expect("Failed to parse spec with artifact");
assert_eq!(spec.artifact.len(), 1);
assert_eq!(spec.artifact[0].id, "myfile");
assert_eq!(spec.artifact[0].sha256.as_deref(), Some("abc123"));
}
#[test]
fn test_action_parse() {
assert_eq!(Action::parse("script"), Action::Script);
assert_eq!(Action::parse("apt-install"), Action::AptInstall);
assert_eq!(
Action::parse("custom-action"),
Action::Custom("custom-action".to_string())
);
}
#[test]
fn test_invalid_toml() {
let toml = "INVALID [[[";
let result = InstallerSpec::parse(toml);
assert!(result.is_err());
}
}