fix: Use built-in image inspection for podman and docker

This commit is contained in:
Gerald Pinder 2024-10-03 17:50:59 -04:00
parent 566380a82a
commit 4e0ab374c9
8 changed files with 195 additions and 80 deletions

View file

@ -35,7 +35,6 @@ use self::{
docker_driver::DockerDriver,
github_driver::GithubDriver,
gitlab_driver::GitlabDriver,
image_metadata::ImageMetadata,
local_driver::LocalDriver,
opts::{
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts,
@ -44,8 +43,8 @@ use self::{
podman_driver::PodmanDriver,
skopeo_driver::SkopeoDriver,
types::{
BuildDriverType, CiDriverType, DetermineDriver, InspectDriverType, RunDriverType,
SigningDriverType,
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType,
RunDriverType, SigningDriverType,
},
};
@ -57,7 +56,6 @@ mod docker_driver;
mod functions;
mod github_driver;
mod gitlab_driver;
pub mod image_metadata;
mod local_driver;
pub mod opts;
mod podman_driver;

View file

@ -1,4 +1,5 @@
use std::{
collections::HashMap,
env,
io::Write,
path::Path,
@ -9,15 +10,15 @@ use std::{
use blue_build_utils::{
cmd,
constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE},
constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST},
credentials::Credentials,
string_vec,
traits::IntoCollector,
};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, info, trace, warn};
use miette::{bail, IntoDiagnostic, Result};
use miette::{bail, miette, IntoDiagnostic, Report, Result};
use oci_distribution::Reference;
use once_cell::sync::Lazy;
use semver::Version;
use serde::Deserialize;
@ -25,9 +26,9 @@ use tempdir::TempDir;
use crate::{
drivers::{
image_metadata::ImageMetadata,
opts::{RunOptsEnv, RunOptsVolume},
types::Platform,
types::{InspectDriverType, Platform},
Driver,
},
logging::{CommandLogging, Logger},
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
@ -35,9 +36,53 @@ use crate::{
use super::{
opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts},
types::ImageMetadata,
BuildDriver, DriverVersion, InspectDriver, RunDriver,
};
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
struct DockerImageMetadata {
config: DockerImageMetadataConfig,
repo_digests: Vec<String>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
struct DockerImageMetadataConfig {
labels: HashMap<String, serde_json::Value>,
}
impl TryFrom<Vec<DockerImageMetadata>> for ImageMetadata {
type Error = Report;
fn try_from(mut value: Vec<DockerImageMetadata>) -> Result<Self> {
if value.is_empty() {
bail!("Need at least one metadata entry:\n{value:?}");
}
let mut value = value.swap_remove(0);
if value.repo_digests.is_empty() {
bail!("Metadata requires at least 1 digest:\n{value:#?}");
}
let digest: Reference = value
.repo_digests
.swap_remove(0)
.parse()
.into_diagnostic()?;
let digest = digest
.digest()
.ok_or_else(|| miette!("Unable to read digest from {digest}"))?
.to_string();
Ok(Self {
labels: value.config.labels,
digest,
})
}
}
#[derive(Debug, Deserialize)]
struct DockerVerisonJsonClient {
#[serde(alias = "Version")]
@ -280,7 +325,14 @@ impl BuildDriver for DockerDriver {
format!(
"type=image,name={first_image},push=true,compression={},oci-mediatypes=true",
opts.compression
)
),
// Load the image to the local image registry
// if the inspect driver is docker so that
// we don't have to pull the image again to inspect.
if matches!(
Driver::get_inspect_driver(),
InspectDriverType::Docker,
) => "--load",
);
} else {
cmd!(command, "--load");
@ -318,34 +370,44 @@ impl BuildDriver for DockerDriver {
impl InspectDriver for DockerDriver {
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
trace!("DockerDriver::get_labels({opts:#?})");
trace!("DockerDriver::get_metadata({opts:#?})");
let url = opts.tag.as_ref().map_or_else(
|| format!("docker://{}", opts.image),
|tag| format!("docker://{}:{tag}", opts.image),
|| format!("{}", opts.image),
|tag| format!("{}:{tag}", opts.image),
);
let progress = Logger::multi_progress().add(
ProgressBar::new_spinner()
.with_style(ProgressStyle::default_spinner())
.with_message(format!("Inspecting metadata for {}", url.bold())),
.with_message(format!(
"Inspecting metadata for {}, pulling image...",
url.bold()
)),
);
progress.enable_steady_tick(Duration::from_millis(100));
let mut args = Vec::new();
if !matches!(opts.platform, Platform::Native) {
args.extend(["--override-arch", opts.platform.arch()]);
}
args.extend(["inspect", &url]);
let mut command = cmd!(
"docker",
"pull",
if !matches!(opts.platform, Platform::Native) => [
"--platform",
opts.platform.to_string(),
],
&url,
);
trace!("{command:?}");
let output = Self::run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(args.collect_into_vec())
.remove(true)
.build(),
)
.into_diagnostic()?;
let output = command.output().into_diagnostic()?;
if !output.status.success() {
bail!("Failed to pull {} for inspection!", url.bold());
}
let mut command = cmd!("docker", "image", "inspect", "--format=json", &url);
trace!("{command:?}");
let output = command.output().into_diagnostic()?;
progress.finish_and_clear();
Logger::multi_progress().remove(&progress);
@ -356,7 +418,11 @@ impl InspectDriver for DockerDriver {
bail!("Failed to inspect image {url}")
}
serde_json::from_slice(&output.stdout).into_diagnostic()
serde_json::from_slice::<Vec<DockerImageMetadata>>(&output.stdout)
.into_diagnostic()
.inspect(|metadata| trace!("{metadata:#?}"))
.and_then(ImageMetadata::try_from)
.inspect(|metadata| trace!("{metadata:#?}"))
}
}

View file

@ -1,26 +0,0 @@
use blue_build_utils::constants::IMAGE_VERSION_LABEL;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Deserialize, Debug, Clone)]
pub struct ImageMetadata {
#[serde(alias = "Labels")]
pub labels: HashMap<String, Value>,
#[serde(alias = "Digest")]
pub digest: String,
}
impl ImageMetadata {
#[must_use]
pub fn get_version(&self) -> Option<u64> {
Some(
self.labels
.get(IMAGE_VERSION_LABEL)?
.as_str()
.and_then(|v| lenient_semver::parse(v).ok())?
.major,
)
}
}

