|
2 | 2 | //!
|
3 | 3 | //! APIs for operating on container images in the bootc storage.
|
4 | 4 |
|
5 |
| -use anyhow::{Context, Result}; |
| 5 | +use anyhow::{ensure, Context, Result}; |
6 | 6 | use bootc_utils::CommandRunExt;
|
| 7 | +use clap::ValueEnum; |
7 | 8 | use fn_error_context::context;
|
8 | 9 | use ostree_ext::container::{ImageReference, Transport};
|
| 10 | +use serde::Serialize; |
| 11 | +use tabled::{settings::Style, Tabled}; |
9 | 12 |
|
10 |
| -use crate::imgstorage::Storage; |
| 13 | +use crate::cli::{ImageListFormat, ImageListType}; |
11 | 14 |
|
12 | 15 | /// The name of the image we push to containers-storage if nothing is specified.
|
13 | 16 | const IMAGE_DEFAULT: &str = "localhost/bootc";
|
14 | 17 |
|
| 18 | +#[derive(Clone, Tabled, Serialize, ValueEnum)] |
| 19 | +enum ImageListTypeColumn { |
| 20 | + Host, |
| 21 | + Logical, |
| 22 | +} |
| 23 | + |
| 24 | +impl std::fmt::Display for ImageListTypeColumn { |
| 25 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 26 | + self.to_possible_value().unwrap().get_name().fmt(f) |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +#[derive(Tabled, Serialize)] |
| 31 | +struct ImageOutput { |
| 32 | + #[tabled(rename = "IMAGE TYPE")] |
| 33 | + image_type: ImageListTypeColumn, |
| 34 | + |
| 35 | + #[tabled(rename = "IMAGE")] |
| 36 | + image: String, |
| 37 | + // TODO: Add hash, size, etc? Difficult because [`ostree_ext::container::store::list_images`] |
| 38 | + // only gives us the pullspec. |
| 39 | +} |
| 40 | + |
| 41 | +#[context("Listing host images")] |
| 42 | +fn list_host_images(sysroot: &crate::store::Storage) -> Result<Vec<ImageOutput>> { |
| 43 | + let repo = sysroot.repo(); |
| 44 | + let images = ostree_ext::container::store::list_images(&repo).context("Querying images")?; |
| 45 | + |
| 46 | + Ok(images |
| 47 | + .iter() |
| 48 | + .map(|x| ImageOutput { |
| 49 | + image: x.to_string(), |
| 50 | + image_type: ImageListTypeColumn::Host, |
| 51 | + }) |
| 52 | + .collect()) |
| 53 | +} |
| 54 | + |
| 55 | +#[context("Listing logical images")] |
| 56 | +fn list_logical_images(sysroot: &crate::store::Storage) -> Result<Vec<ImageOutput>> { |
| 57 | + let output = sysroot |
| 58 | + .get_ensure_imgstore()? |
| 59 | + .new_image_cmd()? |
| 60 | + .arg("list") |
| 61 | + .arg("--format={{.Repository}}:{{.Tag}}") |
| 62 | + .log_debug() |
| 63 | + .output()?; |
| 64 | + |
| 65 | + ensure!( |
| 66 | + output.status.success(), |
| 67 | + "Failed to list images: {}", |
| 68 | + String::from_utf8_lossy(&output.stderr) |
| 69 | + ); |
| 70 | + |
| 71 | + let stdout = String::from_utf8(output.stdout)?; |
| 72 | + |
| 73 | + let images = stdout |
| 74 | + .lines() |
| 75 | + .map(|x| ImageOutput { |
| 76 | + image: x.to_string(), |
| 77 | + image_type: ImageListTypeColumn::Logical, |
| 78 | + }) |
| 79 | + .collect(); |
| 80 | + |
| 81 | + Ok(images) |
| 82 | +} |
| 83 | + |
15 | 84 | #[context("Listing images")]
|
16 |
| -pub(crate) async fn list_entrypoint() -> Result<()> { |
17 |
| - let sysroot = crate::cli::get_storage().await?; |
18 |
| - let repo = &sysroot.repo(); |
| 85 | +pub(crate) async fn list_entrypoint( |
| 86 | + list_type: ImageListType, |
| 87 | + list_format: ImageListFormat, |
| 88 | +) -> Result<()> { |
| 89 | + // TODO: Get the storage from the container image, not the booted storage |
| 90 | + let sysroot: crate::store::Storage = crate::cli::get_storage().await?; |
19 | 91 |
|
20 |
| - let images = ostree_ext::container::store::list_images(repo).context("Querying images")?; |
| 92 | + let images = match list_type { |
| 93 | + ImageListType::All => { |
| 94 | + let images = list_host_images(&sysroot)?; |
| 95 | + let images = images.into_iter().chain(list_logical_images(&sysroot)?); |
| 96 | + images.collect() |
| 97 | + } |
| 98 | + ImageListType::Host => list_host_images(&sysroot)?, |
| 99 | + ImageListType::Logical => list_logical_images(&sysroot)?, |
| 100 | + }; |
21 | 101 |
|
22 |
| - println!("# Host images"); |
23 |
| - for image in images { |
24 |
| - println!("{image}"); |
| 102 | + match list_format { |
| 103 | + ImageListFormat::Table => { |
| 104 | + println!("{}", tabled::Table::new(images).with(Style::blank())); |
| 105 | + } |
| 106 | + ImageListFormat::Json => { |
| 107 | + let json = serde_json::to_string_pretty(&images)?; |
| 108 | + println!("{}", json); |
| 109 | + } |
25 | 110 | }
|
26 |
| - println!(); |
27 |
| - |
28 |
| - println!("# Logically bound images"); |
29 |
| - let mut listcmd = sysroot.get_ensure_imgstore()?.new_image_cmd()?; |
30 |
| - listcmd.arg("list"); |
31 |
| - listcmd.run()?; |
32 | 111 |
|
33 | 112 | Ok(())
|
34 | 113 | }
|
@@ -79,7 +158,7 @@ pub(crate) async fn push_entrypoint(source: Option<&str>, target: Option<&str>)
|
79 | 158 | /// Thin wrapper for invoking `podman image <X>` but set up for our internal
|
80 | 159 | /// image store (as distinct from /var/lib/containers default).
|
81 | 160 | pub(crate) async fn imgcmd_entrypoint(
|
82 |
| - storage: &Storage, |
| 161 | + storage: &crate::imgstorage::Storage, |
83 | 162 | arg: &str,
|
84 | 163 | args: &[std::ffi::OsString],
|
85 | 164 | ) -> std::result::Result<(), anyhow::Error> {
|
|
0 commit comments