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
635 changes: 187 additions & 448 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ reqwest = { version = "0.13.2", features = ["json"] }
rstest = "0.26.1"
secrecy = "0.10.3"
regex = "1.12.3"
semver = "1.0.28"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.140"
snafu = "0.9.0"
Expand Down
5 changes: 4 additions & 1 deletion rust/boil/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ publish = false

[dependencies]
cap-std.workspace = true
clap.workspace = true
# We use the unstable-markdown feature to get proper markdown list rendering.
# See tracking issue: https://github.com/clap-rs/clap/issues/5900
clap = { workspace = true, features = ["unstable-markdown"] }
Comment thread
Techassi marked this conversation as resolved.
clap_complete.workspace = true
clap_complete_nushell.workspace = true
git2.workspace = true
Expand All @@ -19,6 +21,7 @@ oci-spec.workspace = true
reqwest.workspace = true
regex.workspace = true
secrecy.workspace = true
semver.workspace = true
serde.workspace = true
serde_json.workspace = true
snafu.workspace = true
Expand Down
22 changes: 17 additions & 5 deletions rust/boil/src/cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ pub struct BuildArguments {
#[arg(help_heading = "Image Options", required = true)]
pub images: Vec<ImageSelector>,

// NOTE (@Techassi): Should this maybe be renamed to vendor_version?
/// The image version being built.
/// The vendor version being built.
#[arg(
short, long,
value_parser = Cli::parse_image_version,
default_value_t = Cli::default_image_version(),
// TODO (@Techassi): Eventually remove these aliases
short_alias = 'i',
alias = "image-version",
value_parser = Cli::parse_vendor_version,
default_value_t = Cli::default_vendor_version(),
help_heading = "Image Options"
)]
pub image_version: String,
pub vendor_version: String,

/// Target platform of the image.
#[arg(
Expand Down Expand Up @@ -111,6 +113,16 @@ pub struct BuildArguments {
#[arg(long, help_heading = "Build Options")]
pub strip_architecture: bool,

/// Produces a floating tag (if needed) in addition to the fully-qualified tag.
///
/// Examples:
///
/// - `4.5.6-prefix3.2.1` -> `4.5.6-prefix3.2`
/// - `4.5.6-prefix0.0.0-dev` -> `4.5.6-prefix0.0.0-dev`
/// - `4.5.6-prefix0.0.0-pr123` -> `4.5.6-prefix0.0.0-pr123`
#[arg(long, help_heading = "Build Options")]
pub floating_tag: bool,

/// Loads the image into the local image store.
///
/// DEPRECATED: Use -- --load instead.
Expand Down
16 changes: 9 additions & 7 deletions rust/boil/src/cli/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ pub struct ImageCheckArguments {
/// The image version to check.
#[arg(
short, long,
value_parser = Cli::parse_image_version,
default_value_t = Cli::default_image_version(),
value_parser = Cli::parse_vendor_version,
default_value_t = Cli::default_vendor_version(),
help_heading = "Image Options"
)]
pub image_version: String,
Expand All @@ -59,15 +59,17 @@ pub struct ImageSizeArguments {
/// Optionally specify one or more images to check. Checks all images by default.
pub image: Vec<ImageSelector>,

// NOTE (@Techassi): Should this maybe be renamed to vendor_version?
/// The image version to use.
/// The vendor version to use.
#[arg(
short, long,
value_parser = Cli::parse_image_version,
default_value_t = Cli::default_image_version(),
// TODO (@Techassi): Eventually remove these aliases
short_alias = 'i',
alias = "image-version",
value_parser = Cli::parse_vendor_version,
default_value_t = Cli::default_vendor_version(),
help_heading = "Image Options"
)]
pub image_version: String,
pub vendor_version: String,

/// Target platform of the image.
#[arg(
Expand Down
6 changes: 3 additions & 3 deletions rust/boil/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ static VALID_IMAGE_TAG: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_][a-zA-Z0-9_.-]+$").expect("regex is valid"));