View file

@ -1,25 +1,25 @@
use std::{
collections::HashMap,
io::Write,
path::Path,
process::{Command, ExitStatus, Stdio},
time::Duration,
};
use blue_build_utils::{
cmd, constants::SKOPEO_IMAGE, credentials::Credentials, traits::IntoCollector,
};
use blue_build_utils::{cmd, credentials::Credentials};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info, trace, warn};
use miette::{bail, miette, IntoDiagnostic, Result};
use miette::{bail, miette, IntoDiagnostic, Report, Result};
use oci_distribution::Reference;
use semver::Version;
use serde::Deserialize;
use tempdir::TempDir;
use crate::{
drivers::{
image_metadata::ImageMetadata,
opts::{RunOptsEnv, RunOptsVolume},
types::ImageMetadata,
types::Platform,
},
logging::{CommandLogging, Logger},
@ -31,6 +31,46 @@ use super::{
BuildDriver, DriverVersion, InspectDriver, RunDriver,
};
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
struct PodmanImageMetadata {
labels: HashMap<String, serde_json::Value>,
repo_digests: Vec<String>,
}
impl TryFrom<Vec<PodmanImageMetadata>> for ImageMetadata {
type Error = Report;
fn try_from(mut value: Vec<PodmanImageMetadata>) -> std::result::Result<Self, Self::Error> {
if value.is_empty() {
bail!("Podman inspection must have at least one metadata entry:\n{value:?}");
}
if value.is_empty() {
bail!("Need at least one metadata entry:\n{value:?}");
}
let mut value = value.swap_remove(0);
if value.repo_digests.is_empty() {
bail!("Podman Metadata requires at least 1 digest:\n{value:#?}");
}
let digest: Reference = value
.repo_digests
.swap_remove(0)
.parse()
.into_diagnostic()?;
let digest = digest
.digest()
.ok_or_else(|| miette!("Unable to read digest from {digest}"))?
.to_string();
Ok(Self {
labels: value.labels,
digest,
})
}
}
#[derive(Debug, Deserialize)]
struct PodmanVersionJsonClient {
#[serde(alias = "Version")]
@ -194,31 +234,41 @@ impl InspectDriver for PodmanDriver {
trace!("PodmanDriver::get_metadata({opts:#?})");
let url = opts.tag.as_deref().map_or_else(
|| format!("docker://{}", opts.image),
|tag| format!("docker://{}:{tag}", opts.image),
|| format!("{}", opts.image),
|tag| format!("{}:{tag}", opts.image),
);
let progress = Logger::multi_progress().add(
ProgressBar::new_spinner()
.with_style(ProgressStyle::default_spinner())
.with_message(format!("Inspecting metadata for {}", url.bold())),
.with_message(format!(
"Inspecting metadata for {}, pulling image...",
url.bold()
)),
);
progress.enable_steady_tick(Duration::from_millis(100));
let mut args = Vec::new();
if !matches!(opts.platform, Platform::Native) {
args.extend(["--override-arch", opts.platform.arch()]);
}
args.extend(["inspect", &url]);
let mut command = cmd!(
"podman",
"pull",
if !matches!(opts.platform, Platform::Native) => [
"--platform",
opts.platform.to_string(),
],
&url,
);
trace!("{command:?}");
let output = Self::run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(args.collect_into_vec())
.remove(true)
.build(),
)
.into_diagnostic()?;
let output = command.output().into_diagnostic()?;
if !output.status.success() {
bail!("Failed to pull {} for inspection!", url.bold());
}
let mut command = cmd!("podman", "image", "inspect", "--format=json", &url);
trace!("{command:?}");
let output = command.output().into_diagnostic()?;
progress.finish_and_clear();
Logger::multi_progress().remove(&progress);
@ -228,7 +278,11 @@ impl InspectDriver for PodmanDriver {
} else {
bail!("Failed to inspect image {url}");
}
serde_json::from_slice(&output.stdout).into_diagnostic()
serde_json::from_slice::<Vec<PodmanImageMetadata>>(&output.stdout)
.into_diagnostic()
.inspect(|metadata| trace!("{metadata:#?}"))
.and_then(TryFrom::try_from)
.inspect(|metadata| trace!("{metadata:#?}"))
}
}

