Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion crates/composefs-ctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ use std::sync::Arc;
use anyhow::{Context as _, Result};
use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::engine::ArgValueCompleter;
#[cfg(any(feature = "oci", feature = "ostree"))]
use comfy_table::{Table, presets::UTF8_FULL};
#[cfg(feature = "ostree")]
use complete::complete_ostree_refs;
Expand Down Expand Up @@ -726,6 +725,16 @@ enum Command {
},
/// Imports a composefs image (unsafe!)
ImportImage { reference: String },
/// List all named image references in the repository
#[clap(name = "images", alias = "list-images")]
Images {
/// Output as JSON array
#[clap(long)]
json: bool,
/// Show full digest instead of truncated form
#[clap(long)]
no_trunc: bool,
},
/// Commands for dealing with OCI images and layers
#[cfg(feature = "oci")]
Oci {
Expand Down Expand Up @@ -1904,6 +1913,31 @@ where
} => {
mount_opts.mount_image(&repo, &name, &mountpoint)?;
}
Command::Images { json, no_trunc } => {
Comment thread
cgwalters marked this conversation as resolved.
let reply =
varlink::run_list_image_refs(&repo).map_err(|e| anyhow::anyhow!("{e:?}"))?;

if json {
serde_json::to_writer_pretty(std::io::stdout().lock(), &reply)?;
println!();
} else if reply.images.is_empty() {
println!("No images found");
} else {
let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_header(["NAME", "DIGEST"]);

for entry in &reply.images {
let digest_display = if !no_trunc && entry.digest.len() > 12 {
&entry.digest[..12]
} else {
&entry.digest
};
table.add_row([entry.name.as_str(), digest_display]);
}
println!("{table}");
}
}
Command::ImageObjects { name } => {
let objects = repo.objects_for_image(&name)?;
for object in objects {
Expand Down
73 changes: 66 additions & 7 deletions crates/composefs-ctl/src/varlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,41 @@ async fn run_image_objects<ObjectID: FsVerityHashValue>(
Ok(ImageObjectsReply { object_ids })
}

/// A single image reference entry.
#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
pub struct ImageRefEntry {
/// The reference name.
pub name: String,
/// The fs-verity digest the reference points to.
pub digest: String,
}

/// Reply listing all named image references in the repository.
#[derive(Debug, Clone, Serialize, Deserialize, zlink::introspect::Type)]
pub struct ListImageRefsReply {
/// The image references.
pub images: Vec<ImageRefEntry>,
}

/// Collect all named image references from the repository.
pub fn run_list_image_refs<ObjectID: FsVerityHashValue>(
repo: &Repository<ObjectID>,
) -> std::result::Result<ListImageRefsReply, RepositoryError> {
let refs = repo
.list_image_refs("")
.map_err(|e| RepositoryError::InternalError {
message: format!("{e:#}"),
})?;
let images = refs
.into_iter()
.map(|(name, target)| {
let digest = target.rsplit('/').next().unwrap_or(&target).to_string();
ImageRefEntry { name, digest }
})
.collect();
Ok(ListImageRefsReply { images })
}

/// Options for a `Mount` call. All fields are optional for forward
/// compatibility — new mount options can be added without breaking the
/// wire format.
Expand Down Expand Up @@ -743,9 +778,10 @@ mod service_impl {
#![allow(missing_docs)]

use super::{
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply, MountParams,
MountReply, OpenRepo, OpenRepositoryReply, RepositoryError, run_fsck, run_gc,
run_image_objects, run_init_repository, run_mount,
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply,
ListImageRefsReply, MountParams, MountReply, OpenRepo, OpenRepositoryReply,
RepositoryError, run_fsck, run_gc, run_image_objects, run_init_repository,
run_list_image_refs, run_mount,
};
use composefs::fsverity::{Algorithm, Sha256HashValue, Sha512HashValue};

Expand Down Expand Up @@ -852,6 +888,17 @@ mod service_impl {
}
}

/// List all named image references in the repository.
async fn list_image_refs(
&self,
handle: u64,
) -> std::result::Result<ListImageRefsReply, RepositoryError> {
match self.lookup_repo(handle)? {
OpenRepo::Sha256(ref r) => run_list_image_refs::<Sha256HashValue>(r),
OpenRepo::Sha512(ref r) => run_list_image_refs::<Sha512HashValue>(r),
}
}

/// Create a detached mount of an image and return the mount fd.
///
/// If overlay upper/work directories are needed, pass them as two fds
Expand Down Expand Up @@ -898,10 +945,11 @@ mod service_impl {
parse_local_fetch, pull_stream,
};
use super::{
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply, MountParams,
MountReply, OpenRepo, OpenRepositoryReply, RepositoryError, run_compute_id, run_fsck,
run_gc, run_image_objects, run_init_repository, run_inspect, run_list_images, run_mount,
run_oci_fsck, run_oci_mount, run_tag, run_untag,
CfsctlService, FsckReply, GcReply, ImageObjectsReply, InitRepositoryReply,
ListImageRefsReply, MountParams, MountReply, OpenRepo, OpenRepositoryReply,
RepositoryError, run_compute_id, run_fsck, run_gc, run_image_objects, run_init_repository,
run_inspect, run_list_image_refs, run_list_images, run_mount, run_oci_fsck, run_oci_mount,
run_tag, run_untag,
};
use composefs::fsverity::{Algorithm, Sha256HashValue, Sha512HashValue};

Expand Down Expand Up @@ -1010,6 +1058,17 @@ mod service_impl {
}
}

/// List all named image references in the repository.
async fn list_image_refs(
&self,
handle: u64,
) -> std::result::Result<ListImageRefsReply, RepositoryError> {
match self.lookup_repo(handle)? {
OpenRepo::Sha256(ref r) => run_list_image_refs::<Sha256HashValue>(r),
OpenRepo::Sha512(ref r) => run_list_image_refs::<Sha512HashValue>(r),
}
}

/// Create a detached mount of an image and return the mount fd.
///
/// If overlay upper/work directories are needed, pass them as two fds
Expand Down