nightly(podman-api): Use podman-api crate for building images
This commit is contained in:
parent
218cc9c7d3
commit
1b950b08dc
11 changed files with 3068 additions and 108 deletions
|
|
@ -1,2 +1,2 @@
|
|||
[language-server.rust-analyzer.config]
|
||||
cargo.features = ["init"]
|
||||
cargo.features = ["nightly"]
|
||||
|
|
|
|||
2798
Cargo.lock
generated
2798
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
|
@ -7,8 +7,6 @@ repository = "https://gitlab.com/wunker-bunker/blue-build"
|
|||
license = "Apache-2.0"
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
askama = { version = "0.12.1", features = ["serde-json"] }
|
||||
|
|
@ -18,18 +16,23 @@ clap = { version = "4.4.4", features = ["derive"] }
|
|||
clap-verbosity-flag = "2.1.1"
|
||||
derive_builder = "0.12.0"
|
||||
env_logger = "0.10.1"
|
||||
futures-util = { version = "0.3.30", optional = true }
|
||||
log = "0.4.20"
|
||||
podman-api = { version = "0.10.0", optional = true }
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
serde_yaml = "0.9.25"
|
||||
sigstore = { version = "0.8.0", optional = true }
|
||||
tokio = { version = "1.35.1", features = ["full"], optional = true }
|
||||
typed-builder = "0.18.0"
|
||||
users = "0.11.0"
|
||||
|
||||
[features]
|
||||
default = ["build"]
|
||||
nightly = ["build"]
|
||||
default = []
|
||||
nightly = ["builtin-podman"]
|
||||
builtin-podman = ["podman-api", "tokio", "futures-util"]
|
||||
tls = ["podman-api/tls", "builtin-podman"]
|
||||
init = []
|
||||
build = []
|
||||
|
||||
[dev-dependencies]
|
||||
rusty-hook = "0.11.2"
|
||||
|
|
|
|||
20
Earthfile
20
Earthfile
|
|
@ -22,14 +22,11 @@ default:
|
|||
BUILD +blue-build-cli-alpine --NIGHTLY=$NIGHTLY
|
||||
BUILD +installer --NIGHTLY=$NIGHTLY
|
||||
BUILD +integration-test-template --NIGHTLY=$NIGHTLY
|
||||
BUILD +integration-test-build --NIGHTLY=$NIGHTLY
|
||||
|
||||
nightly:
|
||||
BUILD +default --NIGHTLY=true
|
||||
|
||||
integration-tests:
|
||||
BUILD +integration-test-template --NIGHTLY=true --NIGHTLY=false
|
||||
BUILD +integration-test-build --NIGHTLY=true --NIGHTLY=false
|
||||
|
||||
lint:
|
||||
FROM +common
|
||||
|
||||
|
|
@ -97,11 +94,11 @@ blue-build-cli-alpine:
|
|||
DO cargo+SAVE_IMAGE --IMAGE=$IMAGE --TAG=$TAG --LATEST=$LATEST --NIGHTLY=$NIGHTLY --ALPINE=true
|
||||
|
||||
installer:
|
||||
FROM alpine
|
||||
# FROM alpine
|
||||
FROM mgoltzsche/podman:minimal
|
||||
ARG NIGHTLY=false
|
||||
|
||||
BUILD +install --BUILD_TARGET="x86_64-unknown-linux-gnu" --NIGHTLY=$NIGHTLY
|
||||
|
||||
COPY (+install/bb --BUILD_TARGET="x86_64-unknown-linux-gnu") /out/bb
|
||||
COPY install.sh /install.sh
|
||||
|
||||
|
|
@ -113,7 +110,8 @@ installer:
|
|||
DO cargo+SAVE_IMAGE --IMAGE=$IMAGE --TAG=$TAG --LATEST=$LATEST --NIGHTLY=$NIGHTLY --INSTALLER=$INSTALLER
|
||||
|
||||
integration-test-template:
|
||||
FROM DOCKERFILE -f +integration-test-template-containerfile/test/Containerfile +integration-test-template-containerfile/test/*
|
||||
ARG NIGHTLY=false
|
||||
FROM DOCKERFILE -f +integration-test-template-containerfile/test/Containerfile +integration-test-template-containerfile/test/* --NIGHTLY=$NIGHTLY
|
||||
|
||||
integration-test-template-containerfile:
|
||||
ARG NIGHTLY=false
|
||||
|
|
@ -126,13 +124,19 @@ integration-test-build:
|
|||
ARG NIGHTLY=false
|
||||
FROM +integration-test-base --NIGHTLY=$NIGHTLY
|
||||
|
||||
RUN --privileged bb -vv build config/recipe-jp-desktop.yml
|
||||
RUN --entrypoint --privileged podman info && bb -vv build config/recipe-jp-desktop.yml
|
||||
|
||||
integration-test-base:
|
||||
ARG NIGHTLY=false
|
||||
|
||||
FROM +blue-build-cli-alpine --NIGHTLY=$NIGHTLY
|
||||
|
||||
RUN echo "#!/bin/sh
|
||||
echo 'Running podman'" > /usr/bin/podman
|
||||
|
||||
RUN echo "#!/bin/sh
|
||||
echo 'Running buildah'" > /usr/bin/buildah
|
||||
|
||||
GIT CLONE https://gitlab.com/wunker-bunker/wunker-os.git /test
|
||||
WORKDIR /test
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div align="center">
|
||||
<center>
|
||||
<figure>
|
||||
<img src="logos/BlueBuild-banner.png" alt="BlueBuild Banner" height="300" />
|
||||
<img src="https://gitlab.com/wunker-bunker/blue-build/-/raw/main/logos/BlueBuild-banner.png" alt="BlueBuild Banner" style="max-height: 300px;" />
|
||||
<figcaption>Graphic Designer: Ian Price</figcaption>
|
||||
</figure>
|
||||
</center>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ struct BlueBuildArgs {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum CommandArgs {
|
||||
/// Build an image from a recipe
|
||||
Build(build::BuildCommand),
|
||||
|
||||
/// Generate a Containerfile from a recipe
|
||||
Template(template::TemplateCommand),
|
||||
|
||||
|
|
@ -28,10 +31,6 @@ enum CommandArgs {
|
|||
|
||||
#[cfg(feature = "init")]
|
||||
New(init::NewCommand),
|
||||
|
||||
/// Build an image from a recipe
|
||||
#[cfg(feature = "build")]
|
||||
Build(build::BuildCommand),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -39,12 +38,15 @@ fn main() {
|
|||
|
||||
env_logger::builder()
|
||||
.filter_level(args.verbosity.log_level_filter())
|
||||
.filter_module("hyper::proto", log::LevelFilter::Info)
|
||||
.write_style(WriteStyle::Always)
|
||||
.init();
|
||||
|
||||
trace!("{args:#?}");
|
||||
|
||||
match args.command {
|
||||
CommandArgs::Build(mut command) => command.run(),
|
||||
|
||||
CommandArgs::Template(command) => command.run(),
|
||||
|
||||
#[cfg(feature = "init")]
|
||||
|
|
@ -52,8 +54,5 @@ fn main() {
|
|||
|
||||
#[cfg(feature = "init")]
|
||||
CommandArgs::New(command) => command.run(),
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
CommandArgs::Build(command) => command.run(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
246
src/build.rs
246
src/build.rs
|
|
@ -1,3 +1,6 @@
|
|||
#[cfg(feature = "podman-api")]
|
||||
mod build_strategy;
|
||||
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
|
|
@ -10,6 +13,22 @@ use log::{debug, error, info, trace, warn};
|
|||
use typed_builder::TypedBuilder;
|
||||
use users::{Users, UsersCache};
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
use podman_api::{
|
||||
api::Image,
|
||||
opts::{ImageBuildOpts, ImageListOpts, ImagePushOpts, RegistryAuth},
|
||||
Podman,
|
||||
};
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
use build_strategy::BuildStrategy;
|
||||
|
||||
#[cfg(feature = "futures-util")]
|
||||
use futures_util::StreamExt;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::{
|
||||
ops,
|
||||
template::{Recipe, TemplateCommand},
|
||||
|
|
@ -47,37 +66,87 @@ pub struct BuildCommand {
|
|||
|
||||
/// The registry's domain name.
|
||||
#[arg(long)]
|
||||
#[builder(default, setter(into))]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
registry: Option<String>,
|
||||
|
||||
/// The url path to your base
|
||||
/// project images.
|
||||
#[arg(long)]
|
||||
#[builder(default, setter(into))]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
registry_path: Option<String>,
|
||||
|
||||
/// The username to login to the
|
||||
/// container registry.
|
||||
#[arg(short = 'U', long)]
|
||||
#[builder(default, setter(into))]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
username: Option<String>,
|
||||
|
||||
/// The password to login to the
|
||||
/// container registry.
|
||||
#[arg(short = 'P', long)]
|
||||
#[builder(default, setter(into))]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
password: Option<String>,
|
||||
|
||||
/// The connection string used to connect
|
||||
/// to a remote podman socket.
|
||||
#[cfg(feature = "podman-api")]
|
||||
#[arg(short, long)]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
connection: Option<String>,
|
||||
|
||||
/// The path to the `cert.pem`, `key.pem`,
|
||||
/// and `ca.pem` files needed to connect to
|
||||
/// a remote podman build socket.
|
||||
#[cfg(feature = "tls")]
|
||||
#[arg(long)]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
tls_path: Option<PathBuf>,
|
||||
|
||||
/// Whether to sign the image.
|
||||
#[cfg(feature = "sigstore")]
|
||||
#[arg(short, long)]
|
||||
#[builder(default)]
|
||||
sign: bool,
|
||||
|
||||
/// Path to the public key used to sign the image.
|
||||
///
|
||||
/// If the contents of the key are in an environment
|
||||
/// variable, you can use `env://` to sepcify which
|
||||
/// variable to read from.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// bb build --public-key env://PUBLIC_KEY ...
|
||||
#[cfg(feature = "sigstore")]
|
||||
#[arg(long)]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
public_key: Option<String>,
|
||||
|
||||
/// Path to the private key used to sign the image.
|
||||
///
|
||||
/// If the contents of the key are in an environment
|
||||
/// variable, you can use `env://` to sepcify which
|
||||
/// variable to read from.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// bb build --private-key env://PRIVATE_KEY ...
|
||||
#[cfg(feature = "sigstore")]
|
||||
#[arg(long)]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
private_key: Option<String>,
|
||||
}
|
||||
|
||||
impl BuildCommand {
|
||||
/// Runs the command and returns a result.
|
||||
pub fn try_run(&self) -> Result<()> {
|
||||
pub fn try_run(&mut self) -> Result<()> {
|
||||
trace!("BuildCommand::try_run()");
|
||||
|
||||
if self.push && self.rebase {
|
||||
bail!("You cannot use '--rebase' and '--push' at the same time");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "podman-api"))]
|
||||
if let Err(e1) = ops::check_command_exists("buildah") {
|
||||
ops::check_command_exists("podman").map_err(|e2| {
|
||||
anyhow!("Need either 'buildah' or 'podman' commands to proceed: {e1}, {e2}")
|
||||
|
|
@ -109,11 +178,22 @@ impl BuildCommand {
|
|||
.try_run()?;
|
||||
|
||||
info!("Building image for recipe at {}", self.recipe.display());
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
match BuildStrategy::determine_strategy()? {
|
||||
BuildStrategy::Socket(socket) => {
|
||||
let rt = Runtime::new()?;
|
||||
rt.block_on(self.build_image_podman_api(Podman::unix(socket)))
|
||||
}
|
||||
_ => self.build_image(),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "podman-api"))]
|
||||
self.build_image()
|
||||
}
|
||||
|
||||
/// Runs the command and exits if there is an error.
|
||||
pub fn run(&self) {
|
||||
pub fn run(&mut self) {
|
||||
trace!("BuildCommand::run()");
|
||||
|
||||
if let Err(e) = self.try_run() {
|
||||
|
|
@ -122,6 +202,156 @@ impl BuildCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
async fn build_image_podman_api(&self, client: Podman) -> Result<()> {
|
||||
use podman_api::opts::ImageTagOpts;
|
||||
|
||||
trace!("BuildCommand::build_image({client:#?})");
|
||||
|
||||
let (registry, username, password) = if self.push {
|
||||
let registry = match (
|
||||
self.registry.as_ref(),
|
||||
env::var("CI_REGISTRY").ok(),
|
||||
env::var("GITHUB_ACTIONS").ok(),
|
||||
) {
|
||||
(Some(registry), _, _) => registry.to_owned(),
|
||||
(None, Some(ci_registry), None) => ci_registry,
|
||||
(None, None, Some(_)) => "ghcr.io".to_string(),
|
||||
_ => bail!("Need '--registry' set in order to login"),
|
||||
};
|
||||
|
||||
let username = match (
|
||||
self.username.as_ref(),
|
||||
env::var("CI_REGISTRY_USER").ok(),
|
||||
env::var("GITHUB_ACTOR").ok(),
|
||||
) {
|
||||
(Some(username), _, _) => username.to_owned(),
|
||||
(None, Some(ci_registry_user), None) => ci_registry_user,
|
||||
(None, None, Some(github_actor)) => github_actor,
|
||||
_ => bail!("Need '--username' set in order to login"),
|
||||
};
|
||||
|
||||
let password = match (
|
||||
self.password.as_ref(),
|
||||
env::var("CI_REGISTRY_PASSWORD").ok(),
|
||||
env::var("REGISTRY_TOKEN").ok(),
|
||||
) {
|
||||
(Some(password), _, _) => password.to_owned(),
|
||||
(None, Some(ci_registry_password), None) => ci_registry_password,
|
||||
(None, None, Some(registry_token)) => registry_token,
|
||||
_ => bail!("Need '--password' set in order to login"),
|
||||
};
|
||||
|
||||
(registry, username, password)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let recipe: Recipe = serde_yaml::from_str(fs::read_to_string(&self.recipe)?.as_str())?;
|
||||
trace!("recipe: {recipe:#?}");
|
||||
|
||||
// Get values for image
|
||||
let tags = recipe.generate_tags();
|
||||
let image_name = self.generate_full_image_name(&recipe)?;
|
||||
let first_image_name = if tags.is_empty() || self.rebase {
|
||||
image_name.clone()
|
||||
} else {
|
||||
format!("{}:{}", &image_name, &tags[0])
|
||||
};
|
||||
debug!("Full tag is {first_image_name}");
|
||||
|
||||
// Get podman ready to build
|
||||
let opts = ImageBuildOpts::builder(".")
|
||||
.tag(&first_image_name)
|
||||
.dockerfile("Containerfile")
|
||||
.remove(true)
|
||||
.layers(true)
|
||||
.pull(true)
|
||||
.build();
|
||||
trace!("Build options: {opts:#?}");
|
||||
|
||||
match client.images().build(&opts) {
|
||||
Ok(mut build_stream) => {
|
||||
while let Some(chunk) = build_stream.next().await {
|
||||
match chunk {
|
||||
Ok(chunk) => debug!("{}", chunk.stream.trim()),
|
||||
Err(e) => error!("{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{}", e),
|
||||
};
|
||||
|
||||
if self.push {
|
||||
debug!("Pushing is enabled");
|
||||
|
||||
trace!("cosign login -u {username} -p [MASKED] {registry}");
|
||||
if !Command::new("cosign")
|
||||
.arg("login")
|
||||
.arg("-u")
|
||||
.arg(&username)
|
||||
.arg("-p")
|
||||
.arg(&password)
|
||||
.arg(®istry)
|
||||
.output()?
|
||||
.status
|
||||
.success()
|
||||
{
|
||||
bail!("Failed to login for cosign!");
|
||||
}
|
||||
info!("Cosign login success at {registry}");
|
||||
|
||||
let first_image = client.images().get(&first_image_name);
|
||||
|
||||
for tag in &tags {
|
||||
let full_image_name = format!("{image_name}:{tag}");
|
||||
|
||||
first_image
|
||||
.tag(&ImageTagOpts::builder().repo(&image_name).tag(tag).build())
|
||||
.await?;
|
||||
debug!("Tagged image {full_image_name}");
|
||||
|
||||
let new_image = client.images().get(&full_image_name);
|
||||
|
||||
info!("Pushing {full_image_name}");
|
||||
match new_image
|
||||
.push(
|
||||
&ImagePushOpts::builder()
|
||||
.tls_verify(true)
|
||||
.auth(
|
||||
RegistryAuth::builder()
|
||||
.username(&username)
|
||||
.password(&password)
|
||||
.server_address(®istry)
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => info!("Pushed {full_image_name} successfully!"),
|
||||
Err(e) => bail!("Failed to push image: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
self.sign_images(&image_name, &tags[0])?;
|
||||
} else if self.rebase {
|
||||
debug!("Rebasing onto locally built image {image_name}");
|
||||
|
||||
if Command::new("rpm-ostree")
|
||||
.arg("rebase")
|
||||
.arg(format!("ostree-unverified-image:{first_image_name}"))
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
info!("Successfully rebased to {first_image_name}");
|
||||
} else {
|
||||
bail!("Failed to rebase to {first_image_name}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_image(&self) -> Result<()> {
|
||||
trace!("BuildCommand::build_image()");
|
||||
let recipe: Recipe = serde_yaml::from_str(fs::read_to_string(&self.recipe)?.as_str())?;
|
||||
|
|
@ -208,8 +438,6 @@ impl BuildCommand {
|
|||
bail!("Failed to login for buildah!");
|
||||
}
|
||||
|
||||
info!("Buildah login success at {registry} for user {username}!");
|
||||
|
||||
trace!("cosign login -u {username} -p [MASKED] {registry}");
|
||||
if !Command::new("cosign")
|
||||
.arg("login")
|
||||
|
|
@ -224,7 +452,7 @@ impl BuildCommand {
|
|||
{
|
||||
bail!("Failed to login for cosign!");
|
||||
}
|
||||
info!("Cosign login success at {registry} for user {username}!");
|
||||
info!("Login success at {registry}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
57
src/build/build_strategy.rs
Normal file
57
src/build/build_strategy.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use log::trace;
|
||||
|
||||
use crate::ops;
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub enum BuildStrategy {
|
||||
#[default]
|
||||
Uninitialized,
|
||||
Socket(PathBuf),
|
||||
Buildah,
|
||||
Podman,
|
||||
}
|
||||
|
||||
#[cfg(feature = "podman-api")]
|
||||
impl BuildStrategy {
|
||||
pub fn determine_strategy() -> Result<Self> {
|
||||
trace!("BuildStrategy::determin_strategy()");
|
||||
|
||||
Ok(
|
||||
match (
|
||||
env::var("XDG_RUNTIME_DIR"),
|
||||
PathBuf::from("/run/podman/podman.sock"),
|
||||
PathBuf::from("/var/run/podman/podman.sock"),
|
||||
PathBuf::from("/var/run/podman.sock"),
|
||||
ops::check_command_exists("buildah"),
|
||||
ops::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(xdg_runtime), _, _, _, _, _)
|
||||
if Path::new(&format!("{xdg_runtime}/podman/podman.sock")).exists() =>
|
||||
{
|
||||
Self::Socket(PathBuf::from(format!("{xdg_runtime}/podman/podman.sock")))
|
||||
}
|
||||
(_, run_podman_podman_sock, _, _, _, _) if run_podman_podman_sock.exists() => {
|
||||
Self::Socket(run_podman_podman_sock)
|
||||
}
|
||||
(_, _, var_run_podman_podman_sock, _, _, _)
|
||||
if var_run_podman_podman_sock.exists() =>
|
||||
{
|
||||
Self::Socket(var_run_podman_podman_sock)
|
||||
}
|
||||
(_, _, _, var_run_podman_sock, _, _) if var_run_podman_sock.exists() => {
|
||||
Self::Socket(var_run_podman_sock)
|
||||
}
|
||||
(_, _, _, _, Ok(_), _) => Self::Buildah,
|
||||
(_, _, _, _, _, Ok(_)) => Self::Podman,
|
||||
_ => bail!("Could not determine strategy"),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
//! The root library for blue-build.
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://gitlab.com/wunker-bunker/blue-build/-/raw/main/logos/BlueBuild-logo.png"
|
||||
)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
#[cfg(feature = "init")]
|
||||
pub mod init;
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
pub mod build;
|
||||
|
||||
mod ops;
|
||||
pub mod template;
|
||||
|
|
|
|||
11
src/ops.rs
11
src/ops.rs
|
|
@ -1,11 +1,16 @@
|
|||
use std::process::Command;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use clap::ValueEnum;
|
||||
use log::{debug, trace};
|
||||
|
||||
pub fn check_command_exists(command: &str) -> Result<()> {
|
||||
debug!("Checking if {command} exists");
|
||||
trace!("check_command_exists({command})");
|
||||
debug!("Checking if {command} exists");
|
||||
|
||||
trace!("which {command}");
|
||||
match Command::new("which")
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ pub struct Recipe {
|
|||
|
||||
impl Recipe {
|
||||
pub fn generate_tags(&self) -> Vec<String> {
|
||||
debug!("Generating image tags for {}", &self.name);
|
||||
trace!("Recipe::generate_tags()");
|
||||
debug!("Generating image tags for {}", &self.name);
|
||||
|
||||
let mut tags: Vec<String> = Vec::new();
|
||||
let image_version = &self.image_version;
|
||||
|
|
@ -144,11 +144,12 @@ pub struct Module {
|
|||
pub struct TemplateCommand {
|
||||
/// The recipe file to create a template from
|
||||
#[arg()]
|
||||
#[builder(setter(into))]
|
||||
recipe: PathBuf,
|
||||
|
||||
/// File to output to instead of STDOUT
|
||||
#[arg(short, long)]
|
||||
#[builder(default, setter(into))]
|
||||
#[builder(default, setter(into, strip_option))]
|
||||
output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue