diff --git a/ci/priv-integration.sh b/ci/priv-integration.sh index 41b95520..8a1b6e42 100755 --- a/ci/priv-integration.sh +++ b/ci/priv-integration.sh @@ -29,8 +29,14 @@ for img in "${image}"; do ostree-ext-cli container image deploy --sysroot "${sysroot}" \ --stateroot "${stateroot}" --imgref ostree-unverified-registry:"${img}" ostree admin --sysroot="${sysroot}" status + initial_refs=$(ostree --repo="${sysroot}/ostree/repo" refs | wc -l) ostree-ext-cli container image remove --repo "${sysroot}/ostree/repo" registry:"${img}" + pruned_refs=$(ostree --repo="${sysroot}/ostree/repo" refs | wc -l) + # Removing the image should only drop the image reference, not its layers + test "$(($initial_refs - 1))" = "$pruned_refs" ostree admin --sysroot="${sysroot}" undeploy 0 + # TODO: when we fold together ostree and ostree-ext, automatically prune layers + ostree-ext-cli container image prune-layers --repo="${sysroot}/ostree/repo" ostree --repo="${sysroot}/ostree/repo" refs > refs.txt if test "$(wc -l < refs.txt)" -ne 0; then echo "found refs" diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 17de33c5..e76a9e38 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -252,6 +252,13 @@ pub(crate) enum ContainerImageOpts { skip_gc: bool, }, + /// Garbage collect unreferenced image layer references. + PruneLayers { + /// Path to the repository + #[clap(long, value_parser)] + repo: Utf8PathBuf, + }, + /// Perform initial deployment for a container image Deploy { /// Path to the system root @@ -777,6 +784,12 @@ where } Ok(()) } + ContainerImageOpts::PruneLayers { repo } => { + let repo = parse_repo(&repo)?; + let nlayers = crate::container::store::gc_image_layers(&repo)?; + println!("Removed layers: {nlayers}"); + Ok(()) + } ContainerImageOpts::Copy { src_repo, dest_repo, diff --git a/lib/src/container/store.rs b/lib/src/container/store.rs index d6315c54..e58a9b77 100644 --- a/lib/src/container/store.rs +++ b/lib/src/container/store.rs @@ -983,6 +983,40 @@ pub async fn copy( Ok(()) } +/// Iterate over deployment commits, returning the manifests from +/// commits which point to a container image. +fn list_container_deployment_manifests( + repo: &ostree::Repo, + cancellable: Option<&gio::Cancellable>, +) -> Result> { + let commits = repo + .list_refs_ext( + Some("ostree/0"), + ostree::RepoListRefsExtFlags::empty(), + cancellable, + )? + .into_iter() + .chain(repo.list_refs_ext( + Some("ostree/1"), + ostree::RepoListRefsExtFlags::empty(), + cancellable, + )?) + .map(|v| v.1); + let mut r = Vec::new(); + for commit in commits { + let commit_obj = repo.load_commit(&commit)?.0; + let commit_meta = &glib::VariantDict::new(Some(&commit_obj.child_value(0))); + if commit_meta + .lookup::(META_MANIFEST_DIGEST)? + .is_some() + { + let manifest = manifest_data_from_commitmeta(commit_meta)?.0; + r.push(manifest); + } + } + Ok(r) +} + /// Garbage collect unused image layer references. /// /// This function assumes no transaction is active on the repository. @@ -998,11 +1032,13 @@ fn gc_image_layers_impl( cancellable: Option<&gio::Cancellable>, ) -> Result { let all_images = list_images(repo)?; + let deployment_commits = list_container_deployment_manifests(repo, cancellable)?; let all_manifests = all_images .into_iter() .map(|img| { ImageReference::try_from(img.as_str()).and_then(|ir| manifest_for_image(repo, &ir)) }) + .chain(deployment_commits.into_iter().map(Ok)) .collect::>>()?; let mut referenced_layers = BTreeSet::new(); for m in all_manifests.iter() {