use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::io::BufReader;
use std::path::PathBuf;
use std::{fs::File, path::Path};
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum ContainerStatus {
Creating,
Created,
Running,
Stopped,
Paused,
}
impl Default for ContainerStatus {
fn default() -> Self {
ContainerStatus::Creating
}
}
impl ContainerStatus {
pub fn can_start(&self) -> bool {
matches!(self, ContainerStatus::Created)
}
pub fn can_kill(&self) -> bool {
use ContainerStatus::*;
match self {
Creating | Stopped => false,
Created | Running | Paused => true,
}
}
pub fn can_delete(&self) -> bool {
matches!(self, ContainerStatus::Stopped)
}
pub fn can_pause(&self) -> bool {
matches!(self, ContainerStatus::Running)
}
pub fn can_resume(&self) -> bool {
matches!(self, ContainerStatus::Paused)
}
}
impl Display for ContainerStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let print = match *self {
Self::Creating => "Creating",
Self::Created => "Created",
Self::Running => "Running",
Self::Stopped => "Stopped",
Self::Paused => "Paused",
};
write!(f, "{}", print)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct State {
pub oci_version: String,
pub id: String,
pub status: ContainerStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<i32>,
pub bundle: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub creator: Option<u32>,
pub use_systemd: Option<bool>,
}
impl State {
const STATE_FILE_PATH: &'static str = "state.json";
pub fn new(
container_id: &str,
status: ContainerStatus,
pid: Option<i32>,
bundle: PathBuf,
) -> Self {
Self {
oci_version: "v1.0.2".to_string(),
id: container_id.to_string(),
status,
pid,
bundle,
annotations: Some(HashMap::default()),
created: None,
creator: None,
use_systemd: None,
}
}
pub fn save(&self, container_root: &Path) -> Result<()> {
let state_file_path = Self::file_path(container_root);
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.append(false)
.create(true)
.truncate(true)
.open(&state_file_path)
.with_context(|| format!("failed to open {}", state_file_path.display()))?;
serde_json::to_writer(&file, self)?;
Ok(())
}
pub fn load(container_root: &Path) -> Result<Self> {
let state_file_path = Self::file_path(container_root);
let state_file = File::open(&state_file_path).with_context(|| {
format!("failed to open container state file {:?}", state_file_path)
})?;
let state: Self = serde_json::from_reader(BufReader::new(state_file))?;
Ok(state)
}
pub fn file_path(container_root: &Path) -> PathBuf {
container_root.join(Self::STATE_FILE_PATH)
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct ContainerProcessState {
pub oci_version: String,
pub fds: Vec<String>,
pub pid: i32,
pub metadata: String,
pub state: State,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creating_status() {
let cstatus = ContainerStatus::default();
assert!(!cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(!cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_create_status() {
let cstatus = ContainerStatus::Created;
assert!(cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_running_status() {
let cstatus = ContainerStatus::Running;
assert!(!cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(cstatus.can_kill());
assert!(cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_stopped_status() {
let cstatus = ContainerStatus::Stopped;
assert!(!cstatus.can_start());
assert!(cstatus.can_delete());
assert!(!cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(!cstatus.can_resume());
}
#[test]
fn test_paused_status() {
let cstatus = ContainerStatus::Paused;
assert!(!cstatus.can_start());
assert!(!cstatus.can_delete());
assert!(cstatus.can_kill());
assert!(!cstatus.can_pause());
assert!(cstatus.can_resume());
}
}