Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit b239fdc

Browse files
committed
store: Support importing images without /ostree
A sticking point keeping ostree in the picture here for containers was SELinux handling. When we started this effort I'd feared rewriting. But recently we changed things such that we label derived images using the policy from the final root. This is a relatively small change in code size and complexity, that allows us to import images that don't have "ostree stuff" in them at all, i.e. there's no `/ostree/repo/objects`. The advantage here is that this significantly simplifies constructing base images. The main disadvantage today for people who build images this way is that we end up re-labeling and re-checksumming all objects. But, the real fix for that in the future will be for us to rework things such that we support `security.selinux` for example as native xattrs in the tar stream.
1 parent 2d0c3f6 commit b239fdc

File tree

3 files changed

+86
-52
lines changed

3 files changed

+86
-52
lines changed

lib/src/container/store.rs

Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -227,15 +227,16 @@ pub struct PreparedImport {
227227
/// The layers containing split objects
228228
pub ostree_layers: Vec<ManifestLayerState>,
229229
/// The layer for the ostree commit.
230-
pub ostree_commit_layer: ManifestLayerState,
230+
pub ostree_commit_layer: Option<ManifestLayerState>,
231231
/// Any further non-ostree (derived) layers.
232232
pub layers: Vec<ManifestLayerState>,
233233
}
234234

235235
impl PreparedImport {
236236
/// Iterate over all layers; the commit layer, the ostree split object layers, and any non-ostree layers.
237237
pub fn all_layers(&self) -> impl Iterator<Item = &ManifestLayerState> {
238-
std::iter::once(&self.ostree_commit_layer)
238+
self.ostree_commit_layer
239+
.iter()
239240
.chain(self.ostree_layers.iter())
240241
.chain(self.layers.iter())
241242
}
@@ -376,21 +377,20 @@ fn layer_from_diffid<'a>(
376377
pub(crate) fn parse_manifest_layout<'a>(
377378
manifest: &'a ImageManifest,
378379
config: &ImageConfiguration,
379-
) -> Result<(&'a Descriptor, Vec<&'a Descriptor>, Vec<&'a Descriptor>)> {
380+
) -> Result<(
381+
Option<&'a Descriptor>,
382+
Vec<&'a Descriptor>,
383+
Vec<&'a Descriptor>,
384+
)> {
380385
let config_labels = super::labels_of(config);
381386

382387
let first_layer = manifest
383388
.layers()
384389
.first()
385390
.ok_or_else(|| anyhow!("No layers in manifest"))?;
386-
let target_diffid = config_labels
387-
.and_then(|labels| labels.get(DIFFID_LABEL))
388-
.ok_or_else(|| {
389-
anyhow!(
390-
"No {} label found, not an ostree encapsulated container",
391-
DIFFID_LABEL
392-
)
393-
})?;
391+
let Some(target_diffid) = config_labels.and_then(|labels| labels.get(DIFFID_LABEL)) else {
392+
return Ok((None, Vec::new(), manifest.layers().iter().collect()));
393+
};
394394

395395
let target_layer = layer_from_diffid(manifest, config, target_diffid.as_str())?;
396396
let mut chunk_layers = Vec::new();
@@ -416,7 +416,20 @@ pub(crate) fn parse_manifest_layout<'a>(
416416
}
417417
}
418418

419-
Ok((ostree_layer, chunk_layers, derived_layers))
419+
Ok((Some(ostree_layer), chunk_layers, derived_layers))
420+
}
421+
422+
/// Like [`parse_manifest_layout`] but requires the image has an ostree base.
423+
#[context("Parsing manifest layout")]
424+
pub(crate) fn parse_ostree_manifest_layout<'a>(
425+
manifest: &'a ImageManifest,
426+
config: &ImageConfiguration,
427+
) -> Result<(&'a Descriptor, Vec<&'a Descriptor>, Vec<&'a Descriptor>)> {
428+
let (ostree_layer, component_layers, derived_layers) = parse_manifest_layout(manifest, config)?;
429+
let ostree_layer = ostree_layer.ok_or_else(|| {
430+
anyhow!("No {DIFFID_LABEL} label found, not an ostree encapsulated container")
431+
})?;
432+
Ok((ostree_layer, component_layers, derived_layers))
420433
}
421434

