minor image format refactoring

This commit is contained in:
Yan Ka, Chiu
2023-07-12 03:07:24 -04:00
parent b26696cea2
commit d9f3dd6948
10 changed files with 176 additions and 23 deletions

View File

@@ -168,6 +168,8 @@ pub struct OciInnerConfig {
pub volumes: Option<Set<String>>,
pub exposed_ports: Option<Set<String>>,
pub user: Option<String>,
pub labels: Option<HashMap<String, String>>,
pub stop_signal: Option<String>,
}
#[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Debug)]

View File

@@ -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<String>,
/// 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 {

View File

@@ -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<Self, anyhow::Error>;
@@ -48,7 +49,7 @@ pub(crate) enum ConfigMod {
NoDeinit,
Cmd,
Expose,
Volume,
Volume(String, MountSpec),
Mount(String, String),
SysV(Vec<String>),
}
@@ -100,6 +101,9 @@ impl ConfigMod {
}
}
}
Self::Volume(name, mount_spec) => {
config.mounts.insert(name.clone(), mount_spec.clone());
}
_ => {}
}
}

View File

@@ -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())
{

View File

@@ -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<NetProto>,
}
impl Serialize for Expose {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
value.parse().map_err(|e| E::custom("{e:?}"))
}
}
impl<'de> Deserialize<'de> for Expose {
fn deserialize<D>(deserializer: D) -> Result<Expose, D::Error>
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<Self, Self::Err> {

View File

@@ -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<OciConfig>,
/// 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<HashMap<Var, String>>,
/// Required ports
pub ports: HashMap<u16, String>,
pub ports: HashMap<Expose, String>,
pub devfs_rules: Vec<InterpolatedString>,
@@ -87,6 +87,9 @@ pub struct JailConfig {
#[serde(default)]
pub linux: bool,
#[serde(default, deserialize_with = "default_on_missing")]
pub labels: HashMap<String, String>,
}
#[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::<Expose>() {
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<String>),
PreconditionFailure(String),
Ok,
}

View File

@@ -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<String, Value>,
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,
}
}
}

View File

@@ -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,

View File

@@ -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<T, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or_default())
}
#[derive(Debug, Clone)]
pub enum PathComp {
RootDir,

View File

@@ -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);