mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-05 06:01:32 +00:00
feat: rework oci to preserve permissions via a vfs
This commit is contained in:
@ -1,5 +1,3 @@
|
|||||||
use std::{pin::Pin, str::FromStr};
|
|
||||||
|
|
||||||
use async_stream::try_stream;
|
use async_stream::try_stream;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use krata::{
|
use krata::{
|
||||||
@ -23,12 +21,10 @@ use krataoci::{
|
|||||||
packer::{service::OciPackerService, OciImagePacked, OciPackedFormat},
|
packer::{service::OciPackerService, OciImagePacked, OciPackedFormat},
|
||||||
progress::{OciProgress, OciProgressContext},
|
progress::{OciProgress, OciProgressContext},
|
||||||
};
|
};
|
||||||
|
use std::{pin::Pin, str::FromStr};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
select,
|
select,
|
||||||
sync::{
|
sync::mpsc::{channel, Sender},
|
||||||
broadcast,
|
|
||||||
mpsc::{channel, Sender},
|
|
||||||
},
|
|
||||||
task::JoinError,
|
task::JoinError,
|
||||||
};
|
};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
@ -94,7 +90,7 @@ enum ConsoleDataSelect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum PullImageSelect {
|
enum PullImageSelect {
|
||||||
Progress(Option<OciProgress>),
|
Progress(usize),
|
||||||
Completed(Result<Result<OciImagePacked, anyhow::Error>, JoinError>),
|
Completed(Result<Result<OciImagePacked, anyhow::Error>, JoinError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +366,7 @@ impl ControlService for DaemonControlService {
|
|||||||
GuestOciImageFormat::Squashfs => OciPackedFormat::Squashfs,
|
GuestOciImageFormat::Squashfs => OciPackedFormat::Squashfs,
|
||||||
GuestOciImageFormat::Erofs => OciPackedFormat::Erofs,
|
GuestOciImageFormat::Erofs => OciPackedFormat::Erofs,
|
||||||
};
|
};
|
||||||
let (sender, mut receiver) = broadcast::channel::<OciProgress>(100);
|
let (sender, mut receiver) = channel::<OciProgress>(100);
|
||||||
let context = OciProgressContext::new(sender);
|
let context = OciProgressContext::new(sender);
|
||||||
|
|
||||||
let our_packer = self.packer.clone();
|
let our_packer = self.packer.clone();
|
||||||
@ -380,19 +376,22 @@ impl ControlService for DaemonControlService {
|
|||||||
our_packer.request(name, format, context).await
|
our_packer.request(name, format, context).await
|
||||||
});
|
});
|
||||||
loop {
|
loop {
|
||||||
|
let mut progresses = Vec::new();
|
||||||
let what = select! {
|
let what = select! {
|
||||||
x = receiver.recv() => PullImageSelect::Progress(x.ok()),
|
x = receiver.recv_many(&mut progresses, 10) => PullImageSelect::Progress(x),
|
||||||
x = &mut task => PullImageSelect::Completed(x),
|
x = &mut task => PullImageSelect::Completed(x),
|
||||||
};
|
};
|
||||||
|
|
||||||
match what {
|
match what {
|
||||||
PullImageSelect::Progress(Some(progress)) => {
|
PullImageSelect::Progress(count) => {
|
||||||
let reply = PullImageReply {
|
if count > 0 {
|
||||||
progress: Some(convert_oci_progress(progress)),
|
let progress = progresses.remove(progresses.len() - 1);
|
||||||
digest: String::new(),
|
let reply = PullImageReply {
|
||||||
format: GuestOciImageFormat::Unknown.into(),
|
progress: Some(convert_oci_progress(progress)),
|
||||||
};
|
digest: String::new(),
|
||||||
yield reply;
|
format: GuestOciImageFormat::Unknown.into(),
|
||||||
|
};
|
||||||
|
yield reply;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
PullImageSelect::Completed(result) => {
|
PullImageSelect::Completed(result) => {
|
||||||
@ -413,8 +412,6 @@ impl ControlService for DaemonControlService {
|
|||||||
yield reply;
|
yield reply;
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ use krataoci::{
|
|||||||
progress::{OciProgress, OciProgressContext},
|
progress::{OciProgress, OciProgressContext},
|
||||||
registry::OciPlatform,
|
registry::OciPlatform,
|
||||||
};
|
};
|
||||||
use tokio::{fs, sync::broadcast};
|
use tokio::{fs, sync::mpsc::channel};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@ -22,14 +22,16 @@ async fn main() -> Result<()> {
|
|||||||
fs::create_dir(&cache_dir).await?;
|
fs::create_dir(&cache_dir).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (sender, mut receiver) = broadcast::channel::<OciProgress>(1000);
|
let (sender, mut receiver) = channel::<OciProgress>(100);
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let Some(progress) = receiver.recv().await.ok() else {
|
let mut progresses = Vec::new();
|
||||||
break;
|
let _ = receiver.recv_many(&mut progresses, 100).await;
|
||||||
|
let Some(progress) = progresses.last() else {
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
println!("phase {:?}", progress.phase);
|
println!("phase {:?}", progress.phase);
|
||||||
for (id, layer) in progress.layers {
|
for (id, layer) in &progress.layers {
|
||||||
println!(
|
println!(
|
||||||
"{} {:?} {} of {}",
|
"{} {:?} {} of {}",
|
||||||
id, layer.phase, layer.value, layer.total
|
id, layer.phase, layer.value, layer.total
|
||||||
|
@ -113,48 +113,21 @@ impl OciImageAssembler {
|
|||||||
progress.extracting_layer(&layer.digest, 0, 1);
|
progress.extracting_layer(&layer.digest, 0, 1);
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let (whiteouts, count) = self.process_layer_whiteout(&mut vfs, layer).await?;
|
debug!("process layer digest={}", &layer.digest,);
|
||||||
self.progress
|
|
||||||
.update(|progress| {
|
|
||||||
progress.extracting_layer(&layer.digest, 0, count);
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
debug!(
|
|
||||||
"process layer digest={} whiteouts={:?}",
|
|
||||||
&layer.digest, whiteouts
|
|
||||||
);
|
|
||||||
let mut archive = layer.archive().await?;
|
let mut archive = layer.archive().await?;
|
||||||
let mut entries = archive.entries()?;
|
let mut entries = archive.entries()?;
|
||||||
let mut completed = 0;
|
|
||||||
while let Some(entry) = entries.next().await {
|
while let Some(entry) = entries.next().await {
|
||||||
let mut entry = entry?;
|
let mut entry = entry?;
|
||||||
let path = entry.path()?;
|
let path = entry.path()?;
|
||||||
let mut maybe_whiteout_path_str =
|
|
||||||
path.to_str().map(|x| x.to_string()).unwrap_or_default();
|
|
||||||
if (completed % 10) == 0 {
|
|
||||||
self.progress
|
|
||||||
.update(|progress| {
|
|
||||||
progress.extracting_layer(&layer.digest, completed, count);
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
completed += 1;
|
|
||||||
if whiteouts.contains(&maybe_whiteout_path_str) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
maybe_whiteout_path_str.push('/');
|
|
||||||
if whiteouts.contains(&maybe_whiteout_path_str) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let Some(name) = path.file_name() else {
|
let Some(name) = path.file_name() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(name) = name.to_str() else {
|
let Some(name) = name.to_str() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if name.starts_with(".wh.") {
|
if name.starts_with(".wh.") {
|
||||||
continue;
|
self.process_whiteout_entry(&mut vfs, &entry, name, layer)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
vfs.insert_tar_entry(&entry)?;
|
vfs.insert_tar_entry(&entry)?;
|
||||||
self.process_write_entry(&mut vfs, &mut entry, layer)
|
self.process_write_entry(&mut vfs, &mut entry, layer)
|
||||||
@ -181,45 +154,13 @@ impl OciImageAssembler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_layer_whiteout(
|
|
||||||
&self,
|
|
||||||
vfs: &mut VfsTree,
|
|
||||||
layer: &OciImageLayer,
|
|
||||||
) -> Result<(Vec<String>, usize)> {
|
|
||||||
let mut whiteouts = Vec::new();
|
|
||||||
let mut archive = layer.archive().await?;
|
|
||||||
let mut entries = archive.entries()?;
|
|
||||||
let mut count = 0usize;
|
|
||||||
while let Some(entry) = entries.next().await {
|
|
||||||
let entry = entry?;
|
|
||||||
count += 1;
|
|
||||||
let path = entry.path()?;
|
|
||||||
let Some(name) = path.file_name() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(name) = name.to_str() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if name.starts_with(".wh.") {
|
|
||||||
let path = self
|
|
||||||
.process_whiteout_entry(vfs, &entry, name, layer)
|
|
||||||
.await?;
|
|
||||||
if let Some(path) = path {
|
|
||||||
whiteouts.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((whiteouts, count))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process_whiteout_entry(
|
async fn process_whiteout_entry(
|
||||||
&self,
|
&self,
|
||||||
vfs: &mut VfsTree,
|
vfs: &mut VfsTree,
|
||||||
entry: &Entry<Archive<Pin<Box<dyn AsyncRead + Send>>>>,
|
entry: &Entry<Archive<Pin<Box<dyn AsyncRead + Send>>>>,
|
||||||
name: &str,
|
name: &str,
|
||||||
layer: &OciImageLayer,
|
layer: &OciImageLayer,
|
||||||
) -> Result<Option<String>> {
|
) -> Result<()> {
|
||||||
let path = entry.path()?;
|
let path = entry.path()?;
|
||||||
let mut path = path.to_path_buf();
|
let mut path = path.to_path_buf();
|
||||||
path.pop();
|
path.pop();
|
||||||
@ -231,24 +172,27 @@ impl OciImageAssembler {
|
|||||||
path.push(file);
|
path.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("whiteout entry layer={} path={:?}", &layer.digest, path,);
|
trace!(
|
||||||
|
"whiteout entry {:?} layer={} path={:?}",
|
||||||
|
entry.path()?,
|
||||||
|
&layer.digest,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
let whiteout = path
|
let result = vfs.root.remove(&path);
|
||||||
.to_str()
|
if let Some((parent, mut removed)) = result {
|
||||||
.ok_or(anyhow!("unable to convert path to string"))?
|
delete_disk_paths(&removed).await?;
|
||||||
.to_string();
|
if opaque {
|
||||||
|
removed.children.clear();
|
||||||
let removed = vfs.root.remove(&path);
|
parent.children.push(removed);
|
||||||
if let Some(removed) = removed {
|
}
|
||||||
delete_disk_paths(removed).await?;
|
|
||||||
} else {
|
} else {
|
||||||
trace!(
|
warn!(
|
||||||
"whiteout entry layer={} path={:?} did not exist",
|
"whiteout entry layer={} path={:?} did not exist",
|
||||||
&layer.digest,
|
&layer.digest, path
|
||||||
path
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(if opaque { None } else { Some(whiteout) })
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_write_entry(
|
async fn process_write_entry(
|
||||||
@ -266,6 +210,9 @@ impl OciImageAssembler {
|
|||||||
entry.path()?,
|
entry.path()?,
|
||||||
entry.header().entry_type(),
|
entry.header().entry_type(),
|
||||||
);
|
);
|
||||||
|
entry.set_preserve_permissions(false);
|
||||||
|
entry.set_unpack_xattrs(false);
|
||||||
|
entry.set_preserve_mtime(false);
|
||||||
let path = entry
|
let path = entry
|
||||||
.unpack_in(&self.disk_dir)
|
.unpack_in(&self.disk_dir)
|
||||||
.await?
|
.await?
|
||||||
@ -275,7 +222,7 @@ impl OciImageAssembler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_disk_paths(node: VfsNode) -> Result<()> {
|
async fn delete_disk_paths(node: &VfsNode) -> Result<()> {
|
||||||
let mut queue = vec![node];
|
let mut queue = vec![node];
|
||||||
while !queue.is_empty() {
|
while !queue.is_empty() {
|
||||||
let node = queue.remove(0);
|
let node = queue.remove(0);
|
||||||
@ -285,7 +232,8 @@ async fn delete_disk_paths(node: VfsNode) -> Result<()> {
|
|||||||
}
|
}
|
||||||
fs::remove_file(disk_path).await?;
|
fs::remove_file(disk_path).await?;
|
||||||
}
|
}
|
||||||
queue.extend_from_slice(&node.children);
|
let children = node.children.iter().collect::<Vec<_>>();
|
||||||
|
queue.extend_from_slice(&children);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use tokio::sync::{broadcast::Sender, Mutex};
|
use tokio::sync::{mpsc::Sender, Mutex};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OciProgress {
|
pub struct OciProgress {
|
||||||
@ -108,7 +108,7 @@ impl OciProgressContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self, progress: &OciProgress) {
|
pub fn update(&self, progress: &OciProgress) {
|
||||||
let _ = self.sender.send(progress.clone());
|
let _ = self.sender.try_send(progress.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use tokio::{
|
|||||||
};
|
};
|
||||||
use tokio_tar::{Builder, Entry, EntryType, Header};
|
use tokio_tar::{Builder, Entry, EntryType, Header};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum VfsNodeType {
|
pub enum VfsNodeType {
|
||||||
Directory,
|
Directory,
|
||||||
RegularFile,
|
RegularFile,
|
||||||
@ -100,7 +100,7 @@ impl VfsNode {
|
|||||||
Some(node)
|
Some(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, path: &Path) -> Option<VfsNode> {
|
pub fn remove(&mut self, path: &Path) -> Option<(&mut VfsNode, VfsNode)> {
|
||||||
let parent = path.parent()?;
|
let parent = path.parent()?;
|
||||||
let node = self.lookup_mut(parent)?;
|
let node = self.lookup_mut(parent)?;
|
||||||
let file_name = path.file_name()?;
|
let file_name = path.file_name()?;
|
||||||
@ -109,7 +109,8 @@ impl VfsNode {
|
|||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
.position(|child| file_name == child.name)?;
|
.position(|child| file_name == child.name)?;
|
||||||
Some(node.children.remove(position))
|
let removed = node.children.remove(position);
|
||||||
|
Some((node, removed))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_tar_header(&self) -> Result<Header> {
|
pub fn create_tar_header(&self) -> Result<Header> {
|
||||||
@ -194,7 +195,7 @@ impl VfsTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_tar_entry<X: AsyncRead + Unpin>(&mut self, entry: &Entry<X>) -> Result<()> {
|
pub fn insert_tar_entry<X: AsyncRead + Unpin>(&mut self, entry: &Entry<X>) -> Result<()> {
|
||||||
let meta = VfsNode::from(entry)?;
|
let mut meta = VfsNode::from(entry)?;
|
||||||
let path = entry.path()?.to_path_buf();
|
let path = entry.path()?.to_path_buf();
|
||||||
let parent = if let Some(parent) = path.parent() {
|
let parent = if let Some(parent) = path.parent() {
|
||||||
self.root.lookup_mut(parent)
|
self.root.lookup_mut(parent)
|
||||||
@ -206,7 +207,17 @@ impl VfsTree {
|
|||||||
return Err(anyhow!("unable to find parent of entry"));
|
return Err(anyhow!("unable to find parent of entry"));
|
||||||
};
|
};
|
||||||
|
|
||||||
parent.children.retain(|child| child.name != meta.name);
|
let position = parent
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.position(|child| meta.name == child.name);
|
||||||
|
|
||||||
|
if let Some(position) = position {
|
||||||
|
let old = parent.children.remove(position);
|
||||||
|
if meta.typ == VfsNodeType::Directory {
|
||||||
|
meta.children = old.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
parent.children.push(meta);
|
parent.children.push(meta);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -237,7 +248,6 @@ impl VfsTree {
|
|||||||
if path.components().count() != 0 {
|
if path.components().count() != 0 {
|
||||||
node.write_to_tar(&path, &mut builder).await?;
|
node.write_to_tar(&path, &mut builder).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in &node.children {
|
for child in &node.children {
|
||||||
queue.push((path.clone(), child));
|
queue.push((path.clone(), child));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user