diff --git a/Cargo.lock b/Cargo.lock index edbea1d88..5e0516a9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -896,7 +896,7 @@ dependencies = [ [[package]] name = "cache" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "tokio", @@ -1063,7 +1063,7 @@ dependencies = [ [[package]] name = "command" -version = "1.19.4" +version = "1.19.5" dependencies = [ "komodo_client", "run_command", @@ -1089,7 +1089,7 @@ checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" [[package]] name = "config" -version = "1.19.4" +version = "1.19.5" dependencies = [ "colored", "indexmap 2.11.1", @@ -1330,7 +1330,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "database" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "async-compression", @@ -1638,7 +1638,7 @@ dependencies = [ [[package]] name = "environment" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "formatting", @@ -1648,7 +1648,7 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.19.4" +version = "1.19.5" dependencies = [ "thiserror 2.0.16", ] @@ -1744,7 +1744,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.19.4" +version = "1.19.5" dependencies = [ "serror", ] @@ -1906,7 +1906,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "cache", @@ -2505,7 +2505,7 @@ dependencies = [ [[package]] name = "interpolate" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "komodo_client", @@ -2636,7 +2636,7 @@ dependencies = [ [[package]] name = "komodo_cli" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "chrono", @@ -2661,7 +2661,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "async_timing_util", @@ -2696,7 +2696,7 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "arc-swap", @@ -2766,7 +2766,7 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "arc-swap", @@ -2888,7 +2888,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "komodo_client", @@ -3643,7 +3643,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "periphery_client" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "komodo_client", @@ -4174,7 +4174,7 @@ dependencies = [ [[package]] name = "response" -version = "1.19.4" +version = "1.19.5" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 76da890a1..8a947a26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "1.19.4" +version = "1.19.5" edition = "2024" authors = ["mbecker20 "] license = "GPL-3.0-or-later" diff --git a/bin/core/src/api/execute/build.rs b/bin/core/src/api/execute/build.rs index c54a14cc0..a798e44ce 100644 --- a/bin/core/src/api/execute/build.rs +++ b/bin/core/src/api/execute/build.rs @@ -27,7 +27,7 @@ use komodo_client::{ build::{Build, BuildConfig}, builder::{Builder, BuilderConfig}, deployment::DeploymentState, - komodo_timestamp, + komodo_timestamp, optional_string, permission::PermissionLevel, repo::Repo, update::{Log, Update}, @@ -290,12 +290,10 @@ impl Resolve for RunBuild { repo, registry_tokens, replacers: secret_replacers.into_iter().collect(), - // Push a commit hash tagged image - additional_tags: if update.commit_hash.is_empty() { - Default::default() - } else { - vec![update.commit_hash.clone()] - }, + // To push a commit hash tagged image + commit_hash: optional_string(&update.commit_hash), + // Unused for now + additional_tags: Default::default(), }) => res.context("failed at call to periphery to build"), _ = cancel.cancelled() => { info!("build cancelled during build, cleaning up builder"); diff --git a/bin/core/src/api/execute/deployment.rs b/bin/core/src/api/execute/deployment.rs index 1cdbebb37..211f16a53 100644 --- a/bin/core/src/api/execute/deployment.rs +++ b/bin/core/src/api/execute/deployment.rs @@ -12,7 +12,7 @@ use komodo_client::{ deployment::{ Deployment, DeploymentImage, extract_registry_domain, }, - get_image_names, komodo_timestamp, optional_string, + komodo_timestamp, optional_string, permission::PermissionLevel, server::Server, update::{Log, Update}, @@ -115,7 +115,7 @@ impl Resolve for Deploy { let (version, registry_token) = match &deployment.config.image { DeploymentImage::Build { build_id, version } => { let build = resource::get::(build_id).await?; - let image_names = get_image_names(&build); + let image_names = build.get_image_names(); let image_name = image_names .first() .context("No image name could be created") @@ -249,7 +249,7 @@ pub async fn pull_deployment_inner( let (image, account, token) = match deployment.config.image { DeploymentImage::Build { build_id, version } => { let build = resource::get::(&build_id).await?; - let image_names = get_image_names(&build); + let image_names = build.get_image_names(); let image_name = image_names .first() .context("No image name could be created") diff --git a/bin/periphery/src/api/build.rs b/bin/periphery/src/api/build.rs index ac18ab8f5..be9a3db72 100644 --- a/bin/periphery/src/api/build.rs +++ b/bin/periphery/src/api/build.rs @@ -12,7 +12,7 @@ use interpolate::Interpolator; use komodo_client::entities::{ EnvironmentVar, all_logs_success, build::{Build, BuildConfig}, - environment_vars_from_str, get_image_names, optional_string, + environment_vars_from_str, optional_string, to_path_compatible_name, update::Log, }; @@ -25,9 +25,7 @@ use resolver_api::Resolve; use tokio::fs; use crate::{ - build::{ - image_tags, parse_build_args, parse_secret_args, write_dockerfile, - }, + build::{parse_build_args, parse_secret_args, write_dockerfile}, config::periphery_config, docker::docker_login, helpers::{parse_extra_args, parse_labels}, @@ -126,8 +124,9 @@ impl Resolve for build::Build { mut build, repo: linked_repo, registry_tokens, - additional_tags, mut replacers, + commit_hash, + additional_tags, } = self; let mut logs = Vec::new(); @@ -145,8 +144,6 @@ impl Resolve for build::Build { name, config: BuildConfig { - version, - image_tag, build_path, dockerfile_path, build_args, @@ -265,8 +262,6 @@ impl Resolve for build::Build { // Get command parts - let image_names = get_image_names(&build); - // Add VERSION to build args (if not already there) let mut build_args = environment_vars_from_str(build_args) .context("Invalid build_args")?; @@ -291,9 +286,9 @@ impl Resolve for build::Build { let buildx = if *use_buildx { " buildx" } else { "" }; - let image_tags = - image_tags(&image_names, image_tag, version, &additional_tags) - .context("Failed to parse image tags into command")?; + let image_tags = build + .get_image_tags_as_arg(commit_hash.as_deref(), &additional_tags) + .context("Failed to parse image tags into command")?; let maybe_push = if should_push { " --push" } else { "" }; diff --git a/bin/periphery/src/build.rs b/bin/periphery/src/build.rs index 0d1b05982..65d7c4af7 100644 --- a/bin/periphery/src/build.rs +++ b/bin/periphery/src/build.rs @@ -6,7 +6,7 @@ use std::{ use anyhow::{Context, anyhow}; use formatting::format_serror; use komodo_client::{ - entities::{EnvironmentVar, Version, update::Log}, + entities::{EnvironmentVar, update::Log}, parsers::QUOTE_PATTERN, }; @@ -52,34 +52,6 @@ pub async fn write_dockerfile( } } -pub fn image_tags( - image_names: &[String], - custom_tag: &str, - version: &Version, - additional: &[String], -) -> anyhow::Result { - let Version { major, minor, .. } = version; - let custom_tag = if custom_tag.is_empty() { - String::new() - } else { - format!("-{custom_tag}") - }; - - let mut res = String::new(); - - for image_name in image_names { - write!( - &mut res, - " -t {image_name}:latest{custom_tag} -t {image_name}:{version}{custom_tag} -t {image_name}:{major}.{minor}{custom_tag} -t {image_name}:{major}{custom_tag}" - )?; - for tag in additional { - write!(&mut res, " -t {image_name}:{tag}{custom_tag}")?; - } - } - - Ok(res) -} - pub fn parse_build_args(build_args: &[EnvironmentVar]) -> String { build_args .iter() diff --git a/client/core/rs/src/entities/build.rs b/client/core/rs/src/entities/build.rs index 3807ee112..b9cde08b0 100644 --- a/client/core/rs/src/entities/build.rs +++ b/client/core/rs/src/entities/build.rs @@ -1,4 +1,4 @@ -use std::sync::OnceLock; +use std::{fmt::Write, sync::OnceLock}; use bson::{Document, doc}; use derive_builder::Builder; @@ -26,6 +26,127 @@ use super::{ #[typeshare] pub type Build = Resource; +impl Build { + pub fn get_image_names(&self) -> Vec { + let Build { + name, + config: + BuildConfig { + image_name, + image_registry, + .. + }, + .. + } = self; + let name = if image_name.is_empty() { + name + } else { + image_name + }; + // Local only + if image_registry.is_empty() { + return vec![name.to_string()]; + } + image_registry + .iter() + .map( + |ImageRegistryConfig { + domain, + account, + organization, + }| { + match ( + !domain.is_empty(), + !organization.is_empty(), + !account.is_empty(), + ) { + // If organization and account provided, name under organization. + (true, true, true) => { + format!("{domain}/{organization}/{name}") + } + // Just domain / account provided + (true, false, true) => { + format!("{domain}/{account}/{name}") + } + // Otherwise, just use name (local only) + _ => name.to_string(), + } + }, + ) + .collect() + } + + pub fn get_image_tags( + &self, + image_names: &[String], + commit_hash: Option<&str>, + additional: &[String], + ) -> Vec { + let BuildConfig { + version, + image_tag, + include_latest_tag, + include_version_tags: include_version_tag, + include_commit_tag, + .. + } = &self.config; + + let Version { major, minor, .. } = version; + + let image_tag_postfix = if image_tag.is_empty() { + String::new() + } else { + format!("-{image_tag}") + }; + + let mut tags = Vec::new(); + + for image_name in image_names { + // Pure image tag passthrough when provided + if !image_tag.is_empty() { + tags.push(format!("{image_name}:{image_tag}")); + } + // `:latest` / `:latest-tag` + if *include_latest_tag { + tags.push(format!("{image_name}:latest{image_tag_postfix}")); + } + // `:1.19.5` + `:1.19` etc. / `1.19.5-tag` + if *include_version_tag { + tags + .push(format!("{image_name}:{version}{image_tag_postfix}")); + tags.push(format!( + "{image_name}:{major}.{minor}{image_tag_postfix}" + )); + tags.push(format!("{image_name}:{major}{image_tag_postfix}")); + } + if *include_commit_tag && let Some(hash) = commit_hash { + tags.push(format!("{image_name}:{hash}{image_tag_postfix}")); + } + for tag in additional { + tags.push(format!("{image_name}:{tag}")) + } + } + + tags + } + + pub fn get_image_tags_as_arg( + &self, + commit_hash: Option<&str>, + additional: &[String], + ) -> anyhow::Result { + let mut res = String::new(); + for image_tag in self.get_image_tags( + &self.get_image_names(), + commit_hash, + additional, + ) { + write!(&mut res, " -t {image_tag}")?; + } + Ok(res) + } +} + #[typeshare] pub type BuildListItem = ResourceListItem; @@ -169,6 +290,24 @@ pub struct BuildConfig { #[builder(default)] pub image_tag: String, + /// Push `:latest` / `:latest-image_tag` tags. + #[serde(default = "default_include_tag")] + #[builder(default = "default_include_tag()")] + #[partial_default(default_include_tag())] + pub include_latest_tag: bool, + + /// Push build version semver `:1.19.5` + `1.19` / `:1.19.5-image_tag` tags. + #[serde(default = "default_include_tag")] + #[builder(default = "default_include_tag()")] + #[partial_default(default_include_tag())] + pub include_version_tags: bool, + + /// Push commit hash `:a6v8h83` / `:a6v8h83-image_tag` tags. + #[serde(default = "default_include_tag")] + #[builder(default = "default_include_tag()")] + #[partial_default(default_include_tag())] + pub include_commit_tag: bool, + /// Configure quick links that are displayed in the resource header #[serde(default, deserialize_with = "string_list_deserializer")] #[partial_attr(serde( @@ -344,6 +483,10 @@ fn default_auto_increment_version() -> bool { true } +fn default_include_tag() -> bool { + true +} + fn default_git_provider() -> String { String::from("github.com") } @@ -377,6 +520,9 @@ impl Default for BuildConfig { auto_increment_version: default_auto_increment_version(), image_name: Default::default(), image_tag: Default::default(), + include_latest_tag: default_include_tag(), + include_version_tags: default_include_tag(), + include_commit_tag: default_include_tag(), links: Default::default(), linked_repo: Default::default(), git_provider: default_git_provider(), diff --git a/client/core/rs/src/entities/mod.rs b/client/core/rs/src/entities/mod.rs index a9498473b..6a94c3016 100644 --- a/client/core/rs/src/entities/mod.rs +++ b/client/core/rs/src/entities/mod.rs @@ -5,7 +5,6 @@ use std::{ use anyhow::Context; use async_timing_util::unix_timestamp_ms; -use build::ImageRegistryConfig; use clap::Parser; use derive_empty_traits::EmptyTraits; use derive_variants::{EnumVariants, ExtractVariant}; @@ -129,54 +128,6 @@ pub fn optional_string(string: impl Into) -> Option { } } -pub fn get_image_names( - build::Build { - name, - config: - build::BuildConfig { - image_name, - image_registry, - .. - }, - .. - }: &build::Build, -) -> Vec { - let name = if image_name.is_empty() { - name - } else { - image_name - }; - // Local only - if image_registry.is_empty() { - return vec![name.to_string()]; - } - image_registry - .iter() - .map( - |ImageRegistryConfig { - domain, - account, - organization, - }| { - match ( - !domain.is_empty(), - !organization.is_empty(), - !account.is_empty(), - ) { - // If organization and account provided, name under organization. - (true, true, true) => { - format!("{domain}/{organization}/{name}") - } - // Just domain / account provided - (true, false, true) => format!("{domain}/{account}/{name}"), - // Otherwise, just use name (local only) - _ => name.to_string(), - } - }, - ) - .collect() -} - pub fn to_general_name(name: &str) -> String { name.trim().replace('\n', "_").to_string() } diff --git a/client/core/ts/package.json b/client/core/ts/package.json index 408919ea8..6c8b6d355 100644 --- a/client/core/ts/package.json +++ b/client/core/ts/package.json @@ -1,6 +1,6 @@ { "name": "komodo_client", - "version": "1.19.4", + "version": "1.19.5", "description": "Komodo client package", "homepage": "https://komo.do", "main": "dist/lib.js", diff --git a/client/core/ts/src/lib.ts b/client/core/ts/src/lib.ts index bb473a72f..5b7f0085d 100644 --- a/client/core/ts/src/lib.ts +++ b/client/core/ts/src/lib.ts @@ -199,6 +199,11 @@ export function KomodoClient(url: string, options: InitOptions) { } else { // it is a single update const update = res as any as Update; + + if (update.status === UpdateStatus.Complete || !update._id?.$oid) { + return update; + } + return await poll_update_until_complete(update._id?.$oid!); } }; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 84ea19f3c..248fbed4c 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -598,6 +598,12 @@ export interface BuildConfig { * independantly versioned tags. */ image_tag?: string; + /** Push `:latest` / `:latest-image_tag` tags. */ + include_latest_tag: boolean; + /** Push build version semver `:1.19.5` + `1.19` / `:1.19.5-image_tag` tags. */ + include_version_tags: boolean; + /** Push commit hash `:a6v8h83` / `:a6v8h83-image_tag` tags. */ + include_commit_tag: boolean; /** Configure quick links that are displayed in the resource header */ links?: string[]; /** Choose a Komodo Repo (Resource) to source the build files. */ diff --git a/client/periphery/rs/src/api/build.rs b/client/periphery/rs/src/api/build.rs index 33e62ff58..b9045d1ba 100644 --- a/client/periphery/rs/src/api/build.rs +++ b/client/periphery/rs/src/api/build.rs @@ -18,6 +18,8 @@ pub struct Build { /// Propogate any secret replacers from core interpolation. #[serde(default)] pub replacers: Vec<(String, String)>, + /// Pass the commit hash to use with tagging + pub commit_hash: Option, /// Add more tags for this build in addition to the version tags. #[serde(default)] pub additional_tags: Vec, diff --git a/frontend/public/client/lib.js b/frontend/public/client/lib.js index e1ff19d7e..3aca95f68 100644 --- a/frontend/public/client/lib.js +++ b/frontend/public/client/lib.js @@ -88,6 +88,9 @@ export function KomodoClient(url, options) { else { // it is a single update const update = res; + if (update.status === UpdateStatus.Complete || !update._id?.$oid) { + return update; + } return await poll_update_until_complete(update._id?.$oid); } }; diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 9d4299c83..b4c3a3b66 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -595,6 +595,12 @@ export interface BuildConfig { * independantly versioned tags. */ image_tag?: string; + /** Push `:latest` / `:latest-image_tag` tags. */ + include_latest_tag: boolean; + /** Push build version semver `:1.19.5` + `1.19` / `:1.19.5-image_tag` tags. */ + include_version_tags: boolean; + /** Push commit hash `:a6v8h83` / `:a6v8h83-image_tag` tags. */ + include_commit_tag: boolean; /** Configure quick links that are displayed in the resource header */ links?: string[]; /** Choose a Komodo Repo (Resource) to source the build files. */ diff --git a/frontend/src/components/resources/build/config.tsx b/frontend/src/components/resources/build/config.tsx index 46b37922b..3fccb8af0 100644 --- a/frontend/src/components/resources/build/config.tsx +++ b/frontend/src/components/resources/build/config.tsx @@ -163,6 +163,7 @@ export const BuildConfig = ({ const version_component: ConfigComponent = { label: "Version", + labelHidden: true, components: { version: (_version, set) => { const version = @@ -173,6 +174,7 @@ export const BuildConfig = ({ [] = [ { @@ -292,6 +296,29 @@ export const BuildConfig = ({ ), }, }, + { + label: "Tagging", + labelHidden: true, + components: { + image_name: { + description: "Push the image under a different name", + placeholder: "Custom image name", + }, + image_tag: { + description: `Push a custom tag, plus postfix the other tags (eg ':latest-${customTag ? customTag : ""}').`, + placeholder: "Custom image tag", + }, + include_latest_tag: { + description: `:latest${customTagPostfix}`, + }, + include_version_tags: { + description: `:X.Y.Z${customTagPostfix} + :X.Y${customTagPostfix} + :X${customTagPostfix}`, + }, + include_commit_tag: { + description: `:ae8f8ff${customTagPostfix}`, + }, + }, + }, { label: "Links", labelHidden: true, @@ -314,19 +341,6 @@ export const BuildConfig = ({ ]; const advanced: ConfigComponent[] = [ - { - label: "Tagging", - components: { - image_name: { - description: "Push the image under a different name", - placeholder: "Custom image name", - }, - image_tag: { - description: "Postfix the image version with a custom tag.", - placeholder: "Custom image tag", - }, - }, - }, { label: "Pre Build", description: