Skip to content

Commit

Permalink
oci-runtime-generate: Implement JSON generation for OCI runtime
Browse files Browse the repository at this point in the history
In cdi-go, the oci-runtime-tools generate tool is used to
generate configuration JSON for the OCI runtime. However,
since it is not possible to use existing libraries like cdi-go,
we had to implement this functionality ourselves.

Additionally, since the pr for oci-spec-rs
(youki-dev/oci-spec-rs#166) has been
merged but no release version exists yet, we'll utilize the below
method  for dependency management:
oci-spec = { git = "https://github.com/containers/oci-spec-rs.git", rev = "d338cf8", features = ["runtime"] }

Signed-off-by: Alex Lyn <[email protected]>
  • Loading branch information
Apokleos committed May 30, 2024
1 parent 29ec3f6 commit d3ebb6b
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ path = "src/bin/validate.rs" # Path to the source file
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
oci-spec = "0.6.4"
oci-spec = { git = "https://github.com/containers/oci-spec-rs.git", rev = "d338cf8", features = ["runtime"] }
anyhow = "1.0"
clap = "4.5.3"
serde_json = "1.0"
Expand Down
85 changes: 85 additions & 0 deletions src/generate/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::collections::HashMap;

use oci_spec::runtime::{Hooks, Linux, LinuxIntelRdt, LinuxResources, Mount, Process, Spec};

pub struct Generator {
pub config: Option<Spec>,
pub host_specific: bool,
pub env_map: HashMap<String, usize>,
}

impl Generator {
pub fn spec_gen(spec: Option<Spec>) -> Self {
Generator {
config: spec,
host_specific: false,
env_map: HashMap::new(),
}
}

pub fn init_config(&mut self) {
if self.config.is_none() {
self.config = Some(Spec::default());
}
}

pub fn init_config_process(&mut self) {
self.init_config();

if let Some(spec) = self.config.as_mut() {
if spec.process().is_none() {
spec.set_process(Some(Process::default()));
}
}
}

pub fn init_config_linux(&mut self) {
self.init_config();

if let Some(spec) = self.config.as_mut() {
if spec.linux().is_none() {
spec.set_linux(Some(Linux::default()));
}
}
}

pub fn init_config_linux_resources(&mut self) {
self.init_config_linux();

if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
if linux.resources().is_none() {
linux.set_resources(Some(LinuxResources::default()));
}
}
}

pub fn init_config_hooks(&mut self) {
self.init_config();

if let Some(spec) = self.config.as_mut() {
if spec.hooks().is_none() {
spec.set_hooks(Some(Hooks::default()));
}
}
}

pub fn init_config_mounts(&mut self) {
self.init_config();

if let Some(spec) = self.config.as_mut() {
if spec.mounts().is_none() {
spec.set_mounts(Some(vec![Mount::default()]));
}
}
}

pub fn init_config_linux_intel_rdt(&mut self) {
self.init_config_linux();

if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
if linux.intel_rdt().is_none() {
linux.set_intel_rdt(Some(LinuxIntelRdt::default()));
}
}
}
}
253 changes: 253 additions & 0 deletions src/generate/generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
use std::cmp::Ordering;
use std::path::PathBuf;

use oci_spec::runtime::{Hook, LinuxDevice, LinuxDeviceCgroup, LinuxDeviceType, Mount};

use super::config::Generator;

impl Generator {
// remove_device removes a device from g.config.linux.devices
pub fn remove_device(&mut self, path: &str) {
self.init_config_linux();
if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
if let Some(devices) = linux.devices_mut() {
if let Some(index) = devices
.iter()
.position(|device| device.path() == &PathBuf::from(path))
{
devices.remove(index);
}
}
}
}

// add_device adds a device into g.config.linux.devices
pub fn add_device(&mut self, device: LinuxDevice) {
self.init_config_linux();

if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
if let Some(devices) = linux.devices_mut() {
if let Some(index) = devices.iter().position(|dev| dev.path() == device.path()) {
devices[index] = device;
} else {
devices.push(device);
}
} else {
linux.set_devices(Some(vec![device]));
}
}
}

// add_linux_resources_device adds a device into g.config.linux.resources.devices
pub fn add_linux_resources_device(
&mut self,
allow: bool,
dev_type: LinuxDeviceType,
major: Option<i64>,
minor: Option<i64>,
access: Option<String>,
) {
self.init_config_linux_resources();
if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
if let Some(resource) = linux.resources_mut() {
if let Some(devices) = resource.devices_mut() {
let mut device = LinuxDeviceCgroup::default();
device.set_allow(allow);
device.set_typ(Some(dev_type));
device.set_major(major);
device.set_minor(minor);
device.set_access(access);

devices.push(device);
}
}
}
}

/// set Linux Intel RDT ClosID
pub fn set_linux_intel_rdt_clos_id(&mut self, clos_id: String) {
self.init_config_linux_intel_rdt();
if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
if let Some(intel_rdt) = linux.intel_rdt_mut() {
intel_rdt.set_clos_id(Some(clos_id));
}
}
}

// add_process_additional_gid adds an additional gid into g.config.process.additional_gids.
pub fn add_process_additional_gid(&mut self, gid: u32) {
self.init_config_process();
if let Some(process) = self.config.as_mut().unwrap().process_mut() {
if let Some(additional_gids) = process.user().additional_gids() {
let mut tmp_vec = additional_gids.clone();
if !additional_gids.contains(&gid) {
tmp_vec.push(gid)
}

process.user_mut().set_additional_gids(Some(tmp_vec));
}
}
}

