Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Allow using different container engines #20

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
7 changes: 5 additions & 2 deletions iguana-workflow/obs/iguana-workflow.spec
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
Name: iguana-workflow
Version: 0.0.0
Release: 0
Summary: Iguana initrd workflow parser and executor
Summary: Iguana workflow parser and executor
License: GPL-2.0-only
URL: https://github.com/openSUSE/iguana
Source0: %{name}-%{version}.tar.xz
Source1: vendor.tar.xz
Source2: cargo_config
BuildRequires: cargo-packaging
%if 0%{?is_opensuse}
Requires: crun
%endif
Requires: podman

%description
An iguana workflow, inspired by GitHub workflow, parser and executor. Part of the iguana initrd.
Expand All @@ -49,4 +53,3 @@ cp %{SOURCE2} .cargo/config
%{_bindir}/iguana-workflow

%changelog

43 changes: 32 additions & 11 deletions iguana-workflow/src/engines.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,52 @@
/// Container engines traits
use std::collections::{HashMap, HashSet};
use std::{collections::{HashMap, HashSet}};

use crate::workflow::{Container, WorkflowOptions};

use self::{podman::Podman, crun::CRun, skopeo::Skopeo};

pub(crate) mod podman;

pub(crate) mod crun;
pub(crate) mod skopeo;
pub(crate) mod local_volumes;

pub trait Availability {
fn is_available() -> Result<(), ()>;
}

pub trait ImageOps {
fn prepare_image(&self, image: &str, dry_run: bool) -> Result<(), String>;
fn clean_image(&self, image: &str, opts: &WorkflowOptions) -> Result<(), String>;
fn prepare_image(&mut self, image: &str, dry_run: bool) -> Result<(), String>;
fn clean_image(&mut self, image: &str, opts: &WorkflowOptions) -> Result<(), String>;
}

pub trait VolumeOps {
fn prepare_volume(&self, volume_src: &str, opts: &WorkflowOptions) -> Result<(), String>;
fn clean_volumes(&self, volumes: &HashSet<&str>, opts: &WorkflowOptions) -> Result<(), String>;
fn prepare_volume(&mut self, volume_src: &str, opts: &WorkflowOptions) -> Result<(), String>;
fn clean_volumes(&mut self, volumes: &HashSet<&str>, opts: &WorkflowOptions) -> Result<(), String>;
}
pub trait ContainerOps {
fn run_container(
&self,
&mut self,
container: &Container,
is_service: bool,
env: HashMap<String, String>,
opts: &WorkflowOptions,
) -> Result<(), String>;
fn stop_container(&self, name: &str, opts: &WorkflowOptions) -> Result<(), String>;
fn stop_container(&mut self, name: &str, opts: &WorkflowOptions) -> Result<(), String>;
}

// pub fn get_engine() -> dyn ContainerOps {
// let podman = Podman;
// return podman;
// }
pub trait ContainerEngine: ImageOps + VolumeOps + ContainerOps {}
impl<T: Availability + ImageOps + VolumeOps + ContainerOps> ContainerEngine for T {}

pub fn get_engine() -> Result<Box<dyn ContainerEngine>, String> {
if CRun::is_available().is_ok() {
return Ok(Box::new(CRun));
}
// if Runc::is_avaiable().isok() {
// return Ok(Box::new(RunC));
// }
if Podman::is_available().is_ok() {
return Ok(Box::new(Podman));
}
return Err("No supported container engine found!".to_string());
}
77 changes: 77 additions & 0 deletions iguana-workflow/src/engines/crun.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/// CRun container engine implementation
///
/// Requires skopeo and local_volumes engines

use log::debug;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::process::Command;

use crate::workflow::{Container, WorkflowOptions};

use super::{Availability, ContainerOps, ImageOps, VolumeOps};
use super::skopeo::Skopeo;
use super::local_volumes::LocalVolumes;

pub struct CRun;

const CRUN_BIN: &str = "/usr/bin/crun";

impl Availability for CRun{
fn is_available() -> Result<(), ()> {
debug!("Checking crun availability");
if Skopeo::is_available().is_err() {
return Err(())
};

if LocalVolumes::is_available().is_err() {
return Err(())
};

if Path::is_file(Path::new(CRUN_BIN)) {
debug!("crun is available");
return Ok(())
};
debug!("crun binary not available");
return Err(())
}
}

