Skip to content

Commit

Permalink
Generalize OCI spec root
Browse files Browse the repository at this point in the history
We now generalize and document the OCI `Spec` root structure. This means
that some fields have been added and other are now optional.

All corresponding usages of the new spec format have been changed and
tests have been adapted.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
  • Loading branch information
saschagrunert committed Jul 30, 2021
1 parent 29789e3 commit ade1c17
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 85 deletions.
5 changes: 5 additions & 0 deletions oci_spec/src/hooks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use super::*;

// TODO: implement me
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Hooks {}
168 changes: 125 additions & 43 deletions oci_spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,104 @@ use std::convert::TryFrom;
use std::fs;
use std::path::{Path, PathBuf};

mod hooks;
mod linux;
mod miscellaneous;
mod process;
mod solaris;
mod test;
mod vm;
mod windows;

// re-export for ease of use
pub use hooks::*;
pub use linux::*;
pub use miscellaneous::*;
pub use process::*;
pub use solaris::*;
pub use vm::*;
pub use windows::*;

// Base configuration for the container
/// Base configuration for the container.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Spec {
// Version of the Open Container Initiative Runtime Specification with which the bundle complies
#[serde(default, rename = "ociVersion")]
/// MUST be in SemVer v2.0.0 format and specifies the version of the Open Container Initiative
/// Runtime Specification with which the bundle complies. The Open Container Initiative
/// Runtime Specification follows semantic versioning and retains forward and backward
/// compatibility within major versions. For example, if a configuration is compliant with
/// version 1.1 of this specification, it is compatible with all runtimes that support any 1.1
/// or later release of this specification, but is not compatible with a runtime that supports
/// 1.0 and not 1.1.
pub version: String,
// Computer os and arch
pub platform: Option<Platform>,
// Configures container process
pub process: Process,
// Configures container's root filesystem
pub root: Root,
// Configures container's hostname
#[serde(default)]
pub hostname: String,
// Configures additional mounts (on top of Root)
#[serde(default)]
pub mounts: Vec<Mount>,
// Arbitrary metadata for container
#[serde(default)]
pub annotations: HashMap<String, String>,
// Platform specific config for Linux based containers
#[serde(default)]
pub linux: Linux,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Specifies the container's root filesystem. On Windows, for Windows Server Containers, this
/// field is REQUIRED. For Hyper-V Containers, this field MUST NOT be set.
///
/// On all other platforms, this field is REQUIRED.
pub root: Option<Root>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Specifies additional mounts beyond `root`. The runtime MUST mount entries in the listed
/// order.
///
/// For Linux, the parameters are as documented in
/// [`mount(2)`](http://man7.org/linux/man-pages/man2/mount.2.html) system call man page. For
/// Solaris, the mount entry corresponds to the 'fs' resource in the
/// [`zonecfg(1M)`](http://docs.oracle.com/cd/E86824_01/html/E54764/zonecfg-1m.html) man page.
pub mounts: Option<Vec<Mount>>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Specifies the container process. This property is REQUIRED when
/// [`start`](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md#start) is
/// called.
pub process: Option<Process>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Specifies the container's hostname as seen by processes running inside the container. On
/// Linux, for example, this will change the hostname in the container [UTS
/// namespace](http://man7.org/linux/man-pages/man7/namespaces.7.html). Depending on your
/// [namespace
/// configuration](https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md#namespaces),
/// the container UTS namespace may be the runtime UTS namespace.
pub hostname: Option<String>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Hooks allow users to specify programs to run before or after various lifecycle events.
/// Hooks MUST be called in the listed order. The state of the container MUST be passed to
/// hooks over stdin so that they may do work appropriate to the current state of the
/// container.
pub hooks: Option<Hooks>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Annotations contains arbitrary metadata for the container. This information MAY be
/// structured or unstructured. Annotations MUST be a key-value map. If there are no
/// annotations then this property MAY either be absent or an empty map.
///
/// Keys MUST be strings. Keys MUST NOT be an empty string. Keys SHOULD be named using a
/// reverse domain notation - e.g. com.example.myKey. Keys using the org.opencontainers
/// namespace are reserved and MUST NOT be used by subsequent specifications. Runtimes MUST
/// handle unknown annotation keys like any other unknown property.
///
/// Values MUST be strings. Values MAY be an empty string.
pub annotations: Option<HashMap<String, String>>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Linux is platform-specific configuration for Linux based containers.
pub linux: Option<Linux>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Solaris is platform-specific configuration for Solaris based containers.
pub solaris: Option<Solaris>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// Windows is platform-specific configuration for Windows based containers.
pub windows: Option<Windows>,

