use std::collections::BTreeMap;
use super::error::StateError;
use super::row::Row;
use super::store::Store;
use crate::types::DnsName;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InstanceStatus {
Active,
Tombstoned,
}
impl InstanceStatus {
pub(super) fn from_sql(s: &str) -> Result<Self, StateError> {
match s {
"active" => Ok(Self::Active),
"tombstoned" => Ok(Self::Tombstoned),
other => Err(StateError::RowDecode {
column: 2,
detail: format!("unknown instance status {other:?}"),
}),
}
}
}
#[derive(Debug, Clone)]
pub struct InstanceRecord {
pub name: DnsName,
pub substrate: DnsName,
pub status: InstanceStatus,
pub definition: String,
pub source_overrides: BTreeMap<String, String>,
pub dirty: bool,
pub definition_dir: String,
pub created_at: i64,
pub tombstoned_at: Option<i64>,
}
const SELECT_COLUMNS: &str = "name, substrate, status, definition, source_overrides, created_at, tombstoned_at, definition_dir, dirty";
impl TryFrom<&Row> for InstanceRecord {
type Error = StateError;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
let status = row.get_string(2)?;
let overrides_json = row.get_string(4)?;
let name = row.get_string(0)?;
let substrate = row.get_string(1)?;
Ok(Self {
name: DnsName::try_new(&name).map_err(|err| StateError::RowDecode {
column: 0,
detail: err.to_string(),
})?,
substrate: DnsName::try_new(&substrate).map_err(|err| StateError::RowDecode {
column: 1,
detail: err.to_string(),
})?,
status: InstanceStatus::from_sql(&status)?,
definition: row.get_string(3)?,
source_overrides: serde_json::from_str(&overrides_json).unwrap_or_default(),
created_at: row.get_i64(5)?,
tombstoned_at: row.get_opt_i64(6)?,
definition_dir: row.get_string(7)?,
dirty: row.get_i64(8)? != 0,
})
}
}
impl Store {
pub fn create_instance(
&self,
name: &str,
substrate: &str,
definition: &str,
source_overrides: &BTreeMap<String, String>,
definition_dir: &str,
dirty: bool,
) -> Result<InstanceRecord, StateError> {
let overrides_json =
serde_json::to_string(source_overrides).unwrap_or_else(|_| "{}".into());
let result = self.execute(
"INSERT INTO instances (name, substrate, status, definition, source_overrides, created_at, definition_dir, dirty)
VALUES (?1, ?2, 'active', ?3, ?4, ?5, ?6, ?7)",
&[
name.into(),
substrate.into(),
definition.into(),
overrides_json.into(),
Self::now().into(),
definition_dir.into(),
i64::from(dirty).into(),
],
);
match result {
Ok(_) => self
.instance(name)?
.ok_or_else(|| StateError::InstanceNotFound { name: name.into() }),
Err(err) => {
if let Some(existing) = self.instance(name)? {
Err(StateError::InstanceExists {
name: name.into(),
existing_substrate: existing.substrate.as_str().to_owned(),
})
} else {
Err(err)
}
}
}
}
pub fn instance(&self, name: &str) -> Result<Option<InstanceRecord>, StateError> {
self.query_row(
&format!("SELECT {SELECT_COLUMNS} FROM instances WHERE name = ?1"),
&[name.into()],
|row| InstanceRecord::try_from(row),
)
}
pub fn instances(&self) -> Result<Vec<InstanceRecord>, StateError> {
self.query_map(
&format!("SELECT {SELECT_COLUMNS} FROM instances ORDER BY name"),
&[],
|row| InstanceRecord::try_from(row),
)
}
pub fn tombstone_instance(&self, name: &str) -> Result<(), StateError> {
let changed = self.execute(
"UPDATE instances SET status = 'tombstoned', tombstoned_at = ?2 WHERE name = ?1",
&[name.into(), Self::now().into()],
)?;
if changed == 0 {
return Err(StateError::InstanceNotFound { name: name.into() });
}
Ok(())
}
pub fn revive_instance(
&self,
name: &str,
definition: &str,
source_overrides: &BTreeMap<String, String>,
dirty: bool,
) -> Result<(), StateError> {
let overrides_json =
serde_json::to_string(source_overrides).unwrap_or_else(|_| "{}".into());
let changed = self.execute(
"UPDATE instances SET status = 'active', definition = ?2, source_overrides = ?3,
created_at = ?4, tombstoned_at = NULL, dirty = ?5 WHERE name = ?1",
&[
name.into(),
definition.into(),
overrides_json.into(),
Self::now().into(),
i64::from(dirty).into(),
],
)?;
if changed == 0 {
return Err(StateError::InstanceNotFound { name: name.into() });
}
self.execute(
"DELETE FROM checkpoints WHERE instance = ?1",
&[name.into()],
)?;
Ok(())
}
pub fn update_source_overrides(
&self,
name: &str,
source_overrides: &BTreeMap<String, String>,
) -> Result<(), StateError> {
let overrides_json =
serde_json::to_string(source_overrides).unwrap_or_else(|_| "{}".into());
self.execute(
"UPDATE instances SET source_overrides = ?2 WHERE name = ?1",
&[name.into(), overrides_json.into()],
)?;
Ok(())
}
pub fn update_dirty(&self, name: &str, dirty: bool) -> Result<(), StateError> {
self.execute(
"UPDATE instances SET dirty = ?2 WHERE name = ?1",
&[name.into(), i64::from(dirty).into()],
)?;
Ok(())
}
}