422435
/// Find the timestamp of the manifest (or config), ignoring errors.
@@ -601,10 +614,10 @@ impl ImageImporter {
601614
parse_manifest_layout(&manifest, &config)?;
602615

603616
let query = |l: &Descriptor| query_layer(&self.repo, l.clone());
604-
let commit_layer = query(commit_layer)?;
617+
let commit_layer = commit_layer.map(query).transpose()?;
605618
let component_layers = component_layers
606619
.into_iter()
607-
.map(query)
620+
.map(|l| query(l))
608621
.collect::<Result<Vec<_>>>()?;
609622
let remaining_layers = remaining_layers
610623
.into_iter()
@@ -693,6 +706,7 @@ impl ImageImporter {
693706
pub(crate) async fn unencapsulate_base(
694707
&mut self,
695708
import: &mut store::PreparedImport,
709+
require_ostree: bool,
696710
write_refs: bool,
697711
) -> Result<()> {
698712
tracing::debug!("Fetching base");
@@ -707,6 +721,14 @@ impl ImageImporter {
707721
None
708722
}
709723
};
724+
let Some(commit_layer) = import.ostree_commit_layer.as_mut() else {
725+
if require_ostree {
726+
anyhow::bail!(
727+
"No {DIFFID_LABEL} label found, not an ostree encapsulated container"
728+
);
729+
}
730+
return Ok(());
731+
};
710732
let des_layers = self.proxy.get_layer_info(&self.proxy_img).await?;
711733
for layer in import.ostree_layers.iter_mut() {
712734
if layer.commit.is_some() {
@@ -754,31 +776,31 @@ impl ImageImporter {
754776
.await?;
755777
}
756778
}
757-
if import.ostree_commit_layer.commit.is_none() {
779+
if commit_layer.commit.is_none() {
758780
if let Some(p) = self.layer_progress.as_ref() {
759781
p.send(ImportProgress::OstreeChunkStarted(
760-
import.ostree_commit_layer.layer.clone(),
782+
commit_layer.layer.clone(),
761783
))
762784
.await?;
763785
}
764786
let (blob, driver) = fetch_layer_decompress(
765787
&self.proxy,
766788
&self.proxy_img,
767789
&import.manifest,
768-
&import.ostree_commit_layer.layer,
790+
&commit_layer.layer,
769791
self.layer_byte_progress.as_ref(),
770792
des_layers.as_ref(),
771793
self.imgref.imgref.transport,
772794
)
773795
.await?;
774796
let repo = self.repo.clone();
775-
let target_ref = import.ostree_commit_layer.ostree_ref.clone();
797+
let target_ref = commit_layer.ostree_ref.clone();
776798
let import_task =
777799
crate::tokio_util::spawn_blocking_cancellable_flatten(move |cancellable| {
778800
let txn = repo.auto_transaction(Some(cancellable))?;
779801
let mut importer = crate::tar::Importer::new_for_commit(&repo, remote);
780-
let blob = tokio_util::io::SyncIoBridge::new(blob);
781-
let mut archive = tar::Archive::new(blob);
802+
let mut blob = tokio_util::io::SyncIoBridge::new(blob);
803+
let mut archive = tar::Archive::new(&mut blob);
782804
importer.import_commit(&mut archive, Some(cancellable))?;
783805
let commit = importer.finish_import_commit();
784806
if write_refs {
@@ -790,10 +812,10 @@ impl ImageImporter {
790812
Ok::<_, anyhow::Error>(commit)
791813
});
792814
let commit = super::unencapsulate::join_fetch(import_task, driver).await?;
793-
import.ostree_commit_layer.commit = Some(commit);
815+
commit_layer.commit = Some(commit);
794816
if let Some(p) = self.layer_progress.as_ref() {
795817
p.send(ImportProgress::OstreeChunkCompleted(
796-
import.ostree_commit_layer.layer.clone(),
818+
commit_layer.layer.clone(),
797819
))
798820
.await?;
799821
}
@@ -816,11 +838,12 @@ impl ImageImporter {
816838
anyhow::bail!("Image has {} non-ostree layers", prep.layers.len());
817839
}
818840
let deprecated_warning = prep.deprecated_warning().map(ToOwned::to_owned);
819-
self.unencapsulate_base(&mut prep, false).await?;
841+
self.unencapsulate_base(&mut prep, true, false).await?;
820842
// TODO change the imageproxy API to ensure this happens automatically when
821843
// the image reference is dropped
822844
self.proxy.close_image(&self.proxy_img).await?;
823-
let ostree_commit = prep.ostree_commit_layer.commit.unwrap();
845+
// SAFETY: We know we have a commit
846+
let ostree_commit = prep.ostree_commit_layer.unwrap().commit.unwrap();
824847
let image_digest = prep.manifest_digest;
825848
Ok(Import {
826849
ostree_commit,
@@ -842,20 +865,23 @@ impl ImageImporter {
842865
}
843866
// First download all layers for the base image (if necessary) - we need the SELinux policy
844867
// there to label all following layers.
845-
self.unencapsulate_base(&mut import, true).await?;
868+
self.unencapsulate_base(&mut import, false, true).await?;
846869
let des_layers = self.proxy.get_layer_info(&self.proxy_img).await?;
847870
let proxy = self.proxy;
848871
let proxy_img = self.proxy_img;
849872
let target_imgref = self.target_imgref.as_ref().unwrap_or(&self.imgref);
850-
let base_commit = import.ostree_commit_layer.commit.clone().unwrap();
873+
let base_commit = import
874+
.ostree_commit_layer
875+
.as_ref()
876+
.map(|c| c.commit.clone().unwrap());
851877

852-
let root_is_transient = {
853-
let rootf = self
854-
.repo
855-
.read_commit(&base_commit, gio::Cancellable::NONE)?
856-
.0;
878+
let root_is_transient = if let Some(base) = base_commit.as_ref() {
879+
let rootf = self.repo.read_commit(&base, gio::Cancellable::NONE)?.0;
857880
let rootf = rootf.downcast_ref::<ostree::RepoFile>().unwrap();
858881
crate::ostree_prepareroot::overlayfs_root_enabled(rootf)?
882+
} else {
883+
// For generic images we assume they're using composefs
884+
true
859885
};
860886
tracing::debug!("Base rootfs is transient: {root_is_transient}");
861887

@@ -886,7 +912,7 @@ impl ImageImporter {
886912
// An important aspect of this is that we SELinux label the derived layers using
887913
// the base policy.
888914
let opts = crate::tar::WriteTarOptions {
889-
base: Some(base_commit.clone()),
915+
base: base_commit.clone(),
890916
selinux: true,
891917
allow_nonusr: root_is_transient,
892918
retain_var: self.ostree_v2024_3,
@@ -968,14 +994,16 @@ impl ImageImporter {
968994
process_whiteouts: false,
969995
..Default::default()
970996
};
971-
repo.checkout_at(
972-
Some(&checkout_opts),
973-
(*td).as_raw_fd(),
974-
rootpath,
975-
&base_commit,
976-
cancellable,
977-
)
978-
.context("Checking out base commit")?;
997+
if let Some(base) = base_commit.as_ref() {
998+
repo.checkout_at(
999+
Some(&checkout_opts),
1000+
(*td).as_raw_fd(),
1001+
rootpath,
1002+
&base,
1003+
cancellable,
1004+
)
1005+
.context("Checking out base commit")?;
1006+
}
9791007

9801008
// Layer all subsequent commits
9811009
checkout_opts.process_whiteouts = true;
@@ -1001,10 +1029,12 @@ impl ImageImporter {
10011029
let sepolicy = ostree::SePolicy::new_at(rootpath.as_raw_fd(), cancellable)?;
10021030
tracing::debug!("labeling from merged tree");
10031031
modifier.set_sepolicy(Some(&sepolicy));
1004-
} else {
1032+
} else if let Some(base) = base_commit.as_ref() {
10051033
tracing::debug!("labeling from base tree");
10061034
// TODO: We can likely drop this; we know all labels should be pre-computed.
1007-
modifier.set_sepolicy_from_commit(repo, &base_commit, cancellable)?;
1035+
modifier.set_sepolicy_from_commit(repo, &base, cancellable)?;
1036+
} else {
1037+
unreachable!()
10081038
}
10091039

10101040
let mt = ostree::MutableTree::new();
@@ -1296,6 +1326,7 @@ pub(crate) fn export_to_oci(
12961326
let srcinfo = query_image(repo, imgref)?.ok_or_else(|| anyhow!("No such image"))?;
12971327
let (commit_layer, component_layers, remaining_layers) =
12981328
parse_manifest_layout(&srcinfo.manifest, &srcinfo.configuration)?;
1329+
let commit_layer = commit_layer.ok_or_else(|| anyhow!("Missing {DIFFID_LABEL}"))?;
12991330
let commit_chunk_ref = ref_for_layer(commit_layer)?;
13001331
let commit_chunk_rev = repo.require_rev(&commit_chunk_ref)?;
13011332
let mut chunking = chunking::Chunking::new(repo, &commit_chunk_rev)?;
@@ -1761,10 +1792,12 @@ pub(crate) fn verify_container_image(
17611792
.0
17621793
.downcast::<ostree::RepoFile>()
17631794
.expect("downcast");
1764-
println!(
1765-
"Verifying with base ostree layer {}",
1766-
ref_for_layer(commit_layer)?
1767-
);
1795+
if let Some(commit_layer) = commit_layer {
1796+
println!(
1797+
"Verifying with base ostree layer {}",
1798+
ref_for_layer(commit_layer)?
1799+
);
1800+
}
17681801
compare_commit_trees(
17691802
repo,
17701803
"/".into(),

lib/src/container/update_detachedmeta.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ pub async fn update_detached_metadata(
7070
.ok_or_else(|| anyhow!("Image is missing container configuration"))?;
7171

7272
// Find the OSTree commit layer we want to replace
73-
let (commit_layer, _, _) = container_store::parse_manifest_layout(&manifest, &config)?;
73+
let (commit_layer, _, _) =
74+
container_store::parse_ostree_manifest_layout(&manifest, &config)?;
7475
let commit_layer_idx = manifest
7576
.layers()
7677
.iter()

lib/tests/it/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ async fn test_container_chunked() -> Result<()> {
859859
assert!(prep.deprecated_warning().is_none());
860860
assert_eq!(prep.version(), Some("42.0"));
861861
let digest = prep.manifest_digest.clone();
862-
assert!(prep.ostree_commit_layer.commit.is_none());
862+
assert!(prep.ostree_commit_layer.as_ref().unwrap().commit.is_none());
863863
assert_eq!(prep.ostree_layers.len(), nlayers);
864864
assert_eq!(prep.layers.len(), 0);
865865
for layer in prep.layers.iter() {
@@ -936,7 +936,7 @@ r usr/bin/bash bash-v0
936936
let to_fetch = prep.layers_to_fetch().collect::<Result<Vec<_>>>()?;
937937
assert_eq!(to_fetch.len(), 2);
938938
assert_eq!(expected_digest, prep.manifest_digest);
939-
assert!(prep.ostree_commit_layer.commit.is_none());
939+
assert!(prep.ostree_commit_layer.as_ref().unwrap().commit.is_none());
940940
assert_eq!(prep.ostree_layers.len(), nlayers);
941941
let (first, second) = (to_fetch[0], to_fetch[1]);
942942
assert!(first.0.commit.is_none());
@@ -979,7 +979,7 @@ r usr/bin/bash bash-v0
979979
};
980980
let to_fetch = prep.layers_to_fetch().collect::<Result<Vec<_>>>()?;
981981
assert_eq!(to_fetch.len(), 1);
982-
assert!(prep.ostree_commit_layer.commit.is_some());
982+
assert!(prep.ostree_commit_layer.as_ref().unwrap().commit.is_some());
983983
assert_eq!(prep.ostree_layers.len(), nlayers);
984984

985985
// We want to test explicit layer pruning
@@ -1314,7 +1314,7 @@ async fn test_container_write_derive() -> Result<()> {
13141314
store::PrepareResult::Ready(r) => r,
13151315
};
13161316
let expected_digest = prep.manifest_digest.clone();
1317-
assert!(prep.ostree_commit_layer.commit.is_none());
1317+
assert!(prep.ostree_commit_layer.as_ref().unwrap().commit.is_none());
13181318
assert_eq!(prep.layers.len(), 1);
13191319
for layer in prep.layers.iter() {
13201320
assert!(layer.commit.is_none());
@@ -1387,7 +1387,7 @@ async fn test_container_write_derive() -> Result<()> {
13871387
store::PrepareResult::Ready(r) => r,
13881388
};
13891389
// We *should* already have the base layer.
1390-
assert!(prep.ostree_commit_layer.commit.is_some());
1390+
assert!(prep.ostree_commit_layer.as_ref().unwrap().commit.is_some());
13911391
// One new layer
13921392
assert_eq!(prep.layers.len(), 1);
13931393
for layer in prep.layers.iter() {

0 commit comments

Comments
 (0)