From 19a489cca020a09a194225898ad3e3b21adf7810 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 23 Apr 2024 12:18:08 +0100 Subject: [PATCH] Add runc config/state/cli parser --- src/runc/cli.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++ src/runc/config.rs | 45 ++++++++++++++++++++++++++++++ src/runc/mod.rs | 3 ++ src/runc/state.rs | 37 +++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 src/runc/cli.rs create mode 100644 src/runc/config.rs create mode 100644 src/runc/state.rs diff --git a/src/runc/cli.rs b/src/runc/cli.rs new file mode 100644 index 0000000..bb03c5f --- /dev/null +++ b/src/runc/cli.rs @@ -0,0 +1,69 @@ +use std::path::PathBuf; + +use clap::ValueEnum; +use once_cell::sync::Lazy; + +#[derive(ValueEnum, Clone, Copy, PartialEq, Eq)] +pub enum LogFormat { + Json, + Text, +} + +/// runc command line. +#[derive(clap::Parser)] +pub struct Command { + #[command(flatten)] + pub global: GlobalOptions, + + #[command(subcommand)] + pub command: Subcommand, +} + +#[derive(clap::Args)] +pub struct GlobalOptions { + #[arg(long)] + pub debug: bool, + + #[arg(long)] + pub log: Option, + + #[arg(long, default_value = "text")] + pub log_format: LogFormat, + + #[arg(long, default_value = "/run/runc")] + pub root: PathBuf, + + #[arg(long)] + pub systemd_cgroup: bool, +} + +#[derive(clap::Subcommand)] +pub enum Subcommand { + // We only care about the `create` subcommand. + // We need to be able to parse the rest (hence `trailing_var_arg` and `external_subcommand`) without error, but + // we don't make use of these and forward to runc directly. + Create(CreateOptions), + Run { + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + args: Vec, + }, + #[command(external_subcommand)] + #[allow(unused)] + Other(Vec), +} + +static BUNDLE_DEFAULT: Lazy = Lazy::new(|| std::env::current_dir().unwrap()); + +#[derive(clap::Args)] +pub struct CreateOptions { + #[arg(short, long, default_value = BUNDLE_DEFAULT.as_os_str())] + pub bundle: PathBuf, + + #[arg(long)] + pub console_socket: Option, + + #[arg(long)] + pub pid_file: Option, + + pub container_id: String, +} diff --git a/src/runc/config.rs b/src/runc/config.rs new file mode 100644 index 0000000..4a4cf13 --- /dev/null +++ b/src/runc/config.rs @@ -0,0 +1,45 @@ +use std::collections::HashMap; +use std::path::Path; + +use anyhow::{Context, Result}; +use serde::Deserialize; + +#[non_exhaustive] +#[derive(Debug, Deserialize)] +pub struct User { + pub uid: u32, + pub gid: u32, +} + +#[non_exhaustive] +#[derive(Debug, Deserialize)] +pub struct Process { + pub user: User, +} + +/// OCI config. +/// +/// Only config that we need are implemented here. +/// Ref: https://github.com/opencontainers/runtime-spec/blob/main/config.md +#[non_exhaustive] +#[derive(Debug, Deserialize)] +pub struct Config { + pub process: Process, + #[serde(default)] + pub annotations: HashMap, +} + +impl Config { + pub fn from_str(s: &str) -> Result { + serde_json::from_str(s).context("Cannot parse config.json") + } + + pub fn from_config(path: &Path) -> Result { + Self::from_str(&std::fs::read_to_string(path).context("Cannot read config.json")?) + } + + pub fn from_bundle(bundle: &Path) -> Result { + let config = bundle.join("config.json"); + Self::from_config(&config) + } +} diff --git a/src/runc/mod.rs b/src/runc/mod.rs index f4ee9bc..3048d96 100644 --- a/src/runc/mod.rs +++ b/src/runc/mod.rs @@ -1 +1,4 @@ +pub mod cli; +pub mod config; pub mod log; +pub mod state; diff --git a/src/runc/state.rs b/src/runc/state.rs new file mode 100644 index 0000000..27b501a --- /dev/null +++ b/src/runc/state.rs @@ -0,0 +1,37 @@ +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result}; +use serde::Deserialize; + +#[non_exhaustive] +#[derive(Debug, Deserialize)] +pub struct CgroupPaths { + #[serde(rename = "")] + pub unified: PathBuf, + pub devices: Option, +} + +/// runc `libcontainer` states. +/// +/// Only states that we need are implemented here. +/// Ref: https://github.com/opencontainers/runc/blob/6a2813f16ad4e3be44903f6fb499c02837530ad5/libcontainer/container_linux.go#L52 +#[non_exhaustive] +#[derive(Debug, Deserialize)] +pub struct State { + pub init_process_pid: u32, + pub cgroup_paths: CgroupPaths, +} + +impl State { + pub fn from_str(s: &str) -> Result { + serde_json::from_str(s).context("Cannot parse state.json") + } + + pub fn from_state(path: &Path) -> Result { + Self::from_str(&std::fs::read_to_string(path).context("Cannot read state.json")?) + } + + pub fn from_root_and_id(root: &Path, id: &str) -> Result { + Self::from_state(&root.join(format!("{}/state.json", id))) + } +}