mirror of
https://github.com/michael-yuji/xc.git
synced 2026-03-26 18:56:28 +01:00
bug fixes
This commit is contained in:
@@ -548,7 +548,6 @@ impl Session {
|
||||
// if there is none, just register this manifest
|
||||
let manifest_variant = self.query_manifest(tag).await?;
|
||||
let manifest_list = match manifest_variant {
|
||||
None => todo!(),
|
||||
Some(ManifestVariant::List(mut manifest_list)) => {
|
||||
let mut manifests = manifest_list
|
||||
.manifests
|
||||
|
||||
@@ -26,6 +26,7 @@ mod util;
|
||||
|
||||
use crate::util::*;
|
||||
use clap::{Parser, Subcommand};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::process::Command;
|
||||
@@ -216,18 +217,21 @@ fn zfs_dataset_get_mountpoint(dataset: &String) -> Result<Option<String>, std::i
|
||||
}
|
||||
|
||||
pub fn do_create(args: CreateArgs) -> Result<(), std::io::Error> {
|
||||
let mut output: Box<dyn Write> = match args.file.as_str() {
|
||||
let output: Box<dyn Write> = match args.file.as_str() {
|
||||
"-" => Box::new(std::io::stdout()),
|
||||
path => Box::new(File::create(path)?),
|
||||
};
|
||||
|
||||
output = match args.compression {
|
||||
CompressionType::Zstd => Box::new(ZstdEncoder::new(output, 3)?.auto_finish()),
|
||||
let sha256 = std::rc::Rc::new(std::cell::RefCell::new(Sha256::new()));
|
||||
let handle = DigestSink::<Box<dyn Write>>::new(output, sha256.clone());
|
||||
|
||||
let mut output: Box<dyn Write> = match args.compression {
|
||||
CompressionType::Zstd => Box::new(ZstdEncoder::new(handle, 3)?.auto_finish()),
|
||||
CompressionType::Gzip => Box::new(flate2::write::GzEncoder::new(
|
||||
output,
|
||||
handle,
|
||||
flate2::Compression::default(),
|
||||
)),
|
||||
_ => output,
|
||||
_ => Box::new(handle),
|
||||
};
|
||||
|
||||
if args.zfs_diff {
|
||||
@@ -301,7 +305,7 @@ pub fn do_create(args: CreateArgs) -> Result<(), std::io::Error> {
|
||||
&removing,
|
||||
&mut output,
|
||||
args.write_to_stderr,
|
||||
)
|
||||
)?;
|
||||
} else {
|
||||
create_tar(
|
||||
args.without_oci,
|
||||
@@ -311,8 +315,18 @@ pub fn do_create(args: CreateArgs) -> Result<(), std::io::Error> {
|
||||
&args.remove,
|
||||
&mut output,
|
||||
args.write_to_stderr,
|
||||
)
|
||||
)?;
|
||||
}
|
||||
|
||||
drop(output);
|
||||
let digest: [u8; 32] = sha256.borrow().clone().finalize().into();
|
||||
|
||||
if !args.write_to_stderr {
|
||||
println!("sha256:{}", hex(digest));
|
||||
} else {
|
||||
eprintln!("sha256:{}", hex(digest));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn do_extract(args: ExtractArgs) -> Result<(), std::io::Error> {
|
||||
@@ -377,6 +391,7 @@ fn create_tar<W: Write>(
|
||||
.arg("-T-") //.args(paths)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()?;
|
||||
|
||||
let mut child_stdin = child.stdin.take().unwrap();
|
||||
|
||||
@@ -298,9 +298,10 @@ pub struct Summary {
|
||||
}
|
||||
|
||||
pub fn remove_path(p: std::path::PathBuf) -> std::io::Result<()> {
|
||||
/*
|
||||
eprintln!("enter remove path");
|
||||
log::warn!("removing: {p:?}");
|
||||
|
||||
*/
|
||||
let path = std::path::PathBuf::from(&p);
|
||||
if path.exists() {
|
||||
if path.is_dir() {
|
||||
@@ -308,10 +309,10 @@ pub fn remove_path(p: std::path::PathBuf) -> std::io::Result<()> {
|
||||
} else {
|
||||
_ = std::fs::remove_file(path);
|
||||
}
|
||||
eprintln!("exit remove path");
|
||||
// eprintln!("exit remove path");
|
||||
Ok(())
|
||||
} else {
|
||||
eprintln!("exit remove path");
|
||||
// eprintln!("exit remove path");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -90,6 +90,33 @@ impl<R: Read> Read for DigestReaderHandle<R> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct DigestSink<W: Write> {
|
||||
sink: W,
|
||||
digest: Rc<RefCell<Sha256>>,
|
||||
}
|
||||
|
||||
//pub struct DigestSinkHandle<W: Write>(pub Rc<RefCell<DigestSink<W>>>);
|
||||
|
||||
impl<T: Write> DigestSink<T> {
|
||||
pub fn new<W: Write>(sink: W, digest: Rc<RefCell<Sha256>>) -> DigestSink<W> {
|
||||
DigestSink {
|
||||
sink,
|
||||
digest,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<W: Write> Write for DigestSink<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||
let size = self.sink.write(buf)?;
|
||||
self.digest.borrow_mut().update(&buf[..size]);
|
||||
Ok(size)
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||
self.sink.flush()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DigestWriter<W: Write> {
|
||||
sink: W,
|
||||
digest: Sha256,
|
||||
@@ -110,11 +137,12 @@ impl<T: Write> DigestWriter<T> {
|
||||
|
||||
impl<W: Write> Write for DigestWriter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||
self.digest.update(buf);
|
||||
self.sink.write(buf)
|
||||
let size = self.sink.write(buf)?;
|
||||
self.digest.update(&buf[..size]);
|
||||
Ok(size)
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
self.sink.flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,16 @@ impl std::fmt::Display for Var {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Var {
|
||||
type Err = std::io::Error;
|
||||
fn from_str(s: &str) -> Result<Var, Self::Err> {
|
||||
Var::new(s).ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"must conform to IEEE Std 1003.1-2001",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Var {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
// 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.
|
||||
pub mod add_env;
|
||||
pub mod copy;
|
||||
pub mod from;
|
||||
pub mod run;
|
||||
@@ -33,14 +34,14 @@ use anyhow::Context;
|
||||
use std::collections::HashMap;
|
||||
use varutil::string_interpolation::{InterpolatedString, Var};
|
||||
use xc::models::jail_image::{JailConfig, SpecialMount};
|
||||
use xc::models::{EntryPoint, MountSpec, SystemVPropValue};
|
||||
use xc::models::{EntryPoint, EnvSpec, MountSpec, SystemVPropValue};
|
||||
|
||||
pub(crate) trait Directive: Sized {
|
||||
fn from_action(action: &Action) -> Result<Self, anyhow::Error>;
|
||||
fn run_in_context(&self, context: &mut JailContext) -> Result<(), anyhow::Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ConfigMod {
|
||||
Allow(Vec<String>),
|
||||
ReplaceAllow(Vec<String>),
|
||||
@@ -55,6 +56,7 @@ pub(crate) enum ConfigMod {
|
||||
Volume(String, MountSpec),
|
||||
Mount(String, String),
|
||||
SysV(Vec<String>),
|
||||
AddEnv(Var, EnvSpec),
|
||||
}
|
||||
|
||||
impl ConfigMod {
|
||||
@@ -62,6 +64,9 @@ impl ConfigMod {
|
||||
match self {
|
||||
Self::NoInit => config.init = Vec::new(),
|
||||
Self::NoDeinit => config.deinit = Vec::new(),
|
||||
Self::AddEnv(variable, spec) => {
|
||||
config.envs.insert(variable.clone(), spec.clone());
|
||||
}
|
||||
Self::Allow(allows) => {
|
||||
for allow in allows.iter() {
|
||||
if let Some(param) = allow.strip_prefix('-') {
|
||||
@@ -232,10 +237,7 @@ impl Directive for ConfigMod {
|
||||
curr = args_iter.next();
|
||||
}
|
||||
|
||||
let arg0 = curr
|
||||
.as_ref()
|
||||
.clone()
|
||||
.context("entry point requires one variable")?;
|
||||
let arg0 = curr.as_ref().context("entry point requires one variable")?;
|
||||
Ok(ConfigMod::EntryPoint(entry_point, arg0.to_string(), envs))
|
||||
}
|
||||
"CMD" => {
|
||||
@@ -270,3 +272,52 @@ impl Directive for ConfigMod {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_directive_entrypoint_no_env() {
|
||||
let action = Action {
|
||||
directive_name: "ENTRYPOINT".to_string(),
|
||||
directive_args: HashMap::new(),
|
||||
args: vec!["arg0".to_string()],
|
||||
heredoc: None,
|
||||
};
|
||||
let config_mod = ConfigMod::from_action(&action).expect("cannot parse");
|
||||
assert_eq!(
|
||||
config_mod,
|
||||
ConfigMod::EntryPoint("main".to_string(), "arg0".to_string(), HashMap::new())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_directive_entrypoint_with_env() {
|
||||
let action = Action {
|
||||
directive_name: "ENTRYPOINT".to_string(),
|
||||
directive_args: HashMap::new(),
|
||||
args: vec![
|
||||
"A=BCD".to_string(),
|
||||
"PATH=/usr/bin:/usr/sbin".to_string(),
|
||||
"arg0".to_string(),
|
||||
],
|
||||
heredoc: None,
|
||||
};
|
||||
let config_mod = ConfigMod::from_action(&action).expect("cannot parse");
|
||||
let mut expect_hashmap = HashMap::new();
|
||||
|
||||
expect_hashmap.insert(
|
||||
Var::new("A").unwrap(),
|
||||
InterpolatedString::new("BCD").unwrap(),
|
||||
);
|
||||
expect_hashmap.insert(
|
||||
Var::new("PATH").unwrap(),
|
||||
InterpolatedString::new("/usr/bin:/usr/sbin").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
config_mod,
|
||||
ConfigMod::EntryPoint("main".to_string(), "arg0".to_string(), expect_hashmap)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +255,7 @@ fn main() -> Result<(), ActionError> {
|
||||
empty_dns,
|
||||
output_inplace,
|
||||
} => {
|
||||
use crate::jailfile::directives::add_env::*;
|
||||
use crate::jailfile::directives::copy::*;
|
||||
use crate::jailfile::directives::from::*;
|
||||
use crate::jailfile::directives::run::*;
|
||||
@@ -311,19 +312,25 @@ fn main() -> Result<(), ActionError> {
|
||||
let mut context = JailContext::new(conn, dns, net_req, output_inplace);
|
||||
|
||||
for action in actions.iter() {
|
||||
if action.directive_name == "RUN" {
|
||||
let directive = RunDirective::from_action(action)?;
|
||||
directive.run_in_context(&mut context)?;
|
||||
} else if action.directive_name == "FROM" {
|
||||
macro_rules! do_directive {
|
||||
($name:expr, $tpe:ty) => {
|
||||
if action.directive_name == $name {
|
||||
let directive = <$tpe>::from_action(action)?;
|
||||
directive.run_in_context(&mut context)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
do_directive!("RUN", RunDirective);
|
||||
do_directive!("COPY", CopyDirective);
|
||||
do_directive!("VOLUME", VolumeDirective);
|
||||
do_directive!("ADDENV", AddEnvDirective);
|
||||
|
||||
if action.directive_name == "FROM" {
|
||||
let directive = FromDirective::from_action(action)?;
|
||||
directive.run_in_context(&mut context)?;
|
||||
std::thread::sleep_ms(500);
|
||||
} 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())
|
||||
{
|
||||
|
||||
@@ -369,25 +369,32 @@ impl ProcessRunner {
|
||||
cmd.jwork_dir(work_dir);
|
||||
}
|
||||
|
||||
let spawn_info = match &exec.output_mode {
|
||||
let spawn_info_result = match &exec.output_mode {
|
||||
StdioMode::Terminal => {
|
||||
let socket_path = format!("/var/run/xc.{}.{}", self.container.id, id);
|
||||
let log_path = format!("/var/log/xc.{}.{}.log", self.container.id, id);
|
||||
spawn_process_pty(cmd, &log_path, &socket_path)?
|
||||
spawn_process_pty(cmd, &log_path, &socket_path)
|
||||
}
|
||||
StdioMode::Files { stdout, stderr } => spawn_process_files(&mut cmd, stdout, stderr)?,
|
||||
StdioMode::Files { stdout, stderr } => spawn_process_files(&mut cmd, stdout, stderr),
|
||||
StdioMode::Inherit => {
|
||||
let out_path = format!("/var/log/xc.{}.{}.out.log", self.container.id, id);
|
||||
let err_path = format!("/var/log/xc.{}.{}.err.log", self.container.id, id);
|
||||
spawn_process_files(&mut cmd, &Some(out_path), &Some(err_path))?
|
||||
spawn_process_files(&mut cmd, &Some(out_path), &Some(err_path))
|
||||
}
|
||||
StdioMode::Forward {
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
} => spawn_process_forward(&mut cmd, *stdin, *stdout, *stderr)?,
|
||||
} => spawn_process_forward(&mut cmd, *stdin, *stdout, *stderr),
|
||||
};
|
||||
|
||||
let spawn_info = spawn_info_result.map_err(|error| {
|
||||
if let Some(n) = notify.clone() {
|
||||
n.notify_waiters();
|
||||
}
|
||||
error
|
||||
})?;
|
||||
|
||||
let pid = spawn_info.pid;
|
||||
|
||||
tx.send_if_modified(|status| {
|
||||
|
||||
@@ -198,6 +198,12 @@ impl Default for JailImage {
|
||||
}
|
||||
|
||||
impl JailImage {
|
||||
pub fn os(&self) -> &str {
|
||||
&self.0.os
|
||||
}
|
||||
pub fn architecture(&self) -> &str {
|
||||
&self.0.architecture
|
||||
}
|
||||
pub fn chain_id(&self) -> Option<ChainId> {
|
||||
if self.0.rootfs.diff_ids.is_empty() {
|
||||
None
|
||||
|
||||
@@ -294,10 +294,18 @@ impl ServerContext {
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
let (diff_id, _digest) = {
|
||||
let mut results = std::str::from_utf8(&output.stdout).unwrap().trim().lines();
|
||||
let diff_id = results.next().expect("unexpected output");
|
||||
let digest = results.next().expect("unexpected output");
|
||||
eprintln!("diff_id: {diff_id}");
|
||||
(
|
||||
OciDigest::from_str(diff_id).unwrap(),
|
||||
OciDigest::from_str(digest).unwrap()
|
||||
)
|
||||
};
|
||||
zfs.destroy(format!("{running_dataset}@{commit_id}"), true, true, true)?;
|
||||
|
||||
let diff_id = std::str::from_utf8(&output.stderr).unwrap().trim();
|
||||
Ok(OciDigest::from_str(diff_id)?)
|
||||
Ok(diff_id)
|
||||
}
|
||||
|
||||
pub(crate) async fn do_commit(
|
||||
@@ -357,14 +365,19 @@ impl ServerContext {
|
||||
.output()
|
||||
.expect("fail to spawn ocitar");
|
||||
|
||||
let diff_id = {
|
||||
let diff_id = std::str::from_utf8(&output.stdout).unwrap().trim();
|
||||
let (diff_id, digest) = {
|
||||
let mut results = std::str::from_utf8(&output.stdout).unwrap().trim().lines();
|
||||
let diff_id = results.next().expect("unexpected output");
|
||||
let digest = results.next().expect("unexpected output");
|
||||
eprintln!("diff_id: {diff_id}");
|
||||
eprintln!("rename: {temp_file} -> {}/{diff_id}", config.layers_dir);
|
||||
std::fs::rename(temp_file, format!("{}/{diff_id}", config.layers_dir))?;
|
||||
OciDigest::from_str(diff_id).unwrap()
|
||||
eprintln!("rename: {temp_file} -> {}/{digest}", config.layers_dir);
|
||||
std::fs::rename(temp_file, format!("{}/{digest}", config.layers_dir))?;
|
||||
(
|
||||
OciDigest::from_str(diff_id).unwrap(),
|
||||
OciDigest::from_str(digest).unwrap()
|
||||
)
|
||||
};
|
||||
//
|
||||
|
||||
chain_id.consume_diff_id(oci_util::digest::DigestAlgorithm::Sha256, &diff_id);
|
||||
let new_name = format!("{}/{chain_id}", config.image_dataset);
|
||||
zfs.rename(&dst_dataset, new_name)?;
|
||||
@@ -377,7 +390,7 @@ impl ServerContext {
|
||||
.register_and_tag_manifest(name, tag, &manifest)
|
||||
.await;
|
||||
|
||||
context.map_diff_id(&diff_id, &diff_id, "plain").await?;
|
||||
context.map_diff_id(&diff_id, &digest, "zstd").await?;
|
||||
|
||||
Ok(commit_id)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ use oci_util::digest::OciDigest;
|
||||
use oci_util::distribution::client::*;
|
||||
use oci_util::image_reference::ImageReference;
|
||||
use oci_util::models::Descriptor;
|
||||
use oci_util::models::Platform;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -267,9 +268,23 @@ pub async fn push_image(
|
||||
state.push_manifest = true;
|
||||
Ok(())
|
||||
});
|
||||
session.register_manifest(&tag, &manifest).await?;
|
||||
|
||||
debug!("Registering manifest: {manifest:#?}");
|
||||
|
||||
let arch = record.manifest.architecture();
|
||||
let arch_tag = format!("{tag}-{arch}");
|
||||
let platform = Platform {
|
||||
os: record.manifest.os().to_string(),
|
||||
architecture: arch.to_string(),
|
||||
os_version: None,
|
||||
os_features: Vec::new(),
|
||||
variant: None,
|
||||
features: Vec::new(),
|
||||
};
|
||||
let descriptor = session.register_manifest(&arch_tag, &manifest).await?;
|
||||
session
|
||||
.merge_manifest_list(&descriptor, &platform, &tag)
|
||||
.await?;
|
||||
|
||||
_ = emitter.use_try(|state| {
|
||||
state.done = true;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user