fix: Allow docker driver to properly use cache (#126)

This fix involves creating a new function for the `BuildDriver` trait
called `build_tag_push`. In order to get the proper logic in place to
make use of `docker buildx`, I had to create a separate function that
would construct the build command to include all of the tags necessary
for pushing. A default implementation of `build_tag_push` will be used
for `podman` and `buildah` which was originally from the build command's
functions. Now that we have custom logic for docker builds, we can take
advantage of using the GitHub cache features without having the `--load`
arg which had a big negative effect on build times. We can now also use
docker for creating local `oci-archive` tarballs for local rebasing.
Making use of the `oci-archive` will require the user to create a
`docker-container` builder as it is not supported on the standard
`docker` builder.

https://docs.docker.com/build/exporters/oci-docker/
This commit is contained in:
Gerald Pinder 2024-03-19 16:51:09 -04:00 committed by GitHub
parent 5fc4096f0f
commit 7c34d0c5a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 304 additions and 193 deletions

View file

@ -91,7 +91,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker
install: true
- name: Earthly login
@ -119,6 +118,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
GH_PR_EVENT_NUMBER: ${{ github.event.number }}
COSIGN_PRIVATE_KEY: ${{ secrets.TEST_SIGNING_SECRET }}
BB_BUILDKIT_CACHE_GHA: true
run: |
cd integration-tests/test-repo
if [ -n "$GH_TOKEN" ] && [ -n "$COSIGN_PRIVATE_KEY" ]; then

View file

@ -18,7 +18,11 @@ use colorized::{Color, Colors};
use log::{debug, info, trace, warn};
use typed_builder::TypedBuilder;
use crate::{commands::template::TemplateCommand, credentials, drivers::Driver};
use crate::{
commands::template::TemplateCommand,
credentials,
drivers::{opts::BuildTagPushOpts, Driver},
};
use super::BlueBuildCommand;
@ -215,7 +219,6 @@ impl BlueBuildCommand for BuildCommand {
if self.push {
blue_build_utils::check_command_exists("cosign")?;
blue_build_utils::check_command_exists("skopeo")?;
check_cosign_files()?;
}
@ -238,7 +241,29 @@ impl BuildCommand {
Self::login()?;
}
self.run_build(&image_name, &tags)?;
let opts = if let Some(archive_dir) = self.archive.as_ref() {
BuildTagPushOpts::builder()
.archive_path(format!(
"{}/{}.{ARCHIVE_SUFFIX}",
archive_dir.to_string_lossy().trim_end_matches('/'),
recipe.name.to_lowercase().replace('/', "_"),
))
.build()
} else {
BuildTagPushOpts::builder()
.image(&image_name)
.tags(tags.iter().map(String::as_str).collect::<Vec<_>>())
.push(self.push)
.no_retry_push(self.no_retry_push)
.retry_count(self.retry_count)
.build()
};
Driver::get_build_driver().build_tag_push(&opts)?;
if self.push {
sign_images(&image_name, tags.first().map(String::as_str))?;
}
info!("Build complete!");
@ -286,61 +311,53 @@ impl BuildCommand {
trace!("BuildCommand::generate_full_image_name({recipe:#?})");
info!("Generating full image name");
let image_name = if let Some(archive_dir) = &self.archive {
format!(
"oci-archive:{}/{}.{ARCHIVE_SUFFIX}",
archive_dir.to_string_lossy().trim_end_matches('/'),
recipe.name.to_lowercase().replace('/', "_"),
)
} else {
match (
env::var(CI_REGISTRY).ok().map(|s| s.to_lowercase()),
env::var(CI_PROJECT_NAMESPACE)
.ok()
.map(|s| s.to_lowercase()),
env::var(CI_PROJECT_NAME).ok().map(|s| s.to_lowercase()),
env::var(GITHUB_REPOSITORY_OWNER)
.ok()
.map(|s| s.to_lowercase()),
self.registry.as_ref().map(|s| s.to_lowercase()),
self.registry_namespace.as_ref().map(|s| s.to_lowercase()),
) {
(_, _, _, _, Some(registry), Some(registry_path)) => {
trace!("registry={registry}, registry_path={registry_path}");
format!(
"{}/{}/{}",
registry.trim().trim_matches('/'),
registry_path.trim().trim_matches('/'),
recipe.name.trim(),
)
}
(
Some(ci_registry),
Some(ci_project_namespace),
Some(ci_project_name),
None,
None,
None,
) => {
trace!("CI_REGISTRY={ci_registry}, CI_PROJECT_NAMESPACE={ci_project_namespace}, CI_PROJECT_NAME={ci_project_name}");
warn!("Generating Gitlab Registry image");
format!(
"{ci_registry}/{ci_project_namespace}/{ci_project_name}/{}",
recipe.name.trim().to_lowercase()
)
}
(None, None, None, Some(github_repository_owner), None, None) => {
trace!("GITHUB_REPOSITORY_OWNER={github_repository_owner}");
warn!("Generating Github Registry image");
format!("ghcr.io/{github_repository_owner}/{}", &recipe.name)
}
_ => {
trace!("Nothing to indicate an image name with a registry");
if self.push {
bail!("Need '--registry' and '--registry-path' in order to push image");
}
let image_name = match (
env::var(CI_REGISTRY).ok().map(|s| s.to_lowercase()),
env::var(CI_PROJECT_NAMESPACE)
.ok()
.map(|s| s.to_lowercase()),
env::var(CI_PROJECT_NAME).ok().map(|s| s.to_lowercase()),
env::var(GITHUB_REPOSITORY_OWNER)
.ok()
.map(|s| s.to_lowercase()),
self.registry.as_ref().map(|s| s.to_lowercase()),
self.registry_namespace.as_ref().map(|s| s.to_lowercase()),
) {
(_, _, _, _, Some(registry), Some(registry_path)) => {
trace!("registry={registry}, registry_path={registry_path}");
format!(
"{}/{}/{}",
registry.trim().trim_matches('/'),
registry_path.trim().trim_matches('/'),
recipe.name.trim(),
)
}
(
Some(ci_registry),
Some(ci_project_namespace),
Some(ci_project_name),
None,
None,
None,
) => {
trace!("CI_REGISTRY={ci_registry}, CI_PROJECT_NAMESPACE={ci_project_namespace}, CI_PROJECT_NAME={ci_project_name}");
warn!("Generating Gitlab Registry image");
format!(
"{ci_registry}/{ci_project_namespace}/{ci_project_name}/{}",
recipe.name.trim().to_lowercase()
)
}
(None, None, None, Some(github_repository_owner), None, None) => {
trace!("GITHUB_REPOSITORY_OWNER={github_repository_owner}");
warn!("Generating Github Registry image");
format!("ghcr.io/{github_repository_owner}/{}", &recipe.name)
}
_ => {
trace!("Nothing to indicate an image name with a registry");
if self.push {
bail!("Need '--registry' and '--registry-namespace' in order to push image");
}
recipe.name.trim().to_lowercase()
}
};
@ -348,59 +365,6 @@ impl BuildCommand {
Ok(image_name)
}
/// # Errors
///
/// Will return `Err` if the build fails.
fn run_build(&self, image_name: &str, tags: &[String]) -> Result<()> {
trace!("BuildCommand::run_build({image_name}, {tags:#?})");
let strat = Driver::get_build_driver();
let full_image = if self.archive.is_some() {
image_name.to_string()
} else {
tags.first()
.map_or_else(|| image_name.to_string(), |t| format!("{image_name}:{t}"))
};
info!("Building image {full_image}");
strat.build(&full_image)?;
if tags.len() > 1 && self.archive.is_none() {
debug!("Tagging all images");
for tag in tags {
debug!("Tagging {image_name} with {tag}");
strat.tag(&full_image, image_name, tag)?;
if self.push {
let retry_count = if self.no_retry_push {
0
} else {
self.retry_count
};
debug!("Pushing all images");
// Push images with retries (1s delay between retries)
blue_build_utils::retry(retry_count, 1000, || {
debug!("Pushing image {image_name}:{tag}");
let tag_image = format!("{image_name}:{tag}");
strat.push(&tag_image)
})?;
}
}
}
if self.push {
sign_images(image_name, tags.first().map(String::as_str))?;
}
Ok(())
}
}
// ======================================================== //
@ -413,7 +377,10 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
env::set_var("COSIGN_PASSWORD", "");
env::set_var("COSIGN_YES", "true");
let image_digest = get_image_digest(image_name, tag)?;
let image_digest = Driver::get_inspection_driver()
.get_metadata(image_name, tag.map_or_else(|| "latest", |t| t))?
.digest;
let image_name_digest = format!("{image_name}@{image_digest}");
let image_name_tag = tag.map_or_else(|| image_name.to_owned(), |t| format!("{image_name}:{t}"));
match (
@ -433,7 +400,7 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
(_, _, _, _, _, _, _, Ok(cosign_private_key))
if !cosign_private_key.is_empty() && Path::new(COSIGN_PATH).exists() =>
{
sign_priv_public_pair(&image_digest, &image_name_tag)?;
sign_priv_public_pair(&image_name_digest, &image_name_tag)?;
}
// Gitlab keyless
(
@ -448,19 +415,19 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
) => {
trace!("CI_PROJECT_URL={ci_project_url}, CI_DEFAULT_BRANCH={ci_default_branch}, CI_SERVER_PROTOCOL={ci_server_protocol}, CI_SERVER_HOST={ci_server_host}");
info!("Signing image: {image_digest}");
info!("Signing image: {image_name_digest}");
trace!("cosign sign {image_digest}");
trace!("cosign sign {image_name_digest}");
if Command::new("cosign")
.arg("sign")
.arg(&image_digest)
.arg(&image_name_digest)
.status()?
.success()
{
info!("Successfully signed image!");
} else {
bail!("Failed to sign image: {image_digest}");
bail!("Failed to sign image: {image_name_digest}");
}
let cert_ident =
@ -487,18 +454,18 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
(_, _, _, _, _, Ok(_), Ok(github_worflow_ref), _) => {
trace!("GITHUB_WORKFLOW_REF={github_worflow_ref}");
info!("Signing image {image_digest}");
info!("Signing image {image_name_digest}");
trace!("cosign sign {image_digest}");
trace!("cosign sign {image_name_digest}");
if Command::new("cosign")
.arg("sign")
.arg(&image_digest)
.arg(&image_name_digest)
.status()?
.success()
{
info!("Successfully signed image!");
} else {
bail!("Failed to sign image: {image_digest}");
bail!("Failed to sign image: {image_name_digest}");
}
trace!("cosign verify --certificate-identity-regexp {github_worflow_ref} --certificate-oidc-issuer {GITHUB_TOKEN_ISSUER_URL} {image_name_tag}");
@ -553,30 +520,6 @@ fn sign_priv_public_pair(image_digest: &str, image_name_tag: &str) -> Result<()>
Ok(())
}
fn get_image_digest(image_name: &str, tag: Option<&str>) -> Result<String> {
trace!("get_image_digest({image_name}, {tag:?})");
let image_url = tag.map_or_else(
|| format!("docker://{image_name}"),
|tag| format!("docker://{image_name}:{tag}"),
);
trace!("skopeo inspect --format='{{.Digest}}' {image_url}");
let image_digest = String::from_utf8(
Command::new("skopeo")
.arg("inspect")
.arg("--format='{{.Digest}}'")
.arg(&image_url)
.output()?
.stdout,
)?;
Ok(format!(
"{image_name}@{}",
image_digest.trim().trim_matches('\'')
))
}
fn check_cosign_files() -> Result<()> {
trace!("check_for_cosign_files()");

View file

@ -58,7 +58,8 @@ impl BlueBuildCommand for UpgradeCommand {
.force(self.common.force)
.build();
let image_name = build.generate_full_image_name(&recipe)?;
let image_name = recipe.name.to_lowercase().replace('/', "_");
clean_local_build_dir(&image_name, false)?;
debug!("Image name is {image_name}");
@ -106,7 +107,7 @@ impl BlueBuildCommand for RebaseCommand {
.force(self.common.force)
.build();
let image_name = build.generate_full_image_name(&recipe)?;
let image_name = recipe.name.to_lowercase().replace('/', "_");
clean_local_build_dir(&image_name, true)?;
debug!("Image name is {image_name}");
@ -158,7 +159,7 @@ fn clean_local_build_dir(image_name: &str, rebase: bool) -> Result<()> {
trace!("clean_local_build_dir()");
let local_build_path = Path::new(LOCAL_BUILD);
let image_file_path = local_build_path.join(image_name.trim_start_matches("oci-archive:"));
let image_file_path = local_build_path.join(format!("{image_name}.{ARCHIVE_SUFFIX}"));
if !image_file_path.exists() && !rebase {
bail!(

View file

@ -33,15 +33,16 @@ use tokio::runtime::Runtime;
#[cfg(feature = "builtin-podman")]
use podman_api_driver::PodmanApiDriver;
use crate::{credentials, image_inspection::ImageInspection};
use crate::{credentials, image_metadata::ImageMetadata};
use self::{
buildah_driver::BuildahDriver, docker_driver::DockerDriver, podman_driver::PodmanDriver,
skopeo_driver::SkopeoDriver,
buildah_driver::BuildahDriver, docker_driver::DockerDriver, opts::BuildTagPushOpts,
podman_driver::PodmanDriver, skopeo_driver::SkopeoDriver,
};
mod buildah_driver;
mod docker_driver;
pub mod opts;
#[cfg(feature = "builtin-podman")]
mod podman_api_driver;
mod podman_driver;
@ -139,15 +140,72 @@ pub trait BuildDriver: Sync + Send {
/// # Errors
/// Will error if login fails.
fn login(&self) -> Result<()>;
/// Runs the logic for building, tagging, and pushing an image.
///
/// # Errors
/// Will error if building, tagging, or pusing fails.
fn build_tag_push(&self, opts: &BuildTagPushOpts) -> Result<()> {
trace!("BuildDriver::build_tag_push({opts:#?})");
let full_image = match (opts.archive_path.as_ref(), opts.image.as_ref()) {
(Some(archive_path), None) => {
format!("oci-archive:{archive_path}")
}
(None, Some(image)) => opts
.tags
.first()
.map_or_else(|| image.to_string(), |tag| format!("{image}:{tag}")),
(Some(_), Some(_)) => bail!("Cannot use both image and archive path"),
(None, None) => bail!("Need either the image or archive path set"),
};
info!("Building image {full_image}");
self.build(&full_image)?;
if !opts.tags.is_empty() && opts.archive_path.is_none() {
let image = opts
.image
.as_ref()
.ok_or_else(|| anyhow!("Image is required in order to tag"))?;
debug!("Tagging all images");
for tag in opts.tags.as_ref() {
debug!("Tagging {} with {tag}", &full_image);
self.tag(&full_image, image.as_ref(), tag)?;
if opts.push {
let retry_count = if opts.no_retry_push {
0
} else {
opts.retry_count
};
debug!("Pushing all images");
// Push images with retries (1s delay between retries)
blue_build_utils::retry(retry_count, 1000, || {
let tag_image = format!("{image}:{tag}");
debug!("Pushing image {tag_image}");
self.push(&tag_image)
})?;
}
}
}
Ok(())
}
}
/// Allows agnostic inspection of images.
pub trait InspectDriver: Sync + Send {
/// Gets the labels on an image tag.
/// Gets the metadata on an image tag.
///
/// # Errors
/// Will error if it is unable to get the labels.
fn get_labels(&self, image_name: &str, tag: &str) -> Result<ImageInspection>;
fn get_metadata(&self, image_name: &str, tag: &str) -> Result<ImageMetadata>;
}
#[derive(Debug, TypedBuilder)]
@ -172,6 +230,7 @@ impl Driver<'_> {
/// # Errors
/// Will error if it is unable to set the user credentials.
pub fn init(self) -> Result<()> {
trace!("Driver::init()");
credentials::set_user_creds(self.username, self.password, self.registry)?;
Ok(())
}
@ -179,16 +238,19 @@ impl Driver<'_> {
/// Gets the current build's UUID
#[must_use]
pub fn get_build_id() -> Uuid {
trace!("Driver::get_build_id()");
*BUILD_ID
}
/// Gets the current run's build strategy
pub fn get_build_driver() -> Arc<dyn BuildDriver> {
trace!("Driver::get_build_driver()");
BUILD_STRATEGY.clone()
}
/// Gets the current run's inspectioin strategy
pub fn get_inspection_driver() -> Arc<dyn InspectDriver> {
trace!("Driver::get_inspection_driver()");
INSPECT_STRATEGY.clone()
}
@ -201,7 +263,7 @@ impl Driver<'_> {
/// Will error if the image doesn't have OS version info
/// or we are unable to lock a mutex.
pub fn get_os_version(recipe: &Recipe) -> Result<String> {
trace!("get_os_version({recipe:#?})");
trace!("Driver::get_os_version({recipe:#?})");
let image = format!("{}:{}", &recipe.base_image, &recipe.image_version);
let mut os_version_lock = OS_VERSION
@ -214,7 +276,7 @@ impl Driver<'_> {
None => {
info!("Retrieving OS version from {image}. This might take a bit");
let inspection =
INSPECT_STRATEGY.get_labels(&recipe.base_image, &recipe.image_version)?;
INSPECT_STRATEGY.get_metadata(&recipe.base_image, &recipe.image_version)?;
let os_version = inspection.get_version().ok_or_else(|| {
anyhow!(
@ -240,7 +302,7 @@ impl Driver<'_> {
}
fn determine_inspect_driver() -> Result<Arc<dyn InspectDriver>> {
trace!("Strategy::determine_inspect_strategy()");
trace!("Driver::determine_inspect_driver()");
let driver: Arc<dyn InspectDriver> = match (
blue_build_utils::check_command_exists("skopeo"),
@ -257,7 +319,7 @@ impl Driver<'_> {
}
fn determine_build_driver() -> Result<Arc<dyn BuildDriver>> {
trace!("Strategy::determine_build_strategy()");
trace!("Driver::determine_build_driver()");
let driver: Arc<dyn BuildDriver> = match (
env::var(XDG_RUNTIME_DIR),

View file

@ -4,14 +4,14 @@ use std::{
};
use anyhow::{bail, Result};
use blue_build_utils::constants::{BB_BUILDKIT_CACHE_GHA, SKOPEO_IMAGE};
use blue_build_utils::constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, SKOPEO_IMAGE};
use log::{info, trace};
use semver::Version;
use serde::Deserialize;
use crate::image_inspection::ImageInspection;
use crate::image_metadata::ImageMetadata;
use super::{credentials, BuildDriver, DriverVersion, InspectDriver};
use super::{credentials, opts::BuildTagPushOpts, BuildDriver, DriverVersion, InspectDriver};
#[derive(Debug, Deserialize)]
struct DockerVerisonJsonClient {
@ -48,34 +48,19 @@ impl DriverVersion for DockerDriver {
impl BuildDriver for DockerDriver {
fn build(&self, image: &str) -> Result<()> {
trace!("docker");
let mut command = Command::new("docker");
trace!("DockerDriver::build({image})");
// https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental
if env::var(BB_BUILDKIT_CACHE_GHA).map_or_else(|_| false, |e| e == "true") {
trace!("buildx build --load --cache-from type=gha --cache-to type=gha");
command
.arg("buildx")
.arg("build")
.arg("--load")
.arg("--cache-from")
.arg("type=gha")
.arg("--cache-to")
.arg("type=gha");
} else {
trace!("build");
command.arg("build");
}
trace!("-t {image} -f Containerfile .");
command
trace!("docker build -t {image} -f {CONTAINER_FILE} .");
let status = Command::new("docker")
.arg("build")
.arg("-t")
.arg(image)
.arg("-f")
.arg("Containerfile")
.arg(".");
.arg(CONTAINER_FILE)
.arg(".")
.status()?;
if command.status()?.success() {
if status.success() {
info!("Successfully built {image}");
} else {
bail!("Failed to build {image}");
@ -84,6 +69,8 @@ impl BuildDriver for DockerDriver {
}
fn tag(&self, src_image: &str, image_name: &str, tag: &str) -> Result<()> {
trace!("DockerDriver::tag({src_image}, {image_name}, {tag})");
let dest_image = format!("{image_name}:{tag}");
trace!("docker tag {src_image} {dest_image}");
@ -102,6 +89,8 @@ impl BuildDriver for DockerDriver {
}
fn push(&self, image: &str) -> Result<()> {
trace!("DockerDriver::push({image})");
trace!("docker push {image}");
let status = Command::new("docker").arg("push").arg(image).status()?;
@ -114,6 +103,8 @@ impl BuildDriver for DockerDriver {
}
fn login(&self) -> Result<()> {
trace!("DockerDriver::login()");
let (registry, username, password) =
credentials::get().map(|c| (&c.registry, &c.username, &c.password))?;
@ -129,14 +120,85 @@ impl BuildDriver for DockerDriver {
if !output.status.success() {
let err_out = String::from_utf8_lossy(&output.stderr);
bail!("Failed to login for buildah: {err_out}");
bail!("Failed to login for docker: {err_out}");
}
Ok(())
}
fn build_tag_push(&self, opts: &BuildTagPushOpts) -> Result<()> {
trace!("DockerDriver::build_tag_push({opts:#?})");
let mut command = Command::new("docker");
trace!("docker buildx build -f {CONTAINER_FILE}");
command
.arg("buildx")
.arg("build")
.arg("-f")
.arg(CONTAINER_FILE);
// https://github.com/moby/buildkit?tab=readme-ov-file#github-actions-cache-experimental
if env::var(BB_BUILDKIT_CACHE_GHA).map_or_else(|_| false, |e| e == "true") {
trace!("--cache-from type=gha --cache-to type=gha");
command
.arg("--cache-from")
.arg("type=gha")
.arg("--cache-to")
.arg("type=gha");
}
match (opts.image.as_ref(), opts.archive_path.as_ref()) {
(Some(image), None) => {
if opts.tags.is_empty() {
trace!("-t {image}");
command.arg("-t").arg(image.as_ref());
} else {
for tag in opts.tags.as_ref() {
let full_image = format!("{image}:{tag}");
trace!("-t {full_image}");
command.arg("-t").arg(full_image);
}
}
if opts.push {
trace!("--push");
command.arg("--push");
} else {
trace!("--builder default");
command.arg("--builder").arg("default");
}
}
(None, Some(archive_path)) => {
trace!("--output type=oci,dest={archive_path}");
command
.arg("--output")
.arg(format!("type=oci,dest={archive_path}"));
}
(Some(_), Some(_)) => bail!("Cannot use both image and archive path"),
(None, None) => bail!("Need either the image or archive path set"),
}
trace!(".");
command.arg(".");
if command.status()?.success() {
if opts.push {
info!("Successfully built and pushed image");
} else {
info!("Successfully built image");
}
} else {
bail!("Failed to build image");
}
Ok(())
}
}
impl InspectDriver for DockerDriver {
fn get_labels(&self, image_name: &str, tag: &str) -> Result<ImageInspection> {
fn get_metadata(&self, image_name: &str, tag: &str) -> Result<ImageMetadata> {
trace!("DockerDriver::get_labels({image_name}, {tag})");
let url = format!("docker://{image_name}:{tag}");
trace!("docker run {SKOPEO_IMAGE} inspect {url}");

3
src/drivers/opts.rs Normal file
View file

@ -0,0 +1,3 @@
mod build;
pub use build::*;

37
src/drivers/opts/build.rs Normal file
View file

@ -0,0 +1,37 @@
use std::borrow::Cow;
use typed_builder::TypedBuilder;
/// Options for building, tagging, and pusing images.
#[derive(Debug, Clone, TypedBuilder)]
pub struct BuildTagPushOpts<'a> {
/// The base image name.
///
/// NOTE: You cannot have this set with archive_path set.
#[builder(default, setter(into, strip_option))]
pub image: Option<Cow<'a, str>>,
/// The path to the archive file.
///
/// NOTE: You cannot have this set with image set.
#[builder(default, setter(into, strip_option))]
pub archive_path: Option<Cow<'a, str>>,
/// The list of tags for the image being built.
#[builder(default, setter(into))]
pub tags: Cow<'a, [&'a str]>,
/// Enable pushing the image.
#[builder(default)]
pub push: bool,
/// Disable retry logic for pushing.
#[builder(default)]
pub no_retry_push: bool,
/// Number of times to retry pushing.
///
/// Defaults to 1.
#[builder(default = 1)]
pub retry_count: u8,
}

View file

@ -6,7 +6,7 @@ use log::{debug, info, trace};
use semver::Version;
use serde::Deserialize;
use crate::image_inspection::ImageInspection;
use crate::image_metadata::ImageMetadata;
use super::{credentials, BuildDriver, DriverVersion, InspectDriver};
@ -107,14 +107,14 @@ impl BuildDriver for PodmanDriver {
if !output.status.success() {
let err_out = String::from_utf8_lossy(&output.stderr);
bail!("Failed to login for buildah: {err_out}");
bail!("Failed to login for podman: {err_out}");
}
Ok(())
}
}
impl InspectDriver for PodmanDriver {
fn get_labels(&self, image_name: &str, tag: &str) -> Result<ImageInspection> {
fn get_metadata(&self, image_name: &str, tag: &str) -> Result<ImageMetadata> {
let url = format!("docker://{image_name}:{tag}");
trace!("podman run {SKOPEO_IMAGE} inspect {url}");

View file

@ -3,7 +3,7 @@ use std::process::{Command, Stdio};
use anyhow::{bail, Result};
use log::{debug, trace};
use crate::image_inspection::ImageInspection;
use crate::image_metadata::ImageMetadata;
use super::InspectDriver;
@ -11,7 +11,7 @@ use super::InspectDriver;
pub struct SkopeoDriver;
impl InspectDriver for SkopeoDriver {
fn get_labels(&self, image_name: &str, tag: &str) -> Result<ImageInspection> {
fn get_metadata(&self, image_name: &str, tag: &str) -> Result<ImageMetadata> {
let url = format!("docker://{image_name}:{tag}");
trace!("skopeo inspect {url}");

View file

@ -4,12 +4,15 @@ use serde_json::Value;
use std::collections::HashMap;
#[derive(Deserialize, Debug, Clone)]
pub struct ImageInspection {
pub struct ImageMetadata {
#[serde(alias = "Labels")]
labels: HashMap<String, Value>,
pub labels: HashMap<String, Value>,
#[serde(alias = "Digest")]
pub digest: String,
}
impl ImageInspection {
impl ImageMetadata {
pub fn get_version(&self) -> Option<String> {
Some(
self.labels

View file

@ -7,4 +7,4 @@ shadow_rs::shadow!(shadow);
pub mod commands;
pub mod credentials;
pub mod drivers;
pub mod image_inspection;
pub mod image_metadata;