#[derive(Debug, Snafu)]
pub enum ParseImageVersionError {
pub enum ParseVendorVersionError {
#[snafu(display("invalid image tag characters for {version:?}"))]
ParseVersion { version: String },
}
Expand All @@ -41,7 +41,7 @@ impl Cli {
PathBuf::from("./boil.toml")
}

pub(super) fn default_image_version() -> String {
pub(super) fn default_vendor_version() -> String {
"0.0.0-dev".to_owned()
}

Expand All @@ -53,7 +53,7 @@ impl Cli {
}

/// Ensure that the given version will be valid for use in the image tag
pub(super) fn parse_image_version(version: &str) -> Result<String, ParseImageVersionError> {
pub(super) fn parse_vendor_version(version: &str) -> Result<String, ParseVendorVersionError> {
if !VALID_IMAGE_TAG.is_match(version) {
return ParseVersionSnafu { version }.fail();
}
Expand Down
1 change: 0 additions & 1 deletion rust/boil/src/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ pub enum Error {
/// This is the `boil build` command handler function.
pub fn run_command(args: Box<BuildArguments>, config: Config) -> Result<(), Error> {
// TODO (@Techassi): Parse Dockerfile instead to build the target graph
// Create bakefile
let bakefile = Bakefile::from_cli_args(&args, config).context(CreateBakefileSnafu)?;
let image_manifest_uris = bakefile.image_manifest_uris();
let count = image_manifest_uris.len();
Expand Down
2 changes: 1 addition & 1 deletion rust/boil/src/cmd/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ pub async fn calculate_size(arguments: ImageSizeArguments, config: Config) -> Re
let image_index_manifest_tag = format_image_index_manifest_tag(
image_version,
&config.metadata.vendor_tag_prefix,
&arguments.image_version,
&arguments.vendor_version,
);

let manifest_tag = format_image_manifest_tag(
Expand Down
43 changes: 38 additions & 5 deletions rust/boil/src/core/bakefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ pub enum Error {
source: std::io::Error,
path: String,
},

#[snafu(display("failed to parse floating vendor version"))]
ParseFloatingVendorVersion {
source: utils::ParseFloatingVendorVersionError,
},
}

#[derive(Debug, Snafu)]
Expand Down Expand Up @@ -318,7 +323,7 @@ impl Bakefile {
let target = BakefileTarget::common(
date_time,
revision,
cli_args.image_version.clone(),
cli_args.vendor_version.clone(),
container_build_args,
user_container_build_args,
metadata,
Expand All @@ -332,6 +337,10 @@ impl Bakefile {
cli_args: &cli::BuildArguments,
config: Config,
) -> Result<Self, Error> {
let floating_vendor_version =
utils::parse_floating_vendor_version(&cli_args.vendor_version, cli_args.floating_tag)
.context(ParseFloatingVendorVersionSnafu)?;

let mut bakefile_targets = BTreeMap::new();
let mut groups: BTreeMap<String, BakefileGroup> = BTreeMap::new();

Expand Down Expand Up @@ -364,7 +373,7 @@ impl Bakefile {
let image_index_manifest_tag = utils::format_image_index_manifest_tag(
&image_version,
&metadata.vendor_tag_prefix,
&cli_args.image_version,
&cli_args.vendor_version,
);

let image_manifest_tag = utils::format_image_manifest_tag(
Expand Down Expand Up @@ -400,7 +409,7 @@ impl Bakefile {
));
build_arguments.insert(docker::BuildArgument::new(
"IMAGE_REPOSITORY_URI".to_owned(),
image_repository_uri,
image_repository_uri.clone(),
));
build_arguments.insert(docker::BuildArgument::new(
"IMAGE_INDEX_MANIFEST_TAG".to_owned(),
Expand All @@ -415,6 +424,30 @@ impl Bakefile {
image_manifest_uri.clone(),
));

let tags = if let Some(floating_vendor_version) = floating_vendor_version.as_deref()
{
let image_index_manifest_floating_tag = utils::format_image_index_manifest_tag(
&image_version,
&metadata.vendor_tag_prefix,
floating_vendor_version,
);

let image_manifest_floating_tag = utils::format_image_manifest_tag(
&image_index_manifest_floating_tag,
cli_args.target_platform.architecture(),
cli_args.strip_architecture,
);

let floating_image_manifest_uri = utils::format_image_manifest_uri(
&image_repository_uri,
&image_manifest_floating_tag,
);

vec![image_manifest_uri, floating_image_manifest_uri]
} else {
vec![image_manifest_uri]
};

// By using a cap-std Dir, we can ensure that the paths provided must be relative to
// the appropriate image folder and wont escape it by providing absolute or relative
// paths with traversals (..).
Expand Down Expand Up @@ -461,11 +494,11 @@ impl Bakefile {
let annotations = BakefileTarget::image_version_annotation(
&image_version,
&metadata.vendor_tag_prefix,
&cli_args.image_version,
&cli_args.vendor_version,
);

let target = BakefileTarget {
tags: vec![image_manifest_uri],
tags,
arguments: build_arguments,
platforms: vec![cli_args.target_platform.clone()],
// NOTE (@Techassi): Should this instead be scoped to the folder of the image we build
Expand Down
61 changes: 60 additions & 1 deletion rust/boil/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
use std::process::Command;
use std::{process::Command, str::FromStr};

use snafu::{ResultExt, Snafu};

use crate::{cli::HostPort, core::platform::Architecture};

// FIXME (@Techassi): We should pull this in from a central pice of code, like stackable-shared.
// stackable-shared needs to add a few features to be able to properly select _only_ what is needed
// without pulling in too many unused deps.
pub trait VersionExt {
fn is_floating(&self) -> bool;

fn floating(&self) -> String;
}

impl VersionExt for semver::Version {
fn is_floating(&self) -> bool {
self.major == 0
&& self.minor == 0
&& self.patch == 0
&& (self.pre.starts_with("pr") || self.pre.as_str() == "dev")
Comment thread
Techassi marked this conversation as resolved.
}

fn floating(&self) -> String {
if self.is_floating() {
self.to_string()
} else {
format!("{major}.{minor}", major = self.major, minor = self.minor)
}
}
}

#[derive(Debug, Snafu)]
pub enum ParseFloatingVendorVersionError {
#[snafu(display("failed to parse {version:?} as semantic version"))]
ParseSemanticVersion {
source: semver::Error,
version: String,
},
}

/// Formats and returns the image repository URI, eg. `oci.stackable.tech/sdp/opa`.
pub fn format_image_repository_uri(
image_registry: &HostPort,
Expand Down Expand Up @@ -49,6 +86,28 @@ pub fn format_registry_token_env_var_name(registry_uri: &str) -> String {
)
}

pub fn parse_floating_vendor_version(
vendor_image_version: &str,
floating_tag: bool,
) -> Result<Option<String>, ParseFloatingVendorVersionError> {
if floating_tag {
let version =
semver::Version::from_str(vendor_image_version).context(ParseSemanticVersionSnafu {
version: vendor_image_version,
})?;

// Return None because the selected version is already considered a floating version as is.
// There is no need to add a second, identical tag to the image.
if version.is_floating() {
return Ok(None);
}

Ok(Some(version.floating()))
} else {
Ok(None)
}
}

pub trait CommandExt {
/// Adds an argument to the command if the `predicate` is `true`.
fn arg_if<S>(&mut self, predicate: bool, arg: S) -> &mut Self
Expand Down
Loading