#[serde(default, skip_serializing_if = "Option::is_none")]
/// VM specifies configuration for Virtual Machine based containers.
pub vm: Option<VM>,
}

// This gives a basic boilerplate for Spec that can be used calling Default::default().
Expand All @@ -49,15 +113,18 @@ impl Default for Spec {
Spec {
// Defaults to most current oci version
version: String::from("1.0.2-dev"),
platform: Some(Default::default()),
process: Default::default(),
root: Default::default(),
process: Some(Default::default()),
root: Some(Default::default()),
// Defaults hostname as youki
hostname: String::from("youki"),
mounts: get_default_mounts(),
hostname: "youki".to_string().into(),
mounts: get_default_mounts().into(),
// Defaults to empty metadata
annotations: Default::default(),
linux: Default::default(),
annotations: Some(Default::default()),
linux: Some(Default::default()),
hooks: None,
solaris: None,
windows: None,
vm: None,
}
}
}
Expand All @@ -82,23 +149,30 @@ impl Spec {
}

pub fn canonicalize_rootfs<P: AsRef<Path>>(&mut self, bundle: P) -> Result<()> {
let canonical_root_path = if self.root.path.is_absolute() {
fs::canonicalize(&self.root.path)
.with_context(|| format!("failed to canonicalize {:?}", self.root.path))?
let root = self.root.as_mut().context("no root path provided")?;
root.path = Self::canonicalize_path(bundle, &root.path)?;
Ok(())
}

fn canonicalize_path<B, P>(bundle: B, path: P) -> Result<PathBuf>
where
B: AsRef<Path>,
P: AsRef<Path>,
{
Ok(if path.as_ref().is_absolute() {
fs::canonicalize(path.as_ref())
.with_context(|| format!("failed to canonicalize {}", path.as_ref().display()))?
} else {
let canonical_bundle_path = fs::canonicalize(&bundle).context(format!(
"failed to canonicalize bundle: {:?}",
bundle.as_ref()
"failed to canonicalize bundle: {}",
bundle.as_ref().display()
))?;

fs::canonicalize(&canonical_bundle_path.join(&self.root.path)).context(format!(
"failed to canonicalize rootfs: {:?}",
&self.root.path
fs::canonicalize(canonical_bundle_path.join(path.as_ref())).context(format!(
"failed to canonicalize rootfs: {}",
path.as_ref().display()
))?
};
self.root.path = canonical_root_path;

Ok(())
})
}
}

Expand All @@ -124,12 +198,16 @@ mod tests {
root: Root {
path: rootfs_absolute_path.clone(),
..Default::default()
},
}
.into(),
..Default::default()
};
spec.canonicalize_rootfs(bundle.path())
.with_context(|| "Failed to canonicalize rootfs")?;
assert_eq!(rootfs_absolute_path, spec.root.path);
assert_eq!(
rootfs_absolute_path,
spec.root.context("no root in spec")?.path
);
}

{
Expand All @@ -138,12 +216,16 @@ mod tests {
root: Root {
path: PathBuf::from(rootfs_name),
..Default::default()
},
}
.into(),
..Default::default()
};
spec.canonicalize_rootfs(bundle.path())
.with_context(|| "Failed to canonicalize rootfs")?;
assert_eq!(rootfs_absolute_path, spec.root.path);
assert_eq!(
rootfs_absolute_path,
spec.root.context("no root in spec")?.path
);
}

Ok(())
Expand Down
5 changes: 5 additions & 0 deletions oci_spec/src/solaris.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use super::*;

// TODO: implement me
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Solaris {}
5 changes: 5 additions & 0 deletions oci_spec/src/vm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use super::*;

// TODO: implement me
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct VM {}
5 changes: 5 additions & 0 deletions oci_spec/src/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use super::*;

// TODO: implement me
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Windows {}
7 changes: 5 additions & 2 deletions src/commands/delete.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fs;
use std::path::PathBuf;

use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use clap::Clap;
use nix::sys::signal::Signal;