View file

@ -8,7 +8,7 @@ use miette::{bail, IntoDiagnostic, Result};
use crate::{drivers::types::Platform, logging::Logger};
use super::{image_metadata::ImageMetadata, opts::GetMetadataOpts, InspectDriver};
use super::{opts::GetMetadataOpts, types::ImageMetadata, InspectDriver};
#[derive(Debug)]
pub struct SkopeoDriver;

View file

@ -13,12 +13,12 @@ use semver::{Version, VersionReq};
use crate::drivers::{functions::get_private_key, types::CiDriverType, Driver};
use super::{
image_metadata::ImageMetadata,
opts::{
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateImageNameOpts, GenerateKeyPairOpts,
GenerateTagsOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts,
VerifyOpts, VerifyType,
},
types::ImageMetadata,
};
/// Trait for retrieving version of a driver.

View file

@ -1,8 +1,10 @@
use std::env;
use std::{collections::HashMap, env};
use blue_build_utils::constants::{GITHUB_ACTIONS, GITLAB_CI};
use blue_build_utils::constants::{GITHUB_ACTIONS, GITLAB_CI, IMAGE_VERSION_LABEL};
use clap::ValueEnum;
use log::trace;
use serde::Deserialize;
use serde_json::Value;
use crate::drivers::{
buildah_driver::BuildahDriver, docker_driver::DockerDriver, podman_driver::PodmanDriver,
@ -205,3 +207,23 @@ impl std::fmt::Display for Platform {
)
}
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct ImageMetadata {
pub labels: HashMap<String, Value>,
pub digest: String,
}
impl ImageMetadata {
#[must_use]
pub fn get_version(&self) -> Option<u64> {
Some(
self.labels
.get(IMAGE_VERSION_LABEL)?
.as_str()
.and_then(|v| lenient_semver::parse(v).ok())?
.major,
)
}
}

View file

@ -179,6 +179,7 @@ impl BlueBuildCommand for BuildCommand {
} else {
PathBuf::from(CONTAINER_FILE)
})
.platform(self.platform)
.recipe(recipe)
.drivers(self.drivers)
.build()