impl ImageOps for CRun {
fn clean_image(&mut self, image: &str, opts: &WorkflowOptions) -> Result<(), String> {
let mut skopeo = Skopeo::new();
return skopeo.clean_image(image, opts)
}
fn prepare_image(&mut self, image: &str, dry_run: bool) -> Result<(), String> {
let mut skopeo = Skopeo::new();
return skopeo.prepare_image(image, dry_run)
}
}

impl VolumeOps for CRun {
fn clean_volumes(&mut self, volumes: &HashSet<&str>, opts: &WorkflowOptions) -> Result<(), String> {
let mut local_volumes = LocalVolumes::new();
return local_volumes.clean_volumes(volumes, opts)
}

fn prepare_volume(&mut self, volume_src: &str, opts: &WorkflowOptions) -> Result<(), String> {
let mut local_volumes = LocalVolumes::new();
return local_volumes.prepare_volume(volume_src, opts)
}
}

impl ContainerOps for CRun {
fn run_container(
&mut self,
container: &Container,
is_service: bool,
env: HashMap<String, String>,
opts: &WorkflowOptions,
) -> Result<(), String> {
Err("Not implemented".to_string())
}

fn stop_container(&mut self, name: &str, opts: &WorkflowOptions) -> Result<(), String> {
Err("Not implemented".to_string())
}
}
104 changes: 104 additions & 0 deletions iguana-workflow/src/engines/local_volumes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use log::debug;
use std::collections::HashMap;
use std::collections::{HashSet, hash_map::DefaultHasher};
use std::fs::{create_dir_all, remove_dir_all};
use std::hash::{Hash, Hasher};
use std::path::Path;

use super::{Availability, VolumeOps};
use crate::workflow::WorkflowOptions;

const VOLUME_BASEDIR: &str = "/run/iguana-volumes";

fn create_hash(msg: &str) -> u64 {
let mut hasher = DefaultHasher::new();
msg.hash(&mut hasher);
hasher.finish()
}

struct Volume {
path: String,
created: bool,
usage: u8
}

pub struct LocalVolumes {
// Volume list is hashed by volume name and value is directory of the volume
volume_list: HashMap<u64, Volume>
}

impl LocalVolumes {
pub fn new() -> LocalVolumes {
LocalVolumes { volume_list: HashMap::new() }
}
}

impl Availability for LocalVolumes{
fn is_available() -> Result<(), ()> {
debug!("Local volumes available");
return Ok(())
}
}

impl VolumeOps for LocalVolumes {
fn prepare_volume(&mut self, name: &str, opts: &WorkflowOptions) -> Result<(), String> {
let dry_run = opts.dry_run;
let volume_name_hash = create_hash(name);
if let Some(volume) = self.volume_list.get_mut(&volume_name_hash) {
volume.usage += 1;
debug!("Reusing volume {name}");
return Ok(())
}
let vol_path = format!("{VOLUME_BASEDIR}/{volume_name_hash}");
if Path::new(&vol_path).is_dir() {
debug!("Volume {name} is existing directory");
let vol = Volume {
path: vol_path,
created: false,
usage: 1
};
self.volume_list.insert(volume_name_hash, vol);
return Ok(())
}
// Create new dir under VOLUME_BASEDIR
debug!("Creating dir for volume: {vol_path}");
if !dry_run {
if let Err(e) = create_dir_all(&vol_path) {
return Err(e.to_string());
}
}

let vol = Volume {
path: vol_path,
created: true,
usage: 1
};
self.volume_list.insert(volume_name_hash, vol);
Ok(())
}

fn clean_volumes(&mut self, volumes: &HashSet<&str>, opts: &WorkflowOptions) -> Result<(), String> {
for volume_name in volumes {
let volume_name_hash = create_hash(volume_name);
if let Some(vol) = self.volume_list.get_mut(&volume_name_hash) {
if vol.usage > 1 {
debug!("Volume {volume_name} is shared volume, decreasing usage");
vol.usage -= 1;
continue;
}
if vol.created {
debug!("Removing files for volume {volume_name}");
if !opts.dry_run {
if let Err(e) = remove_dir_all(&vol.path) {
return Err(e.to_string());
}
}
}
self.volume_list.remove(&volume_name_hash);
continue;
}
return Err(format!("Volume mapping not found for volume {volume_name}"))
}
Ok(())
}
}
Loading