diff --git a/src/cli/build.rs b/src/cli/build.rs index c32918e..7f4cf53 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -358,8 +358,8 @@ impl TryFrom for Build { let mut tags = tags.into_iter(); let tag = tags .next() - .ok_or_eyre("an image tag is required")? - .into_inner(); + .map(|t| t.into_inner()) + .unwrap_or_else(|| "latest".to_string()); ensure!( tags.next().is_none(), "Quadlet only supports setting a single tag" diff --git a/src/cli/compose.rs b/src/cli/compose.rs index 2f00a28..449a86e 100644 --- a/src/cli/compose.rs +++ b/src/cli/compose.rs @@ -16,6 +16,8 @@ use compose_spec::{ }; use indexmap::IndexMap; +use cli::Service as ServiceUnit; +use crate::cli; use crate::quadlet::{self, container::volume::Source, Globals}; use super::{k8s, Build, Container, File, GlobalArgs, Unit}; @@ -95,6 +97,10 @@ impl Compose { compose_file, } = self; + // get the parent directory of the provided compose_file (if exists) + let compose_parent: Option = compose_file.clone().and_then(|file| { + Some(PathBuf::from(file.parent().unwrap())) + }); let mut options = compose_spec::Compose::options(); options.apply_merge(true); let compose = read_from_file_or_stdin(compose_file.as_deref(), &options) @@ -103,18 +109,28 @@ impl Compose { .validate_all() .wrap_err("error validating compose file")?; + let build_required = compose.services.iter().find(|(identifier, service)| { + service.build.is_some() + }).is_some(); + if kube { let mut k8s_file = k8s::File::try_from(compose) .wrap_err("error converting compose file into Kubernetes YAML")?; - let kube = + let mut kube = quadlet::Kube::new(PathBuf::from(format!("{}-kube.yaml", k8s_file.name)).into()); + + // if one of the compose services has a build section let's add --build=true to the podman args. + if build_required { + kube.push_arg("build", "true"); + } + let quadlet_file = quadlet::File { name: k8s_file.name.clone(), unit, resource: kube.into(), globals: Globals::default(), - service: None, + service: if build_required { Some(ServiceUnit::from(compose_parent.unwrap())) } else { None }, install, }; diff --git a/src/cli/k8s/service.rs b/src/cli/k8s/service.rs index 66bf8dd..8d1cda1 100644 --- a/src/cli/k8s/service.rs +++ b/src/cli/k8s/service.rs @@ -43,6 +43,7 @@ use self::mount::tmpfs_and_volumes_try_into_volume_mounts; #[allow(clippy::struct_excessive_bools)] pub(super) struct Service { unsupported: Unsupported, + build: Option>, name: Identifier, resources: ContainerResources, security_context: ContainerSecurityContext, @@ -154,7 +155,6 @@ impl Service { Self { unsupported: Unsupported { attach, - build, blkio_config, cpu_count, cpu_percent, @@ -230,6 +230,7 @@ impl Service { security_opt, user, }, + build, command, entrypoint, environment, @@ -268,6 +269,7 @@ impl Service { tty, volumes, working_dir, + build } = self; unsupported.ensure_empty()?; @@ -277,6 +279,16 @@ impl Service { // converting `tmpfs` always succeeds .wrap_err("error converting `volumes`")?; + let container_image: String; + if build.is_some() { + container_image = match build.unwrap() { + ShortOrLong::Short(build) => Some(build), + ShortOrLong::Long(build) => build.context, + }.unwrap().into_string().unwrap(); + } else { + container_image = image.ok_or_eyre("`image` is required")?.into_inner(); + } + spec.containers.push(Container { name: name.into(), resources: resources.into_resource_requirements(), @@ -316,7 +328,7 @@ impl Service { }) .transpose() .wrap_err("error converting `healthcheck`")?, - image: Some(image.ok_or_eyre("`image` is required")?.into_inner()), + image: Some(container_image), ports: (!ports.is_empty()) .then(|| { ports::into_long_iter(ports) @@ -679,7 +691,6 @@ fn security_opt_try_into_selinux_options( /// [`Container`]s. struct Unsupported { attach: bool, - build: Option>, blkio_config: Option, cpu_count: Option, cpu_percent: Option, @@ -752,7 +763,6 @@ impl Unsupported { fn ensure_empty(&self) -> color_eyre::Result<()> { let Self { attach, - build, blkio_config, cpu_count, cpu_percent, @@ -817,7 +827,6 @@ impl Unsupported { let unsupported_options = [ ("attach", *attach), - ("build", build.is_none()), ("blkio_config", blkio_config.is_none()), ("cpu_count", cpu_count.is_none()), ("cpu_percent", cpu_percent.is_none()), diff --git a/src/cli/service.rs b/src/cli/service.rs index d3686fe..597f9f6 100644 --- a/src/cli/service.rs +++ b/src/cli/service.rs @@ -1,5 +1,5 @@ use std::fmt::{self, Display, Formatter}; - +use std::path::{ PathBuf}; use clap::{Args, ValueEnum}; use compose_spec::service::Restart; @@ -8,6 +8,7 @@ pub struct Service { /// Configure if and when the service should be restarted #[arg(long, value_name = "POLICY")] restart: Option, + working_directory: Option, } impl Service { @@ -22,6 +23,9 @@ impl Display for Service { if let Some(restart) = self.restart.and_then(|restart| restart.to_possible_value()) { writeln!(f, "Restart={}", restart.get_name())?; } + if let Some(working_directory) = self.working_directory.as_ref().and_then(|working_directory| working_directory.into()) { + writeln!(f, "WorkingDirectory={}", working_directory.display())?; + } Ok(()) } } @@ -30,6 +34,16 @@ impl From for Service { fn from(restart: RestartConfig) -> Self { Self { restart: Some(restart), + working_directory: None, + } + } +} + +impl From for Service { + fn from(working_directory: PathBuf) -> Self { + Self { + restart: None, + working_directory: Some(working_directory), } } } diff --git a/src/quadlet/kube.rs b/src/quadlet/kube.rs index b97b498..faeacef 100644 --- a/src/quadlet/kube.rs +++ b/src/quadlet/kube.rs @@ -58,14 +58,14 @@ impl Kube { } /// Add `--{flag} {arg}` to `PodmanArgs=`. - fn push_arg(&mut self, flag: &str, arg: &str) { + pub(crate) fn push_arg(&mut self, flag: &str, arg: &str) { let podman_args = self.podman_args.get_or_insert_with(String::new); if !podman_args.is_empty() { podman_args.push(' '); } podman_args.push_str("--"); podman_args.push_str(flag); - podman_args.push(' '); + podman_args.push('='); podman_args.push_str(arg); } }