Skip to content

Commit

Permalink
Add convenient API/CLI to prune all content
Browse files Browse the repository at this point in the history
This is kind of a usability regression from
ostreedev@979b93a
"deploy: Add an API to prune undeployed images"

This new API and CLI make it convenient to prune the images,
the layers, *and* the underlying objects.

cc coreos/rpm-ostree#4720

This API in particular is what bootc wants.  For rpm-ostree
we do still want to separate pruning refs from objects, because
rpm-ostree may have layered packages, and we only want to traverse
the ostree repo to prune objects once.
  • Loading branch information
cgwalters committed Dec 9, 2023
1 parent b59aaaa commit 77f96df
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 13 deletions.
7 changes: 7 additions & 0 deletions ci/priv-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,20 @@ for img in "${image}"; do
test "$(($initial_refs - 1))" = "$pruned_refs"
ostree admin --sysroot="${sysroot}" undeploy 0
# TODO: when we fold together ostree and ostree-ext, automatically prune layers
n_commits=$(find ${sysroot}/ostree/repo -name '*.commit')
test "${n_commits}" -gt 0
# But right now this still doesn't prune *content*
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"
cat refs.txt
exit 1
fi
# And this one should GC the objects too
ostree-ext-cli container image prune-images --full --repo="${sysroot}/ostree/repo" > out.txt
n_commits=$(find ${sysroot}/ostree/repo -name '*.commit')
test "${n_commits}" -eq 0
done

# Verify we have systemd journal messages
Expand Down
43 changes: 31 additions & 12 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ pub(crate) enum ContainerImageOpts {
#[clap(long)]
/// Also prune layers
and_layers: bool,

#[clap(long, conflicts_with = "and_layers")]
/// Also prune layers and OSTree objects
full: bool,
},

/// Perform initial deployment for a container image
Expand Down Expand Up @@ -1005,25 +1009,40 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
ContainerImageOpts::PruneImages {
sysroot,
and_layers,
full,
} => {
let sysroot = &ostree::Sysroot::new(Some(&gio::File::for_path(&sysroot)));
sysroot.load(gio::Cancellable::NONE)?;
let sysroot = &SysrootLock::new_from_sysroot(sysroot).await?;
let removed = crate::container::deploy::remove_undeployed_images(sysroot)?;
match removed.as_slice() {
[] => {
println!("No unreferenced images.");
return Ok(());
if full {
let res = crate::container::deploy::prune(sysroot)?;
if res.is_empty() {
println!("No content was pruned.");
} else {
println!("Removed images: {}", res.n_images);
println!("Removed layers: {}", res.n_layers);
println!("Removed objects: {}", res.n_objects_pruned);
let objsize = glib::format_size(res.objsize);
println!("Freed: {objsize}");
}
o => {
for imgref in o {
println!("Removed: {imgref}");
} else {
let removed = crate::container::deploy::remove_undeployed_images(sysroot)?;
match removed.as_slice() {
[] => {
println!("No unreferenced images.");
return Ok(());
}
o => {
for imgref in o {
println!("Removed: {imgref}");
}
}
}
}
if and_layers {
let nlayers = crate::container::store::gc_image_layers(&sysroot.repo())?;
println!("Removed layers: {nlayers}");
if and_layers {
let nlayers =
crate::container::store::gc_image_layers(&sysroot.repo())?;
println!("Removed layers: {nlayers}");
}
}
Ok(())
}
Expand Down
46 changes: 45 additions & 1 deletion lib/src/container/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::collections::HashSet;

use super::store::LayeredImageState;
use super::store::{gc_image_layers, LayeredImageState};
use super::{ImageReference, OstreeImageReference};
use crate::container::store::PrepareResult;
use crate::keyfileext::KeyFileExt;
Expand Down Expand Up @@ -174,3 +174,47 @@ pub fn remove_undeployed_images(sysroot: &SysrootLock) -> Result<Vec<ImageRefere
}
Ok(removed)
}

/// The result of a prune operation
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pruned {
/// The number of images that were pruned
pub n_images: u32,
/// The number of image layers that were pruned
pub n_layers: u32,
/// The number of OSTree objects that were pruned
pub n_objects_pruned: u32,
/// The total size of pruned objects
pub objsize: u64,
}

impl Pruned {
/// Whether this prune was a no-op (i.e. no images, layers or objects were pruned).
pub fn is_empty(&self) -> bool {
self.n_images == 0 && self.n_layers == 0 && self.n_objects_pruned == 0
}
}

/// This combines the functionality of [`remove_undeployed_images()`] with [`super::store::gc_image_layers()`].
pub fn prune(sysroot: &SysrootLock) -> Result<Pruned> {
let repo = &sysroot.repo();
// Prune container images which are not deployed.
// SAFETY: There should never be more than u32 images
let n_images = remove_undeployed_images(sysroot)?.len().try_into().unwrap();
// Prune unreferenced layer branches.
let n_layers = gc_image_layers(repo)?;
// Prune the objects in the repo; the above just removed refs (branches).
let (_, n_objects_pruned, objsize) = repo.prune(
ostree::RepoPruneFlags::REFS_ONLY,
0,
ostree::gio::Cancellable::NONE,
)?;
// SAFETY: The number of pruned objects should never be negative
let n_objects_pruned = u32::try_from(n_objects_pruned).unwrap();
Ok(Pruned {
n_images,
n_layers,
n_objects_pruned,
objsize,
})
}

0 comments on commit 77f96df

Please sign in to comment.