pub fn add_multiple_process_env(&mut self, envs: &[String]) {
self.init_config_process();

if let Some(process) = self.config.as_mut().unwrap().process_mut() {
let mut env_vec: Vec<String> = process.env_mut().get_or_insert_with(Vec::new).to_vec();
for env in envs {
let split: Vec<&str> = env.splitn(2, '=').collect();
let key = split[0].to_string();
let idx = self.env_map.entry(key.clone()).or_insert(env_vec.len());
if let Some(elem) = env_vec.get_mut(*idx) {
*elem = env.clone();
} else {
env_vec.push(env.clone());
self.env_map.insert(key, env_vec.len() - 1);
}
}
}
}

// add_prestart_hook adds a prestart hook into g.config.hooks.prestart.
pub fn add_prestart_hook(&mut self, hook: Hook) {
self.init_config_hooks();
if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
if let Some(prestart_hooks) = hooks.prestart_mut() {
prestart_hooks.push(hook);
}
}
}

// add_poststop_hook adds a poststop hook into g.config.hooks.poststop.
pub fn add_poststop_hook(&mut self, hook: Hook) {
self.init_config_hooks();
if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
if let Some(poststop_hooks) = hooks.poststop_mut() {
poststop_hooks.push(hook);
}
}
}

// add_poststart_hook adds a poststart hook into g.config.hooks.poststart.
pub fn add_poststart_hook(&mut self, hook: Hook) {
self.init_config_hooks();
if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
if let Some(poststart_hooks) = hooks.poststart_mut() {
poststart_hooks.push(hook);
}
}
}

// add_createruntime_hook adds a create_runtime hook into g.config.hooks.create_runtime.
pub fn add_createruntime_hook(&mut self, hook: Hook) {
self.init_config_hooks();
if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
if let Some(create_runtime) = hooks.create_runtime_mut() {
create_runtime.push(hook);
}
}
}

// add_createcontainer_hook adds a create_container hook into g.config.hooks.create_container.
pub fn add_createcontainer_hook(&mut self, hook: Hook) {
self.init_config_hooks();
if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
if let Some(create_container) = hooks.create_container_mut() {
create_container.push(hook);
}
}
}

// add_start_container_hook adds a start container hook into g.config.hooks.start_container.
pub fn add_startcontainer_hook(&mut self, hook: Hook) {
self.init_config_hooks();
if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
if let Some(start_container) = hooks.start_container_mut() {
start_container.push(hook);
}
}
}

// remove_mount removes a mount point on the dest directory
pub fn remove_mount(&mut self, dest: &str) {
if let Some(mounts) = self.config.as_mut().unwrap().mounts_mut() {
if let Some(index) = mounts
.iter()
.position(|m| m.destination() == &PathBuf::from(dest))
{
mounts.remove(index);
}
}
}

// add_mount adds a mount into g.config.mounts.
pub fn add_mount(&mut self, mount: Mount) {
self.init_config_mounts();

if let Some(mounts) = self.config.as_mut().unwrap().mounts_mut() {
mounts.push(mount);
}
}

// sort_mounts sorts the mounts in the given OCI Spec.
pub fn sort_mounts(&mut self) {
if let Some(ref mut mounts) = self.config.as_mut().unwrap().mounts_mut() {
mounts.sort_by(|a, b| a.destination().cmp(b.destination()));
}
}

// list_mounts returns the list of mounts
pub fn list_mounts(&self) -> Option<&Vec<Mount>> {
self.config.as_ref().and_then(|spec| spec.mounts().as_ref())
}

// clear_mounts clear g.Config.Mounts
pub fn clear_mounts(&mut self) {
if let Some(spec) = self.config.as_mut() {
spec.set_mounts(None);
}
}
}

// OrderedMounts defines how to sort an OCI Spec Mount slice.
// This is the almost the same implementation sa used by CRI-O and Docker,
// with a minor tweak for stable sorting order (easier to test):
//
// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26
struct OrderedMounts(Vec<Mount>);

#[allow(dead_code)]
impl OrderedMounts {
fn new(mounts: Vec<Mount>) -> Self {
OrderedMounts(mounts)
}

// parts returns the number of parts in the destination of a mount. Used in sorting.
fn parts(&self, i: usize) -> usize {
self.0[i].destination().components().count()
}
}

impl Ord for OrderedMounts {
fn cmp(&self, other: &Self) -> Ordering {
let self_parts = self.parts(0);
let other_parts = other.parts(0);
self_parts
.cmp(&other_parts)
.then_with(|| self.0[0].destination().cmp(other.0[0].destination()))
}
}

impl PartialOrd for OrderedMounts {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl PartialEq for OrderedMounts {
fn eq(&self, other: &Self) -> bool {
self.parts(0) == other.parts(0) && self.0[0].destination() == other.0[0].destination()
}
}

impl Eq for OrderedMounts {}
7 changes: 7 additions & 0 deletions src/generate/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// In cdi-go, the oci-runtime-tools generate tool is used to generate configuration JSON for the OCI runtime.
/// However, since it is not possible to use existing libraries like cdi-go, we had to implement this functionality
/// ourselves. Taking this opportunity, we hope to make this version the starting point for expanding oci-runtime-tools-rs
/// and providing better support for the OCI runtime.
/// It is important to note that at this stage, our primary focus is on implementations related to our cdi-rs project.
pub mod config;
pub mod generator;
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod cache;
pub mod container_edits;
pub mod container_edits_unix;
pub mod device;
pub mod generate;
pub mod parser;
pub mod registry;
pub mod schema;
Expand Down

0 comments on commit d3ebb6b

Please sign in to comment.