Expand Down Expand Up @@ -50,7 +50,10 @@ impl Delete {
log::debug!("remove dir {:?}", container.root);
fs::remove_dir_all(&container.root)?;

let cgroups_path = utils::get_cgroup_path(&spec.linux.cgroups_path, container.id());
let cgroups_path = utils::get_cgroup_path(
&spec.linux.context("no linux in spec")?.cgroups_path,
container.id(),
);

// remove the cgroup created for the container
// check https://man7.org/linux/man-pages/man7/cgroups.7.html
Expand Down
7 changes: 5 additions & 2 deletions src/commands/pause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::fs::canonicalize;
use std::path::PathBuf;

use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use clap::Clap;

use crate::cgroups;
Expand Down Expand Up @@ -45,7 +45,10 @@ impl Pause {
}

let spec = container.spec()?;
let cgroups_path = utils::get_cgroup_path(&spec.linux.cgroups_path, &self.container_id);
let cgroups_path = utils::get_cgroup_path(
&spec.linux.context("no linux in spec")?.cgroups_path,
&self.container_id,
);
// create cgroup manager structure from the config at the path
let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?;
// freeze the container
Expand Down
7 changes: 5 additions & 2 deletions src/commands/resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::fs::canonicalize;
use std::path::PathBuf;

use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use clap::Clap;

use crate::cgroups;
Expand Down Expand Up @@ -43,7 +43,10 @@ impl Resume {
}

let spec = container.spec()?;
let cgroups_path = utils::get_cgroup_path(&spec.linux.cgroups_path, &self.container_id);
let cgroups_path = utils::get_cgroup_path(
&spec.linux.context("no linux in spec")?.cgroups_path,
&self.container_id,
);
// create cgroup manager structure from the config at the path
let cmanager = cgroups::common::create_cgroup_manager(cgroups_path, systemd_cgroup)?;
// resume the frozen container
Expand Down
14 changes: 7 additions & 7 deletions src/container/builder_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl ContainerBuilderImpl {
fn run_container(&mut self) -> Result<()> {
prctl::set_dumpable(false).unwrap();

let linux = &self.spec.linux;
let linux = self.spec.linux.as_ref().context("no linux in spec")?;
let cgroups_path = utils::get_cgroup_path(&linux.cgroups_path, &self.container_id);
let cmanager = cgroups::common::create_cgroup_manager(&cgroups_path, self.use_systemd)?;
let namespaces: Namespaces = linux.namespaces.clone().into();
Expand Down Expand Up @@ -124,12 +124,12 @@ fn container_init(
notify_name: PathBuf,
child: &mut child::ChildProcess,
) -> Result<()> {
let linux = &spec.linux;
let linux = &spec.linux.as_ref().context("no linux in spec")?;
let namespaces: Namespaces = linux.namespaces.clone().into();
// need to create the notify socket before we pivot root, since the unix
// domain socket used here is outside of the rootfs of container
let mut notify_socket: NotifyListener = NotifyListener::new(&notify_name)?;
let proc = &spec.process;
let proc = &spec.process.as_ref().context("no process in spec")?;

// if Out-of-memory score adjustment is set in specification. set the score
// value for the current process check
Expand All @@ -156,7 +156,7 @@ fn container_init(
}

// set limits and namespaces to the process
for rlimit in spec.process.rlimits.iter() {
for rlimit in proc.rlimits.iter() {
command.set_rlimit(rlimit).context("failed to set rlimit")?;
}

Expand All @@ -172,7 +172,7 @@ fn container_init(
// join existing namespaces
namespaces.apply_setns()?;

command.set_hostname(spec.hostname.as_str())?;
command.set_hostname(&spec.hostname.as_ref().context("no hostname in spec")?)?;

if proc.no_new_privileges {
let _ = prctl::set_no_new_privileges(true);
Expand Down Expand Up @@ -206,8 +206,8 @@ fn container_init(
// listing on the notify socket for container start command
notify_socket.wait_for_container_start()?;

let args: &Vec<String> = &spec.process.args;
let envs: &Vec<String> = &spec.process.env;
let args: &Vec<String> = &proc.args;
let envs: &Vec<String> = &proc.env;
utils::do_exec(&args[0], args, envs)?;

// After do_exec is called, the process is replaced with the container
Expand Down
4 changes: 2 additions & 2 deletions src/container/init_builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use nix::unistd;
use oci_spec::Spec;
use rootless::detect_rootless;
Expand Down Expand Up @@ -49,7 +49,7 @@ impl InitContainerBuilder {
unistd::chdir(&*container_dir)?;
let notify_path = container_dir.join(NOTIFY_FILE);
// convert path of root file system of the container to absolute path
let rootfs = fs::canonicalize(&spec.root.path)?;
let rootfs = fs::canonicalize(&spec.root.as_ref().context("no root in spec")?.path)?;

// if socket file path is given in commandline options,
// get file descriptors of console socket
Expand Down
Loading

0 comments on commit ade1c17

Please sign in to comment.