From d9f3dd694883348af3cafbae80016e18fea91c3b Mon Sep 17 00:00:00 2001 From: "Yan Ka, Chiu" Date: Wed, 12 Jul 2023 03:07:24 -0400 Subject: [PATCH] minor image format refactoring --- oci_util/src/models.rs | 2 + xc-bin/src/image.rs | 50 ++++++++++++++++++++++++ xc-bin/src/jailfile/directives/mod.rs | 8 +++- xc-bin/src/main.rs | 4 ++ xc/src/format/docker_compat.rs | 52 ++++++++++++++++++++++++- xc/src/models/jail_image.rs | 56 ++++++++++++++++++--------- xc/src/models/mod.rs | 5 +++ xc/src/models/network.rs | 4 +- xc/src/util.rs | 11 ++++++ xcd/src/context/instantiate.rs | 7 ++++ 10 files changed, 176 insertions(+), 23 deletions(-) diff --git a/oci_util/src/models.rs b/oci_util/src/models.rs index 1a22d26..9043a30 100644 --- a/oci_util/src/models.rs +++ b/oci_util/src/models.rs @@ -168,6 +168,8 @@ pub struct OciInnerConfig { pub volumes: Option>, pub exposed_ports: Option>, pub user: Option, + pub labels: Option>, + pub stop_signal: Option, } #[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Debug)] diff --git a/xc-bin/src/image.rs b/xc-bin/src/image.rs index 69eec85..239950a 100644 --- a/xc-bin/src/image.rs +++ b/xc-bin/src/image.rs @@ -46,6 +46,9 @@ pub(crate) enum ImageAction { Show { image_id: String, }, + Describe { + image_id: ImageReference, + }, GetConfig { image_id: String, }, @@ -83,6 +86,10 @@ pub(crate) enum PatchActions { name: Option, /// mount point in the container mount_point: PathBuf, + + #[clap(long = "required", action)] + required: bool, + /// the image image_reference: ImageReference, }, @@ -164,6 +171,7 @@ pub(crate) fn use_image_action( hints, name, mount_point, + required, image_reference, read_only, } => { @@ -185,6 +193,7 @@ pub(crate) fn use_image_action( read_only, volume_hints, destination, + required, }; config.mounts.insert(key, mountspec); })?; @@ -310,6 +319,47 @@ pub(crate) fn use_image_action( } } } + ImageAction::Describe { image_id } => { + let image_name = image_id.name.to_string(); + let tag = image_id.tag.to_string(); + let reqt = DescribeImageRequest { image_name, tag }; + let res = do_describe_image(conn, reqt)?; + match res { + Err(e) => eprintln!("{e:#?}"), + Ok(res) => { + let image = &res.jail_image; + let config = image.jail_config(); + println!("\n{image_id}"); + println!(" Envs:"); + for (key, value) in config.envs.iter() { + println!(" {key}:"); + println!(" Required: {}", value.required); + println!( + " Description: {}", + value.description.clone().unwrap_or_default() + ); + } + println!(" Ports:"); + for (port, value) in config.ports.iter() { + println!(" {port}: {value}"); + } + println!(" Volumes:"); + for (name, spec) in config.mounts.iter() { + println!(" {name}:"); + println!(" Mount Point: {}", spec.destination); + println!(" Required: {}", spec.required); + println!(" Read-Only: {}", spec.read_only); + if !spec.volume_hints.is_empty() { + println!(" Hints:"); + for (key, value) in spec.volume_hints.iter() { + let desc = serde_json::to_string(value).unwrap(); + println!(" {key}: {desc}"); + } + } + } + } + } + } ImageAction::GetConfig { image_id } => { let (image_name, tag) = image_id.rsplit_once(':').expect("invalid image id"); let reqt = DescribeImageRequest { diff --git a/xc-bin/src/jailfile/directives/mod.rs b/xc-bin/src/jailfile/directives/mod.rs index 4227f30..16bbb2b 100644 --- a/xc-bin/src/jailfile/directives/mod.rs +++ b/xc-bin/src/jailfile/directives/mod.rs @@ -24,13 +24,14 @@ pub mod copy; pub mod from; pub mod run; +pub mod volume; use super::JailContext; use crate::jailfile::parse::Action; use anyhow::Context; use xc::models::jail_image::{JailConfig, SpecialMount}; -use xc::models::SystemVPropValue; +use xc::models::{MountSpec, SystemVPropValue}; pub(crate) trait Directive: Sized { fn from_action(action: &Action) -> Result; @@ -48,7 +49,7 @@ pub(crate) enum ConfigMod { NoDeinit, Cmd, Expose, - Volume, + Volume(String, MountSpec), Mount(String, String), SysV(Vec), } @@ -100,6 +101,9 @@ impl ConfigMod { } } } + Self::Volume(name, mount_spec) => { + config.mounts.insert(name.clone(), mount_spec.clone()); + } _ => {} } } diff --git a/xc-bin/src/main.rs b/xc-bin/src/main.rs index 22cb2ab..beb3362 100644 --- a/xc-bin/src/main.rs +++ b/xc-bin/src/main.rs @@ -34,6 +34,7 @@ use crate::channel::{use_channel_action, ChannelAction}; use crate::error::ActionError; use crate::format::{BindMount, EnvPair, IpWant, PublishSpec}; use crate::image::{use_image_action, ImageAction}; +use crate::jailfile::directives::volume::VolumeDirective; use crate::network::{use_network_action, NetworkAction}; use crate::redirect::{use_rdr_action, RdrAction}; @@ -314,6 +315,9 @@ fn main() -> Result<(), ActionError> { } else if action.directive_name == "COPY" { let directive = CopyDirective::from_action(action)?; directive.run_in_context(&mut context)?; + } else if action.directive_name == "VOLUME" { + let directive = VolumeDirective::from_action(action)?; + directive.run_in_context(&mut context)?; } else if ConfigMod::implemented_directives() .contains(&action.directive_name.as_str()) { diff --git a/xc/src/format/docker_compat.rs b/xc/src/format/docker_compat.rs index 695a6cd..4d65548 100644 --- a/xc/src/format/docker_compat.rs +++ b/xc/src/format/docker_compat.rs @@ -24,6 +24,7 @@ use crate::models::network::{NetProto, PortNum}; use pest::Parser; use pest_derive::Parser; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; #[derive(Parser)] @@ -35,12 +36,61 @@ expose = { (portrange | portnum) ~ ("/" ~ net_proto)? } "#] struct RuleParser; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)] pub struct Expose { pub port: PortNum, pub proto: Option, } +impl Serialize for Expose { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct ExposeVisitor; + +impl<'de> Visitor<'de> for ExposeVisitor { + type Value = Expose; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("(PORT | PORTRANGE)(/ PROTO)?") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + value.parse().map_err(|e| E::custom("{e:?}")) + } +} + +impl<'de> Deserialize<'de> for Expose { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(ExposeVisitor) + } +} + +impl std::fmt::Display for Expose { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.port { + PortNum::Single(n) => write!(f, "{n}")?, + PortNum::Range(s, n) => write!(f, "{s}-{n}")?, + }; + match &self.proto { + Some(proto) => write!(f, "/{proto}")?, + None => {} + } + Ok(()) + } +} + impl FromStr for Expose { type Err = anyhow::Error; fn from_str(input: &str) -> Result { diff --git a/xc/src/models/jail_image.rs b/xc/src/models/jail_image.rs index 644a6a2..3426e0d 100644 --- a/xc/src/models/jail_image.rs +++ b/xc/src/models/jail_image.rs @@ -21,6 +21,10 @@ // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. + +use crate::format::docker_compat::Expose; +use crate::util::default_on_missing; + use super::cmd_arg::CmdArg; use super::exec::Exec; use super::MountSpec; @@ -45,16 +49,12 @@ pub struct JailConfig { pub original_oci_config: Option, - /// If this jail require vnet to run + /// is vnet required #[serde(default)] pub vnet: bool, - /// The mappings between the alias and variable name, for example, a jail can expect an - /// interface myext0 to exists, and map the variable as $MY_EXT0 - pub nics: Option>, - /// Required ports - pub ports: HashMap, + pub ports: HashMap, pub devfs_rules: Vec, @@ -87,6 +87,9 @@ pub struct JailConfig { #[serde(default)] pub linux: bool, + + #[serde(default, deserialize_with = "default_on_missing")] + pub labels: HashMap, } #[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Debug)] @@ -169,11 +172,30 @@ impl JailConfig { }) .unwrap_or_else(HashMap::new); + let ports = config + .config + .as_ref() + .and_then(|config| config.exposed_ports.as_ref()) + .map(|set| { + let mut ports = HashMap::new(); + for port in set.0.iter() { + if let Ok(expose) = port.parse::() { + ports.insert(expose, "".to_string()); + } + } + ports + }) + .unwrap_or_default(); + + let labels = config + .config + .as_ref() + .and_then(|config| config.labels.clone()) + .unwrap_or_default(); + let mut meta = JailConfig { secure_level: 0, vnet: false, - nics: None, - ports: HashMap::new(), devfs_rules: Vec::new(), allow: Vec::new(), sysv_msg: SystemVPropValue::New, @@ -184,8 +206,10 @@ impl JailConfig { init: Vec::new(), deinit: Vec::new(), mounts, - linux: true, + linux: config.os != "FreeBSD", original_oci_config: Some(config.clone()), + ports, + labels, ..JailConfig::default() }; @@ -237,14 +261,10 @@ impl JailConfig { let layers = config.rootfs.diff_ids; - Some(meta.to_image(layers)) + let mut image = meta.to_image(layers); + + image.0.os = config.os; + + Some(image) } } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Satis { - MissingCap(String), - MissingEnv(String, Option), - PreconditionFailure(String), - Ok, -} diff --git a/xc/src/models/mod.rs b/xc/src/models/mod.rs index 0cc0306..f744776 100644 --- a/xc/src/models/mod.rs +++ b/xc/src/models/mod.rs @@ -26,6 +26,8 @@ pub mod exec; pub mod jail_image; pub mod network; +use crate::util::default_on_missing; + use cmd_arg::CmdArg; use exec::ResolvedExec; use serde::{Deserialize, Serialize}; @@ -39,6 +41,8 @@ pub struct MountSpec { pub destination: String, pub volume_hints: HashMap, pub read_only: bool, + #[serde(default, deserialize_with = "default_on_missing")] + pub required: bool, } impl MountSpec { @@ -48,6 +52,7 @@ impl MountSpec { destination: destination.to_string(), volume_hints: HashMap::new(), read_only: false, + required: false, } } } diff --git a/xc/src/models/network.rs b/xc/src/models/network.rs index abb8085..5051b1f 100644 --- a/xc/src/models/network.rs +++ b/xc/src/models/network.rs @@ -61,7 +61,7 @@ impl std::fmt::Display for IpAssign { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)] pub enum PortNum { Single(u16), /// Represents a range of port number by a starting number and length @@ -77,7 +77,7 @@ impl std::fmt::Display for PortNum { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)] pub enum NetProto { Tcp, Udp, diff --git a/xc/src/util.rs b/xc/src/util.rs index c75bde8..9cbf594 100644 --- a/xc/src/util.rs +++ b/xc/src/util.rs @@ -21,12 +21,23 @@ // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. +use serde::{Deserialize, Deserializer}; use std::collections::VecDeque; use std::ffi::{OsStr, OsString}; use std::os::fd::AsRawFd; use std::path::{Component, Path, PathBuf}; use sysctl::{Ctl, Sysctl}; +pub fn default_on_missing<'de, D, T: Default + serde::Deserialize<'de>>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + #[derive(Debug, Clone)] pub enum PathComp { RootDir, diff --git a/xcd/src/context/instantiate.rs b/xcd/src/context/instantiate.rs index fccb52f..5626f53 100644 --- a/xcd/src/context/instantiate.rs +++ b/xcd/src/context/instantiate.rs @@ -213,6 +213,13 @@ impl InstantiateBlueprint { for req in request.ipreq.iter() { match network_manager.allocate(vnet, req, id) { Ok((alloc, router)) => { + if !existing_ifaces.contains(&alloc.interface) { + precondition_failure!( + ENOENT, + "missing network interface {}", + &alloc.interface + ); + } if let Some(router) = router { if default_router.is_none() { default_router = Some(router);