From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id DBAFC1FF144 for ; Tue, 24 Mar 2026 16:36:49 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5EBD316CCB; Tue, 24 Mar 2026 16:37:10 +0100 (CET) From: Filip Schauer To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox 2/2] oci: tests: generate OCI test images on the fly Date: Tue, 24 Mar 2026 16:35:50 +0100 Message-ID: <20260324153553.146040-3-f.schauer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260324153553.146040-1-f.schauer@proxmox.com> References: <20260324153553.146040-1-f.schauer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1774366574959 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.115 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LOTSOFHASH 0.25 Emails with lots of hash-like gibberish SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: VMFGLZFLL2GIECSP34XH23OC2C4B6XGZ X-Message-ID-Hash: VMFGLZFLL2GIECSP34XH23OC2C4B6XGZ X-MailFrom: f.schauer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Get rid of the pre-built tar files and instead generate them on the fly. This makes the tests easier to understand and it is also easier to write new tests this way. This also fixes extraction failures when running tests as a non-root user. The old tar files used UID/GID 0 for all layer entries, whereas now the effective UID/GID of the test runner is used instead. Signed-off-by: Filip Schauer --- proxmox-oci/Cargo.toml | 1 + proxmox-oci/tests/extract_replace.rs | 42 +++-- proxmox-oci/tests/extract_whiteouts.rs | 72 +++++--- proxmox-oci/tests/helper/mod.rs | 3 + .../tests/helper/oci_tar_image_builder.rs | 171 ++++++++++++++++++ .../oci_test_replace_dir_with_file.tar | Bin 8704 -> 0 bytes .../oci_image_data/oci_test_replace_file.tar | Bin 8704 -> 0 bytes .../oci_test_replace_file_with_dir.tar | Bin 8704 -> 0 bytes .../oci_test_whiteout_current_directory.tar | Bin 8704 -> 0 bytes .../oci_test_whiteout_dead_symlink_parent.tar | Bin 8704 -> 0 bytes .../oci_test_whiteout_root_breakout.tar | Bin 7168 -> 0 bytes ...oci_test_whiteout_root_parent_breakout.tar | Bin 7168 -> 0 bytes .../oci_test_whiteout_symlink.tar | Bin 8704 -> 0 bytes 13 files changed, 249 insertions(+), 40 deletions(-) create mode 100644 proxmox-oci/tests/helper/mod.rs create mode 100644 proxmox-oci/tests/helper/oci_tar_image_builder.rs delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_replace_dir_with_file.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_replace_file.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_replace_file_with_dir.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_whiteout_current_directory.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_whiteout_dead_symlink_parent.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_whiteout_root_breakout.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_whiteout_root_parent_breakout.tar delete mode 100644 proxmox-oci/tests/oci_image_data/oci_test_whiteout_symlink.tar diff --git a/proxmox-oci/Cargo.toml b/proxmox-oci/Cargo.toml index 8b922f37..d651390f 100644 --- a/proxmox-oci/Cargo.toml +++ b/proxmox-oci/Cargo.toml @@ -24,3 +24,4 @@ proxmox-io.workspace = true [dev-dependencies] anyhow.workspace = true proxmox-sys.workspace = true +nix = { workspace = true, features = ["user"] } diff --git a/proxmox-oci/tests/extract_replace.rs b/proxmox-oci/tests/extract_replace.rs index 5f684c8a..fe268d89 100644 --- a/proxmox-oci/tests/extract_replace.rs +++ b/proxmox-oci/tests/extract_replace.rs @@ -5,15 +5,20 @@ use anyhow::Result; use proxmox_oci::{parse_and_extract_image, Arch}; use proxmox_sys::fs::make_tmp_dir; +mod helper; +use helper::{LayerEntry, OciTarImageBuilder}; + #[test] fn test_replace_file() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![LayerEntry::file("etc/a", b"1")]) + .layer(vec![LayerEntry::file("etc/a", b"2")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_replace_file.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; let replaced_path = extract_dir.join("etc/a"); assert!(replaced_path.is_file()); @@ -21,42 +26,49 @@ fn test_replace_file() -> Result<()> { // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } #[test] fn test_replace_file_with_dir() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![LayerEntry::file("etc/a", b"1")]) + .layer(vec![LayerEntry::dir("etc/a")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_replace_file_with_dir.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; assert!(extract_dir.join("etc/a").is_dir()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } #[test] fn test_replace_dir_with_file() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![LayerEntry::dir("etc/a")]) + .layer(vec![LayerEntry::file("etc/a", b"1")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_replace_dir_with_file.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; assert!(extract_dir.join("etc/a").is_file()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } diff --git a/proxmox-oci/tests/extract_whiteouts.rs b/proxmox-oci/tests/extract_whiteouts.rs index 20156de0..7c7d95fc 100644 --- a/proxmox-oci/tests/extract_whiteouts.rs +++ b/proxmox-oci/tests/extract_whiteouts.rs @@ -5,95 +5,117 @@ use anyhow::Result; use proxmox_oci::{parse_and_extract_image, Arch}; use proxmox_sys::fs::make_tmp_dir; +mod helper; +use helper::{LayerEntry, OciTarImageBuilder}; + #[test] fn test_whiteout_root_breakout() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![LayerEntry::file(".wh..", b"")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_whiteout_root_breakout.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; // Check that the whiteout did not remove the root directory assert!(extract_dir.exists()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } #[test] fn test_whiteout_root_parent_breakout() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![LayerEntry::file(".wh...", b"")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_whiteout_root_parent_breakout.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; // Check that the whiteout did not remove the root directory assert!(extract_dir.exists()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } #[test] fn test_whiteout_current_directory() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![LayerEntry::file("etc/passwd", b"passwd")]) + .layer(vec![LayerEntry::file("etc/.wh..", b"")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_whiteout_current_directory.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; assert!(!extract_dir.join("etc").exists()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } #[test] fn test_whiteout_symlink() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![ + LayerEntry::file("etc/passwd", b"passwd"), + LayerEntry::symlink("localetc", "etc"), + ]) + .layer(vec![LayerEntry::file(".wh.localetc", b"")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_whiteout_symlink.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; assert!(extract_dir.join("etc/passwd").exists()); assert!(!extract_dir.join("localetc").exists()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } #[test] fn test_whiteout_dead_symlink_parent() -> Result<()> { + let image_dir = make_tmp_dir("/tmp/", None)?; + let image_path = image_dir.join("oci_image.tar"); + OciTarImageBuilder::default() + .layer(vec![ + LayerEntry::file("etc/passwd", b"passwd"), + LayerEntry::symlink("localetc", "dne"), + ]) + .layer(vec![LayerEntry::file("localetc/.wh.etc", b"")]) + .build(&image_path)?; let extract_dir = make_tmp_dir("/tmp/", None)?; - parse_and_extract_image( - &"tests/oci_image_data/oci_test_whiteout_dead_symlink_parent.tar".into(), - &extract_dir, - Some(&Arch::Amd64), - )?; + parse_and_extract_image(&image_path, &extract_dir, Some(&Arch::Amd64))?; assert!(extract_dir.join("etc/passwd").exists()); // Cleanup remove_dir_all(extract_dir)?; + remove_dir_all(image_dir)?; Ok(()) } diff --git a/proxmox-oci/tests/helper/mod.rs b/proxmox-oci/tests/helper/mod.rs new file mode 100644 index 00000000..734a09f8 --- /dev/null +++ b/proxmox-oci/tests/helper/mod.rs @@ -0,0 +1,3 @@ +#![allow(dead_code)] +mod oci_tar_image_builder; +pub use oci_tar_image_builder::{LayerEntry, OciTarImageBuilder}; diff --git a/proxmox-oci/tests/helper/oci_tar_image_builder.rs b/proxmox-oci/tests/helper/oci_tar_image_builder.rs new file mode 100644 index 00000000..c959b51a --- /dev/null +++ b/proxmox-oci/tests/helper/oci_tar_image_builder.rs @@ -0,0 +1,171 @@ +use std::fs::write; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use anyhow::Result; +use flate2::write::GzEncoder; +use flate2::Compression; +use nix::unistd::{getegid, geteuid}; +use oci_spec::image::{ + Arch, Descriptor, DescriptorBuilder, ImageConfigurationBuilder, ImageIndexBuilder, + ImageManifestBuilder, MediaType, OciLayoutBuilder, RootFsBuilder, Sha256Digest, +}; +use sha2::{Digest, Sha256}; + +pub struct LayerEntry { + path: PathBuf, + kind: EntryKind, +} + +enum EntryKind { + File(Vec), + Dir, + Symlink(PathBuf), +} + +impl LayerEntry { + pub fn file(path: impl Into, content: impl Into>) -> Self { + Self { + path: path.into(), + kind: EntryKind::File(content.into()), + } + } + + pub fn dir(path: impl Into) -> Self { + Self { + path: path.into(), + kind: EntryKind::Dir, + } + } + + pub fn symlink(path: impl Into, target: impl Into) -> Self { + Self { + path: path.into(), + kind: EntryKind::Symlink(target.into()), + } + } +} + +#[derive(Default)] +pub struct OciTarImageBuilder { + layers: Vec>, +} + +impl OciTarImageBuilder { + pub fn layer(mut self, entries: Vec) -> Self { + self.layers.push(entries); + self + } + + pub fn build>(self, oci_image_path: P) -> Result<()> { + let mut tar = tar::Builder::new(Vec::new()); + + let mut diff_ids = Vec::with_capacity(self.layers.len()); + let mut layer_descriptors = Vec::with_capacity(self.layers.len()); + + let mut header = tar::Header::new_old(); + header.set_uid(geteuid().as_raw().into()); + header.set_gid(getegid().as_raw().into()); + + for entries in self.layers { + let mut layer_tar = tar::Builder::new(Vec::new()); + + for entry in entries { + let mut header = header.clone(); + let content = match entry.kind { + EntryKind::File(content) => { + header.set_entry_type(tar::EntryType::Regular); + content + } + EntryKind::Dir => { + header.set_entry_type(tar::EntryType::Directory); + vec![] + } + EntryKind::Symlink(target) => { + header.set_entry_type(tar::EntryType::Symlink); + header.set_link_name(target)?; + vec![] + } + }; + header.set_size(content.len() as u64); + header.set_cksum(); + layer_tar.append_data(&mut header, entry.path, &*content)?; + } + + let layer_bytes = layer_tar.into_inner()?; + diff_ids.push(format!("sha256:{}", sha256(&layer_bytes))); + + let mut gz = GzEncoder::new(Vec::new(), Compression::fast()); + gz.write_all(&layer_bytes)?; + let layer_descriptor = add_blob(&mut tar, MediaType::ImageLayerGzip, &gz.finish()?)?; + layer_descriptors.push(layer_descriptor); + } + + let rootfs = RootFsBuilder::default() + .typ("layers") + .diff_ids(diff_ids) + .build()?; + let image_config = ImageConfigurationBuilder::default() + .architecture(Arch::Amd64) + .os("linux") + .rootfs(rootfs) + .build()?; + let config_descriptor = add_blob( + &mut tar, + MediaType::ImageConfig, + &image_config.to_string()?.into_bytes(), + )?; + + let manifest = ImageManifestBuilder::default() + .schema_version(2u32) + .config(config_descriptor) + .layers(layer_descriptors) + .build()?; + let manifest_descriptor = add_blob( + &mut tar, + MediaType::ImageManifest, + &manifest.to_string()?.into_bytes(), + )?; + + let index = ImageIndexBuilder::default() + .schema_version(2u32) + .manifests([manifest_descriptor]) + .build()?; + let index_bytes = index.to_string()?.into_bytes(); + add_file(&mut tar, "index.json", &index_bytes)?; + + let layout = OciLayoutBuilder::default() + .image_layout_version("1.0.0") + .build()?; + let layout_bytes = layout.to_string()?.into_bytes(); + add_file(&mut tar, "oci-layout", &layout_bytes)?; + + Ok(write(oci_image_path, tar.into_inner()?)?) + } +} + +fn sha256(data: &[u8]) -> Sha256Digest { + Sha256Digest::from_str(&format!("{:x}", Sha256::digest(data))).expect("digest must be valid") +} + +fn add_blob( + tar: &mut tar::Builder, + media_type: MediaType, + data: &[u8], +) -> oci_spec::Result { + let hash = sha256(data); + add_file(tar, &format!("blobs/sha256/{hash}"), data)?; + DescriptorBuilder::default() + .media_type(media_type) + .digest(hash) + .size(data.len() as u64) + .build() +} + +fn add_file(tar: &mut tar::Builder, path: &str, data: &[u8]) -> std::io::Result<()> { + let mut header = tar::Header::new_old(); + header.set_size(data.len() as u64); + header.set_cksum(); + tar.append_data(&mut header, path, data) +} diff --git a/proxmox-oci/tests/oci_image_data/oci_test_replace_dir_with_file.tar b/proxmox-oci/tests/oci_image_data/oci_test_replace_dir_with_file.tar deleted file mode 100644 index c4e488602eef13d5cee53710b97ffce5bf0586c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8704 zcmeHM&1+m$6n|QyVekvPu!!jJ7DcEtz26_fjUW-xO=)nU1i7D?C&^5hnS`b_P+h2K z79#!uqM&R23oN*(I}xdi2trrlN_Q@b^}ILJn3;5Dsxt|dytjDo&3X5pd+xdC_q*qw zv8yfHIh!2nh2WB$C;1bu;pcu??;Rt7MN;L5YE@tPMyG4q)oRjiwYuZRoAiBL-@Evm zE!Mx&FocW43WI!6|K0jyLCW<%lBgM2|ELX7hz4(QjE-WC48cM>$A!}|Xb^I%i13jp zB_y^0Qy-O%&`Mx53a|P43&w_Vf9p3QjO@Kb(*zO11WKytC_He?@&ELP^YgW@Yguwa zN#bK7$M_68O zDHlKj+zO?L3I^`ZAdLbt)*1yQpuHa9$cgxuco_eJl=vUv&Dn}`{I|c#@eiwv5Y`+i z%_#QDD9?>l!f5zHsl>#{U;*xeMZ*mZR(c++h7Cb!>djR6-@WI9C3^DYbAPnnf9mGx z#l;6J_p{B%Pwo8vr+l*Uz|K48P-PLA;n5_iTz#*dRA%dhW2|ie|Hf0~gEY~>)-JFE zOe4#Q*DPx3uy?IR~TWJ`8U-BTN6tG=OhV2MH=J+3z|6xK8;GeM)|0BFPTk)kz zwVHNZBdnQML%Wl;n(0E7Jey0`g3rv-)_MR*YS!0RGiSQc;Ou78*IO>DXKQ99)YnWi zi=or4Z{jm=cA$UJ*|nN6TY;YW0C%pe*KI%*boSW_43L}AJE$xK#T4e0GptP@LSyQQ zPyqr7Qqstb&chRXL9qymAdZ+ckh{o8onu0Y!p3~!*~NTzlyb0gyD)s!Y=Iv@_F+xZr=&c8#nXTz8}0Bl|TkSb@;g zwS(yfpK~ukgx4^sA-sg~0@4eJ*QpSM!f~Cw)@$C#I^9-#E2q!pf=*jGm8Pp%bK^p< zMP5gXxv;-6bc+op?bB}Fr=ZFh-^e^P>#;D1`pgft2uGZx!Uiv`ih(o1p#~8VCC&?O zh-X1K2{w^HwDlnn4|NPiNDs)36#)R-FG1)_wOdPx(;(hoSNW z^}n+IGvLNU`BC71ME=)L?EZT)blcRh5AdOkZrY>AK3w;5IwAVGP?=AR3mnMfYrws& zjj?}zjMFgr+a&xWD)zVl+9n9_k6DTT$)26Q#ig`3#l2F5z}^Wk#dTCi>Gt&cOAVz5 ON)MDCC_Qj@df;!g^|Z$T diff --git a/proxmox-oci/tests/oci_image_data/oci_test_replace_file.tar b/proxmox-oci/tests/oci_image_data/oci_test_replace_file.tar deleted file mode 100644 index 77922f7dbb6588a25e2b2514ec017d7327469d06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8704 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 G9{2}Zftk|) diff --git a/proxmox-oci/tests/oci_image_data/oci_test_whiteout_dead_symlink_parent.tar b/proxmox-oci/tests/oci_image_data/oci_test_whiteout_dead_symlink_parent.tar deleted file mode 100644 index b1b5094689402cce0433174f79c4595c10dae392..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8704 zcmeHLU5Hdw93K=H8)k*T@*$~h?Bl_9JfHX6yDu7)U}~C5LLuw<+`YOpv(DTVM_baO zAYADo>Y-?%5A&&njE$_fC=7)xq8@w*q`)2`33^Df?SJo#wzKQBqq8eW_dMLW_dn<3 zcmC)6e&>JwYN)KLt1|Oi5EPD3CjNyo{dcOZ{*Dn5B-6v^6;-42k!mC>0IAclV&qV+ zPOj7bZc=-dtbcV-67J2{Hi-Rled~`YO4t9U?0}~AR}9ukVro2PJ>_}MNyM#2fwDeB z#Bt(dC#AHOAdh=SaxN&>0aWz_30w=r@%pnV)QG>KT%igk6kmC-+X=xQtbfOxY=NiA z{6FXafoxAtzN!apQ9fxa)v#R3_w|s6Vr$WwP;RRYTd?Hi@bFNmWds#g?Jb!?S%-zN zCNx`fV z#%e#XlpA5K@QAXKDWxUkwGB1yoBQY^t3PdwZo&yG?KC`S|EL$U^b z0OQjM@H+hVCy+D;`~^5wRu1eBhZ{p(YZjg&fKvya(@X$DBb*b+X(N@4>Eswuz&hbH zwH#qfg+)GPK2yeUYuvwGH|BvENkAtv>1nurez03(F)$yP>LeG+cvx{PsHKi6t&ugv zC*X2VBdwiKm=i%2p^SQ#IAwyF*r}*vhv$y$Xts%@>BoF|ph^EbrBw@%v8ext{v+5p z)AgTwBGvyE?wnI|qW`j@2gAtfXrxjX`=asc8kyG>DRt?oEp9^SI_!4=us`janQ{N?4XTMr_$WX~J>#+Utea^lXh z=a-$SWmg=3=KQJ4dsg2bOy2v`XCHm|=ecu#UB=Jmf4@7o>+kdLp4k55YnL9luW)G7 zAFH4E`IE`h6NfKNzPjbHFSFwp-&&u^&FRF_&f4-o6aPC6>ROCAJO5|g{}^K_|690o zPR%j@FTDPK%zrrPcb7+^{s`_`{oCV!0KOiv(rm32;Q-&gSypT*N+ja+SDPL`v#xjD z_dl%s`s~7Qb1c)fW~WmN-aC|ev+G3n#_kPAj&vW{ICkj#i)+_*xm>m@7iErqasNw? zt~s{(^H)xd-?iwSolD1_dw5Ca)NP+G&veb{oYKzP_dr-Oc3&aBx3}NsdTY+cf6Axv zA7d<1{$H<~Z>W9}|F_8hG!i>Eo(#EWYN!$5Ps`|ZntGfP*Vl785Ys1-`6xWod_v2; z0eQS$xO;)px2)3fAG7fv(P{BN#&Fk6&;O1c-$n-q@<|qVL$U>~W`Og!fC?x-+C~Ry RmDB^N2T~8D9=Ihv@DEuzwG;pV diff --git a/proxmox-oci/tests/oci_image_data/oci_test_whiteout_root_breakout.tar b/proxmox-oci/tests/oci_image_data/oci_test_whiteout_root_breakout.tar deleted file mode 100644 index 36e25851148064fdc39d0fee90bdd5762e187989..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7168 zcmeHL%ZeLE6m35+X2`5;jCT=D7J)BPMW!8aU7_vwhl6sDMkko_JV>}+TF=Uf? zX^1fd@&oya{6Gx;iF`tqxm8j|mNN2;qmc=5*P_*Zse9`_>eRU<)343=LAKF>U~q)8 z_zPqC_f6UK9U~%0*5MoVYG(X++~^UwwC-Bba~NmAt!wW+>6=vlaZeLoZfYCEyu4fe zF-2|t-^&hIsK0ewIa~(kC^gD)jhPqHV?|sUj0s-DgHVd8@|H{M5n&2Di<~x$yK;rc zvHt80XvA+Ae?tWc%Fo`DoRE^ztg|*ISNO9a{=o5ny4C6AW83os{goe$t9qDsJLIz{ z9e7viy^|vkl3X7h^((6znDF4y&=s|VEawlC$L`N+2{r&P~taz)U8mC-B_gnrtm> z^z%O!@xP`+=P!}4{Wkm)!j|zbIBM~~BAr(%{`kk+x4(b+Yw!9E&kz<$Ifton0TFHq z6h=FQgAiJ1!%JIg*npCIg(TB)<5DAm2pEwDXRmjDfA!k_vWv3o^v)f$ee)0YV$eJM z?y%qQ_030j#myh?<4^CSSJ$53`{|Q=*M9zd`^)@vc=y@UhiL1T%Cf!R{@l)PU1@l& zu?sS=fd6%m)fH<+vVR-?sVJB64^2jk{}t)HQgPycJ+i&3@wPc0P04=XN;c#1k=KoP zk$VZ83A;${626CV7pX4d1uaWL;h11wPp9{)aZ`^@B7N>BI;|s>=KX4T{CGNJRM$<2 z!dYX`qy{6O(Qed9R2jmzm4j(h7LHV%t|(n5EM?Af?X^^f2%)&P*a72vhdB<$y5KR) z2o4KgBBr>Y5(zl0IEB+IPYdkBb8Cl<DPxWV{?;TH2kNn(FH6>iWK_$p>X#-R&%OAQ&8> zPQ1c6ef>~2xyOh|(pl$A^=fK-zpBj`Txz;j^c=?V=9=33Li6)<{#D-)E|!%BF)p8X z{+Ocn{J%&B%$&bRURY+OVT36{xz@(B;JGANGR+ZIG&fdR61byMGR3^l9aYo@NFQqE zcpT54eE^O44C6gi5S8NiKFJA*m-WV&oa4`o_#4On%eD3Ot{K~YQF~kOkA1fX&qF6z z_fC9e$KKT5MJYjqZz8ox_(P0)NcRxWs1Sq_!r0@9dcUaZa(n>lPqxzhvWim&#c2Qe zL}Of*b%?^aGAvLZ?7^DdfjRKwDye|b6(Kw=9H>1>S|FNGR8K%^Qj9E;!C1f^Cp zs&nhDM<`&;92F2s=Q!t-IW3G*7;7WAl))KE16E>A1S8nb!oQ^aWBk(=|8v55q2h%9 z%J%)xJOTg~CJp19l2wL0JeWXsn#nNWf19flzKVWr%Z) zCD)vq96KJMN}rgX?gKNOoIZsAt;OhV2LDYRnvc8)gw{^#h(zPXJ$EA-kmbanM#_Uo{}yS+OY^auHs>vHu^x&0H`esk&8 zW_JJ1O7GQ;-+%n??X4fa`(yk1m2s!@%WtdqI^SRD{#s*aN1zxv|2&JleYRecx98!X zay(oAF^2Dr7XM9RzUbVu>i@K`$7cQi6~j-q2C3_J#i@$kwDtU`7BVS6w-zoMv#&`H z`nXN#S<3x-dAm9f|A?OY{>K=;HCy~I??g^4zwD+i{y|EC<1c_N&QONBhbMaNT^oTm N0&N7^2z*@;cn3S&@<;#x diff --git a/proxmox-oci/tests/oci_image_data/oci_test_whiteout_symlink.tar b/proxmox-oci/tests/oci_image_data/oci_test_whiteout_symlink.tar deleted file mode 100644 index 7e6835cab889566cdf1d8dc1a42b69642b9d0463..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8704 zcmeHL-D_M$6u(Kdl$bo!hEVjuuoMlZ+0OUeSrC0Fg`j95ML`M8d}eRDA8~h6l9oV0 z{D3?Og28|WKVF0iz6t&VN?QyFeG)_p;;RUKlGd5K+nSBtW|K_{#XApo?>&3Y%$YOu z`^}l7N>x=q869W?zEJ?8@ax<5->tIdI|PWMXpA1HSFO$$>kV1+F7-NA@Z7h1lk2s= zhh(qP^{*EtqU@lyL5R!l)*oV!t^Y&W0d4EAq*e@AV2ISxafCP!MiL+ew}v2zfmFc8 zmRT875JP7PHc&IHsWMWU4jzZ~CtIKqzJ0q36^Jmr``(lz#4wM>dd6f2f7-$X-;s+C+}44nwlvNef3yS!k%BqtXZN{N;YuW<50#pJ6x$tPtwXY*xQ zNt~@W@)zL5eBIlp#=2T@$(*+g19ab6)2yv4Z)}n|KR|G!NmK>R9T6I92_Wh*5vX6rozLh%O zbiexvq~_lLygyYY<}M}+t)cEWi`WSvIOA3d<6#DcWJDN?Bw@tH0>uhqOtdrrN+hHM zkj26xU~vqv=IGz9Tl0W8PMuC?GS&+H!T64ViMg{%OR1cSjmE}uj$v$L=vRUPg#^rde3L~MNCW^%I7T!s7 z-h%a7ELMQ|Kc!?wX-~aA*P%ef2KfVpSVlQyRzM4lLR@j@6CbT(M|`4$bE(HO{<-of zvQ``Zdp(dk?983;k09EHf5;)s@ZTYv`zn5B_3+`%>)#an2b8Sa=;g_&8*>{FME%nP z1E73)O3U^2^))cQhBo^?8vXeAXy27D-+S)E7x<}{o(H#|cye|1>U8nxJI^lN{B?-l z9v&XPe(l)E(CM3>{eJnz+b<3t;y?WO+N;<4M*H9S`fpAMSE(u!{4>ey%7=u|24f__J{ZRaGi;Mp7?3=YJl_92->LDlD`QKG=dsIsE{|@z^R$*uF#gO}!hFS^!wu(-# zrN=FKeY2*+SXNKyVsH35-lV^Kd8Y3|d5W`yE40!ORKoA hq+8%#1vm%uAP;gYyXq=a&LWUSAd5g2fk!9;{{UmyntT8N -- 2.47.3