use super::*;
#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum ConnectionMode {
#[default]
Direct,
Jump,
#[serde(alias = "bastion")]
Wallix,
}
impl fmt::Display for ConnectionMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConnectionMode::Direct => write!(f, "direct"),
ConnectionMode::Jump => write!(f, "jump"),
ConnectionMode::Wallix => write!(f, "wallix"),
}
}
}
impl ConnectionMode {
pub fn index(self) -> usize {
match self {
ConnectionMode::Direct => 0,
ConnectionMode::Jump => 1,
ConnectionMode::Wallix => 2,
}
}
pub fn from_index(i: usize) -> Self {
match i {
1 => ConnectionMode::Jump,
2 => ConnectionMode::Wallix,
_ => ConnectionMode::Direct,
}
}
pub fn next(self) -> Self {
Self::from_index((self.index() + 1) % 3)
}
}
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("YAML parse error: {0}")]
Yaml(#[from] serde_yaml_ng::Error),
#[error("Missing configuration for server '{0}': {1}")]
MissingField(String, String),
#[error("File too large: '{path}' exceeds {limit} bytes")]
FileTooLarge { path: String, limit: u64 },
#[error("Include depth limit ({limit}) exceeded at: '{path}'")]
IncludeDepthExceeded { path: String, limit: u32 },
}
#[derive(Debug, Deserialize, Clone)]
pub struct IncludeEntry {
pub label: String,
pub path: String,
#[serde(default)]
pub merge_defaults: bool,
}
#[derive(Debug, Clone)]
pub struct NamespaceEntry {
pub label: String,
pub source_path: String,
pub defaults: Option<Defaults>,
pub entries: Vec<ConfigEntry>,
pub vars: HashMap<String, String>,
}
impl<'de> serde::Deserialize<'de> for NamespaceEntry {
fn deserialize<D: serde::Deserializer<'de>>(_d: D) -> Result<Self, D::Error> {
Err(serde::de::Error::custom(
"NamespaceEntry cannot be deserialized from YAML",
))
}
}
#[derive(Debug, Clone)]
pub enum IncludeWarning {
LoadError {
label: String,
path: String,
error: String,
},
Circular { label: String, path: String },
}
#[derive(Debug, Clone)]
pub struct ValidationWarning {
pub file: String,
pub context: String,
pub field: String,
}
impl fmt::Display for ValidationWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} ({}): champ inconnu \u{00ab} {} \u{00bb}",
self.file, self.context, self.field
)
}
}
#[derive(Debug, Deserialize, Default)]
pub struct Config {
pub defaults: Option<Defaults>,
pub groups: Vec<ConfigEntry>,
#[serde(default)]
pub includes: Vec<IncludeEntry>,
#[serde(default, rename = "_vars")]
pub vars: HashMap<String, String>,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
pub enum ConfigEntry {
Server(Server),
Group(Group),
Namespace(NamespaceEntry),
}
#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum ThemeVariant {
Latte,
Frappe,
Macchiato,
#[default]
Mocha,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct Defaults {
pub user: Option<String>,
pub ssh_key: Option<String>,
pub ssh_cert: Option<String>,
pub ssh_agent_sock: Option<String>,
pub mode: Option<ConnectionMode>,
pub ssh_port: Option<u16>,
pub ssh_options: Option<Vec<String>>,
pub wallix: Option<BastionConfig>,
pub jump: Option<Vec<JumpConfig>>,
pub use_system_ssh_config: Option<bool>,
pub theme: Option<ThemeVariant>,
pub probe_filesystems: Option<Vec<String>>,
pub keep_open: Option<bool>,
pub tunnels: Option<Vec<TunnelConfig>>,
pub default_filter: Option<String>,
pub tags: Option<Vec<String>>,
pub control_master: Option<bool>,
pub agent_forwarding: Option<bool>,
pub control_path: Option<String>,
pub control_persist: Option<String>,
pub pre_connect_hook: Option<String>,
pub post_disconnect_hook: Option<String>,
pub hook_timeout_secs: Option<u64>,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct BastionConfig {
pub host: Option<String>,
pub user: Option<String>,
pub group: Option<String>,
pub template: Option<String>,
pub account: Option<String>,
pub protocol: Option<String>,
pub auto_select: Option<bool>,
pub fail_if_menu_match_error: Option<bool>,
pub selection_timeout_secs: Option<u64>,
pub direct: Option<bool>,
pub authorization: Option<String>,
pub header_columns: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct JumpConfig {
pub host: Option<String>,
pub user: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct TunnelConfig {
pub local_port: u16,
pub remote_host: String,
pub remote_port: u16,
#[serde(default)]
pub label: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Group {
pub name: String,
pub user: Option<String>,
pub ssh_key: Option<String>,
pub mode: Option<ConnectionMode>,
pub ssh_port: Option<u16>,
pub ssh_options: Option<Vec<String>>,
pub wallix: Option<BastionConfig>,
pub wallix_group: Option<String>,
pub jump: Option<Vec<JumpConfig>>,
pub environments: Option<Vec<Environment>>,
pub servers: Option<Vec<Server>>,
pub probe_filesystems: Option<Vec<String>>,
pub tunnels: Option<Vec<TunnelConfig>>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Environment {
pub name: String,
pub user: Option<String>,
pub ssh_key: Option<String>,
pub mode: Option<ConnectionMode>,
pub ssh_port: Option<u16>,
pub ssh_options: Option<Vec<String>>,
pub wallix: Option<BastionConfig>,
pub wallix_group: Option<String>,
pub jump: Option<Vec<JumpConfig>>,
pub servers: Vec<Server>,
pub probe_filesystems: Option<Vec<String>>,
pub tunnels: Option<Vec<TunnelConfig>>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct Server {
pub name: String,
pub host: String, pub user: Option<String>,
pub ssh_key: Option<String>,
pub ssh_cert: Option<String>,
pub ssh_agent_sock: Option<String>,
pub ssh_port: Option<u16>,
pub ssh_options: Option<Vec<String>>,
pub mode: Option<ConnectionMode>,
pub wallix: Option<BastionConfig>,
pub wallix_group: Option<String>,
pub jump: Option<Vec<JumpConfig>>,
pub probe_filesystems: Option<Vec<String>>,
pub tunnels: Option<Vec<TunnelConfig>>,
pub tags: Option<Vec<String>>,
pub pre_connect_hook: Option<String>,
pub post_disconnect_hook: Option<String>,
pub notes: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ResolvedServer {
pub namespace: String,
pub group_name: String,
pub env_name: String,
pub name: String,
pub host: String,
pub user: String,
pub port: u16,
pub ssh_key: String,
pub ssh_cert: String,
pub ssh_agent_sock: String,
pub ssh_options: Vec<String>,
pub default_mode: ConnectionMode,
pub jump_host: Option<String>,
pub bastion_host: Option<String>,
pub bastion_user: Option<String>,
pub bastion_template: String,
pub wallix_group: Option<String>,
pub wallix_account: String,
pub wallix_protocol: String,
pub wallix_auto_select: bool,
pub wallix_fail_if_menu_match_error: bool,
pub wallix_selection_timeout_secs: u64,
pub wallix_direct: bool,
pub wallix_authorization: Option<String>,
pub wallix_header_columns: Vec<String>,
pub use_system_ssh_config: bool,
pub probe_filesystems: Vec<String>,
pub tunnels: Vec<TunnelConfig>,
pub tags: Vec<String>,
pub control_master: bool,
pub agent_forwarding: bool,
pub control_path: String,
pub control_persist: String,
pub pre_connect_hook: Option<String>,
pub post_disconnect_hook: Option<String>,
pub hook_timeout_secs: u64,
pub notes: String,
}