Skip to content

Commit

Permalink
feat: oci compliance work (#85)
Browse files Browse the repository at this point in the history
* chore: rework oci crate to be more composable

* feat: image pull is now internally explicit

* feat: utilize vfs for assembling oci images

* feat: rework oci to preserve permissions via a vfs
  • Loading branch information
azenla authored Apr 15, 2024
1 parent 24c71e9 commit 89055ef
Show file tree
Hide file tree
Showing 33 changed files with 1,499 additions and 1,163 deletions.
39 changes: 19 additions & 20 deletions crates/ctl/src/cli/destroy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,30 @@ impl DestroyCommand {
async fn wait_guest_destroyed(id: &str, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
if let Event::GuestChanged(changed) = event {
let Some(guest) = changed.guest else {
continue;
};
let Event::GuestChanged(changed) = event;
let Some(guest) = changed.guest else {
continue;
};

if guest.id != id {
continue;
}
if guest.id != id {
continue;
}

let Some(state) = guest.state else {
continue;
};
let Some(state) = guest.state else {
continue;
};

if let Some(ref error) = state.error_info {
if state.status() == GuestStatus::Failed {
error!("destroy failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
}
if let Some(ref error) = state.error_info {
if state.status() == GuestStatus::Failed {
error!("destroy failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
}
}

if state.status() == GuestStatus::Destroyed {
std::process::exit(0);
}
if state.status() == GuestStatus::Destroyed {
std::process::exit(0);
}
}
Ok(())
Expand Down
130 changes: 23 additions & 107 deletions crates/ctl/src/cli/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,32 @@ use std::collections::HashMap;

use anyhow::Result;
use clap::Parser;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use krata::{
events::EventStream,
v1::{
common::{
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestStatus,
GuestTaskSpec, GuestTaskSpecEnvVar,
guest_image_spec::Image, GuestImageSpec, GuestOciImageFormat, GuestOciImageSpec,
GuestSpec, GuestStatus, GuestTaskSpec, GuestTaskSpecEnvVar,
},
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
CreateGuestRequest, OciProgressEventLayerPhase, OciProgressEventPhase,
CreateGuestRequest, PullImageRequest,
},
},
};
use log::error;
use tokio::select;
use tonic::{transport::Channel, Request};

use crate::console::StdioConsoleStream;
use crate::{console::StdioConsoleStream, pull::pull_interactive_progress};

use super::pull::PullImageFormat;

#[derive(Parser)]
#[command(about = "Launch a new guest")]
pub struct LauchCommand {
#[arg(short = 'S', long, default_value = "squashfs", help = "Image format")]
image_format: PullImageFormat,
#[arg(short, long, help = "Name of the guest")]
name: Option<String>,
#[arg(
Expand Down Expand Up @@ -71,11 +74,25 @@ impl LauchCommand {
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let response = client
.pull_image(PullImageRequest {
image: self.oci.clone(),
format: match self.image_format {
PullImageFormat::Squashfs => GuestOciImageFormat::Squashfs.into(),
PullImageFormat::Erofs => GuestOciImageFormat::Erofs.into(),
},
})
.await?;
let reply = pull_interactive_progress(response.into_inner()).await?;

let request = CreateGuestRequest {
spec: Some(GuestSpec {
name: self.name.unwrap_or_default(),
image: Some(GuestImageSpec {
image: Some(Image::Oci(GuestOciImageSpec { image: self.oci })),
image: Some(Image::Oci(GuestOciImageSpec {
digest: reply.digest,
format: reply.format,
})),
}),
vcpus: self.cpus,
mem: self.mem,
Expand Down Expand Up @@ -126,14 +143,9 @@ impl LauchCommand {

async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
let mut multi_progress: Option<(MultiProgress, HashMap<String, ProgressBar>)> = None;
while let Ok(event) = stream.recv().await {
match event {
Event::GuestChanged(changed) => {
if let Some((multi_progress, _)) = multi_progress.as_mut() {
let _ = multi_progress.clear();
}

let Some(guest) = changed.guest else {
continue;
};
Expand Down Expand Up @@ -164,102 +176,6 @@ async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
break;
}
}

Event::OciProgress(oci) => {
if multi_progress.is_none() {
multi_progress = Some((MultiProgress::new(), HashMap::new()));
}

let Some((multi_progress, progresses)) = multi_progress.as_mut() else {
continue;
};

match oci.phase() {
OciProgressEventPhase::Resolved
| OciProgressEventPhase::ConfigAcquire
| OciProgressEventPhase::LayerAcquire => {
if progresses.is_empty() && !oci.layers.is_empty() {
for layer in &oci.layers {
let bar = ProgressBar::new(layer.total);
bar.set_style(
ProgressStyle::with_template("{msg} {wide_bar}").unwrap(),
);
progresses.insert(layer.id.clone(), bar.clone());
multi_progress.add(bar);
}
}

for layer in oci.layers {
let Some(progress) = progresses.get_mut(&layer.id) else {
continue;
};

let phase = match layer.phase() {
OciProgressEventLayerPhase::Waiting => "waiting",
OciProgressEventLayerPhase::Downloading => "downloading",
OciProgressEventLayerPhase::Downloaded => "downloaded",
OciProgressEventLayerPhase::Extracting => "extracting",
OciProgressEventLayerPhase::Extracted => "extracted",
_ => "unknown",
};

let simple = if let Some((_, hash)) = layer.id.split_once(':') {
hash
} else {
id
};
let simple = if simple.len() > 10 {
&simple[0..10]
} else {
simple
};
let message = format!("{:width$} {}", simple, phase, width = 10);

if message != progress.message() {
progress.set_message(message);
}

progress.update(|state| {
state.set_len(layer.total);
state.set_pos(layer.value);
});
}
}

OciProgressEventPhase::Packing => {
for (key, bar) in &mut *progresses {
if key == "packing" {
continue;
}
bar.finish_and_clear();
multi_progress.remove(bar);
}
progresses.retain(|k, _| k == "packing");
if progresses.is_empty() {
let progress = ProgressBar::new(100);
progress.set_message("packing");
progress.set_style(
ProgressStyle::with_template("{msg} {wide_bar}").unwrap(),
);
progresses.insert("packing".to_string(), progress);
}
let Some(progress) = progresses.get("packing") else {
continue;
};

progress.update(|state| {
state.set_len(oci.total);
state.set_pos(oci.value);
});
}

_ => {}
}

for progress in progresses {
progress.1.tick();
}
}
}
}
Ok(())
Expand Down
8 changes: 7 additions & 1 deletion crates/ctl/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod launch;
pub mod list;
pub mod logs;
pub mod metrics;
pub mod pull;
pub mod resolve;
pub mod top;
pub mod watch;
Expand All @@ -21,7 +22,7 @@ use tonic::{transport::Channel, Request};
use self::{
attach::AttachCommand, destroy::DestroyCommand, idm_snoop::IdmSnoopCommand,
launch::LauchCommand, list::ListCommand, logs::LogsCommand, metrics::MetricsCommand,
resolve::ResolveCommand, top::TopCommand, watch::WatchCommand,
pull::PullCommand, resolve::ResolveCommand, top::TopCommand, watch::WatchCommand,
};

#[derive(Parser)]
Expand All @@ -48,6 +49,7 @@ pub enum Commands {
Destroy(DestroyCommand),
List(ListCommand),
Attach(AttachCommand),
Pull(PullCommand),
Logs(LogsCommand),
Watch(WatchCommand),
Resolve(ResolveCommand),
Expand Down Expand Up @@ -101,6 +103,10 @@ impl ControlCommand {
Commands::Top(top) => {
top.run(client, events).await?;
}

Commands::Pull(pull) => {
pull.run(client).await?;
}
}
Ok(())
}
Expand Down
42 changes: 42 additions & 0 deletions crates/ctl/src/cli/pull.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use anyhow::Result;
use clap::{Parser, ValueEnum};
use krata::v1::{
common::GuestOciImageFormat,
control::{control_service_client::ControlServiceClient, PullImageRequest},
};

use tonic::transport::Channel;

use crate::pull::pull_interactive_progress;

#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
pub enum PullImageFormat {
Squashfs,
Erofs,
}

#[derive(Parser)]
#[command(about = "Pull an image into the cache")]
pub struct PullCommand {
#[arg(help = "Image name")]
image: String,
#[arg(short = 's', long, default_value = "squashfs", help = "Image format")]
image_format: PullImageFormat,
}

impl PullCommand {
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
let response = client
.pull_image(PullImageRequest {
image: self.image.clone(),
format: match self.image_format {
PullImageFormat::Squashfs => GuestOciImageFormat::Squashfs.into(),
PullImageFormat::Erofs => GuestOciImageFormat::Erofs.into(),
},
})
.await?;
let reply = pull_interactive_progress(response.into_inner()).await?;
println!("{}", reply.digest);
Ok(())
}
}
7 changes: 3 additions & 4 deletions crates/ctl/src/cli/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ impl WatchCommand {
loop {
let event = stream.recv().await?;

if let Event::GuestChanged(changed) = event {
let guest = changed.guest.clone();
self.print_event("guest.changed", changed, guest)?;
}
let Event::GuestChanged(changed) = event;
let guest = changed.guest.clone();
self.print_event("guest.changed", changed, guest)?;
}
}

Expand Down
33 changes: 16 additions & 17 deletions crates/ctl/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,26 @@ impl StdioConsoleStream {
Ok(tokio::task::spawn(async move {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
if let Event::GuestChanged(changed) = event {
let Some(guest) = changed.guest else {
continue;
};
let Event::GuestChanged(changed) = event;
let Some(guest) = changed.guest else {
continue;
};

let Some(state) = guest.state else {
continue;
};
let Some(state) = guest.state else {
continue;
};

if guest.id != id {
continue;
}
if guest.id != id {
continue;
}

if let Some(exit_info) = state.exit_info {
return Some(exit_info.code);
}
if let Some(exit_info) = state.exit_info {
return Some(exit_info.code);
}

let status = state.status();
if status == GuestStatus::Destroying || status == GuestStatus::Destroyed {
return Some(10);
}
let status = state.status();
if status == GuestStatus::Destroying || status == GuestStatus::Destroyed {
return Some(10);
}
}
None
Expand Down
1 change: 1 addition & 0 deletions crates/ctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod cli;
pub mod console;
pub mod format;
pub mod metrics;
pub mod pull;
3 changes: 1 addition & 2 deletions crates/ctl/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl MultiMetricCollector {
let collect = select! {
x = events.recv() => match x {
Ok(event) => {
if let Event::GuestChanged(changed) = event {
let Event::GuestChanged(changed) = event;
let Some(guest) = changed.guest else {
continue;
};
Expand All @@ -93,7 +93,6 @@ impl MultiMetricCollector {
if state.status() != GuestStatus::Destroying {
guests.push(guest);
}
}
false
},

Expand Down
Loading

0 comments on commit 89055ef

Please sign in to comment.