use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use crate::component::ScopedExtensionConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RigSpec {
#[serde(default)]
pub id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub components: HashMap<String, ComponentSpec>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub services: HashMap<String, ServiceSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub symlinks: Vec<SymlinkSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub shared_paths: Vec<SharedPathSpec>,
#[serde(default, skip_serializing_if = "RigResourcesSpec::is_empty")]
pub resources: RigResourcesSpec,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub pipeline: HashMap<String, Vec<PipelineStep>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bench: Option<BenchSpec>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub bench_workloads: HashMap<String, Vec<String>>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub bench_profiles: HashMap<String, Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub app_launcher: Option<AppLauncherSpec>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RigResourcesSpec {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclusive: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub paths: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ports: Vec<u16>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub process_patterns: Vec<String>,
}
impl RigResourcesSpec {
pub fn is_empty(&self) -> bool {
self.exclusive.is_empty()
&& self.paths.is_empty()
&& self.ports.is_empty()
&& self.process_patterns.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppLauncherSpec {
pub platform: AppLauncherPlatform,
pub wrapper_display_name: String,
pub wrapper_bundle_id: String,
pub target_app: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub install_dir: Option<String>,
#[serde(
default = "default_app_preflight",
skip_serializing_if = "Vec::is_empty"
)]
pub preflight: Vec<AppLauncherPreflight>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub on_preflight_fail: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AppLauncherPlatform {
Macos,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AppLauncherPreflight {
#[serde(rename = "rig:check")]
RigCheck,
}
fn default_app_preflight() -> Vec<AppLauncherPreflight> {
vec![AppLauncherPreflight::RigCheck]
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_component: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub components: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_baseline_rig: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub warmup_iterations: Option<u64>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub axes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentSpec {
pub path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub remote_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub triage_remote_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stack: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extensions: Option<HashMap<String, ScopedExtensionConfig>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceSpec {
pub kind: ServiceKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub env: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub health: Option<CheckSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub discover: Option<DiscoverSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoverSpec {
pub pattern: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub argv_contains: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ServiceKind {
HttpStatic,
Command,
External,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymlinkSpec {
pub link: String,
pub target: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedPathSpec {
pub link: String,
pub target: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum PipelineStep {
Service {
#[serde(default, skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
id: String,
op: ServiceOp,
},
Build {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
component: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Extension {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
component: String,
op: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Git {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
component: String,
op: GitOp,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Stack {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
component: String,
op: StackOp,
#[serde(default)]
dry_run: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Command {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
#[serde(rename = "command")]
cmd: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
cwd: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
env: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Symlink {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
op: SymlinkOp,
},
SharedPath {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
op: SharedPathOp,
},
Patch {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
component: String,
file: String,
marker: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
after: Option<String>,
content: String,
#[serde(default = "default_patch_op")]
op: PatchOp,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Check {
#[serde(default, rename = "id", skip_serializing_if = "Option::is_none")]
step_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
depends_on: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
#[serde(flatten)]
spec: CheckSpec,
},
}
fn default_patch_op() -> PatchOp {
PatchOp::Apply
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum GitOp {
Status,
Pull,
Push,
Fetch,
Checkout,
CurrentBranch,
Rebase,
CherryPick,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum StackOp {
Sync,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ServiceOp {
Start,
Stop,
Health,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SymlinkOp {
Ensure,
Verify,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SharedPathOp {
Ensure,
Verify,
Cleanup,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PatchOp {
Apply,
Verify,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CheckSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub http: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expect_status: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contains: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expect_exit: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub newer_than: Option<NewerThanSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewerThanSpec {
pub left: TimeSource,
pub right: TimeSource,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TimeSource {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_mtime: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub process_start: Option<DiscoverSpec>,
}
#[cfg(test)]
#[path = "../../../tests/core/rig/spec_test.rs"]
mod spec_test;
#[cfg(test)]
#[path = "../../../tests/core/rig/bench_default_baseline_spec_test.rs"]
mod bench_default_baseline_spec_test;