diff --git a/Cargo.lock b/Cargo.lock index ebc3137..ad42a6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,9 +377,9 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "compose_spec" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11eaf64d1b140e01bb3717dd714bad4f5d58ecf6af41d731bc1e4d9e6b78089e" +checksum = "3fd9b9dc67f2b3024582ec6d861950f0af0aeaabb8350ccda1f0e51ff8e5895c" dependencies = [ "compose_spec_macros", "indexmap", @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "compose_spec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b81b28b4697353a547b8ab24376c3bfe6f8eea6ad2e8f434641488864d997c" +checksum = "b77735bd89be8da01c8d7e61faec5a9ccb0e313cece3c773c6b3ae251b90c7d4" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 73dc0cd..3bdc943 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ verbose_file_reads = "warn" [dependencies] clap = { version = "4.2", features = ["derive", "wrap_help"] } color-eyre = "0.6" -compose_spec = "0.2" +compose_spec = "0.3.0" indexmap = { version = "2", features = ["serde"] } ipnet = { version = "2.7", features = ["serde"] } k8s-openapi = { version = "0.22.0", features = ["latest"] } diff --git a/src/cli/compose.rs b/src/cli/compose.rs index 814b6e0..0018338 100644 --- a/src/cli/compose.rs +++ b/src/cli/compose.rs @@ -11,7 +11,9 @@ use color_eyre::{ eyre::{bail, ensure, eyre, OptionExt, WrapErr}, Help, }; -use compose_spec::{service::Command, Identifier, Network, Networks, Resource, Service, Volumes}; +use compose_spec::{ + service::Command, Identifier, Network, Networks, Options, Resource, Service, Volumes, +}; use indexmap::IndexMap; use crate::quadlet::{self, container::volume::Source, Globals}; @@ -93,8 +95,13 @@ impl Compose { compose_file, } = self; - let compose = read_from_file_or_stdin(compose_file.as_deref()) + let mut options = compose_spec::Compose::options(); + options.apply_merge(true); + let compose = read_from_file_or_stdin(compose_file.as_deref(), &options) .wrap_err("error reading compose file")?; + compose + .validate_all() + .wrap_err("error validating compose file")?; if kube { let mut k8s_file = k8s::File::try_from(compose) @@ -163,10 +170,13 @@ impl Compose { /// - Stdin was selected and stdin is a terminal. /// - No path was given and none of the default files could be opened. /// - There was an error deserializing [`compose_spec::Compose`]. -fn read_from_file_or_stdin(path: Option<&Path>) -> color_eyre::Result { +fn read_from_file_or_stdin( + path: Option<&Path>, + options: &Options, +) -> color_eyre::Result { let (compose_file, path) = if let Some(path) = path { if path.as_os_str() == "-" { - return read_from_stdin(); + return read_from_stdin(options); } let compose_file = fs::File::open(path) .wrap_err("could not open provided compose file") @@ -181,7 +191,7 @@ fn read_from_file_or_stdin(path: Option<&Path>) -> color_eyre::Result) -> color_eyre::Result) -> color_eyre::Result color_eyre::Result { +fn read_from_stdin(options: &Options) -> color_eyre::Result { let stdin = io::stdin(); if stdin.is_terminal() { bail!("cannot read compose from stdin, stdin is a terminal"); } - serde_yaml::from_reader(stdin).wrap_err("data from stdin is not a valid compose file") + options + .from_yaml_reader(stdin) + .wrap_err("data from stdin is not a valid compose file") } /// Attempt to convert [`Service`]s, [`Networks`], and [`Volumes`] into [`File`]s. diff --git a/src/cli/container/compose.rs b/src/cli/container/compose.rs index 46a6d1d..d4cc281 100644 --- a/src/cli/container/compose.rs +++ b/src/cli/container/compose.rs @@ -8,9 +8,9 @@ use compose_spec::{ service::{ build::Context, device::CgroupRule, AbsolutePath, BlkioConfig, Build, ByteValue, Cgroup, Command, ConfigOrSecret, CpuSet, Cpus, CredentialSpec, Deploy, Develop, Device, EnvFile, - Expose, Extends, Healthcheck, Hostname, Image, Ipc, Limit, Link, Logging, MacAddress, - NetworkConfig, OomScoreAdj, Percent, Platform, Ports, PullPolicy, Ulimits, UserOrGroup, - Uts, Volumes, VolumesFrom, + Expose, Extends, Healthcheck, Hostname, IdOrName, Image, Ipc, Limit, Link, Logging, + MacAddress, NetworkConfig, OomScoreAdj, Percent, Platform, Ports, PullPolicy, Ulimits, + User, Uts, Volumes, VolumesFrom, }, Extensions, Identifier, ItemOrList, ListOrMap, MapKey, ShortOrLong, StringOrNumber, }; @@ -319,7 +319,7 @@ pub struct Quadlet { pub environment: ListOrMap, pub expose: IndexSet, pub annotations: ListOrMap, - pub group_add: IndexSet, + pub group_add: IndexSet, pub healthcheck: Option, pub hostname: Option, pub init: bool, @@ -336,7 +336,7 @@ pub struct Quadlet { pub sysctls: ListOrMap, pub tmpfs: Option>, pub ulimits: Ulimits, - pub user: Option, + pub user: Option, pub userns_mode: Option, pub volumes: Volumes, pub working_dir: Option, diff --git a/src/cli/container/quadlet.rs b/src/cli/container/quadlet.rs index fe644d9..c35c6e9 100644 --- a/src/cli/container/quadlet.rs +++ b/src/cli/container/quadlet.rs @@ -933,12 +933,13 @@ fn network_config_try_into_network_options( /// Returns an error if the given `network_mode` is not supported by `podman run --network`. fn validate_network_mode(network_mode: NetworkMode) -> color_eyre::Result { match network_mode { - NetworkMode::None | NetworkMode::Host => Ok(network_mode.to_string()), + NetworkMode::None | NetworkMode::Host | NetworkMode::Container(_) => { + Ok(network_mode.to_string()) + } NetworkMode::Service(_) => Err(eyre!("network_mode `service:` is not supported") .suggestion("try using the `container:` network_mode instead")), NetworkMode::Other(s) => { if s.starts_with("bridge") - || s.starts_with("container") || s.starts_with("ns:") || s == "private" || s.starts_with("slirp4netns") @@ -971,6 +972,7 @@ fn network_options( ipv6_address, link_local_ips, mac_address, + driver_opts, priority, extensions, }: Network, @@ -979,6 +981,10 @@ fn network_options( link_local_ips.is_empty(), "network `link_local_ips` option is not supported" ); + ensure!( + driver_opts.is_empty(), + "container specific network `driver_opts` are not supported" + ); ensure!( priority.is_none(), "network `priority` option is not supported" @@ -1046,7 +1052,7 @@ fn secret_try_into_short( /// /// Returns an error if the [`Ulimit`] has extensions. fn ulimit_try_into_short( - (resource, ulimit): (service::Resource, ShortOrLong), + (resource, ulimit): (service::Resource, ShortOrLong, Ulimit>), ) -> color_eyre::Result { match ulimit { ShortOrLong::Short(ulimit) => Ok(format!("{resource}={ulimit}")), diff --git a/src/cli/k8s/service.rs b/src/cli/k8s/service.rs index 64126aa..66bf8dd 100644 --- a/src/cli/k8s/service.rs +++ b/src/cli/k8s/service.rs @@ -5,7 +5,7 @@ mod mount; use std::{collections::BTreeMap, net::IpAddr, time::Duration}; use color_eyre::{ - eyre::{bail, ensure, eyre, OptionExt, WrapErr}, + eyre::{ensure, eyre, OptionExt, WrapErr}, Section, }; use compose_spec::{ @@ -16,8 +16,8 @@ use compose_spec::{ ports::{self, Port, Protocol}, AbsolutePath, BlkioConfig, Build, ByteValue, Cgroup, Command, ConfigOrSecret, CpuSet, Cpus, CredentialSpec, DependsOn, Deploy, Develop, Device, EnvFile, Expose, Extends, Healthcheck, - Hostname, Image, Ipc, Limit, Link, Logging, MacAddress, NetworkConfig, OomScoreAdj, - Percent, Platform, Ports, PullPolicy, Restart, Ulimits, UserOrGroup, Uts, Volumes, + Hostname, IdOrName, Image, Ipc, Limit, Link, Logging, MacAddress, NetworkConfig, + OomScoreAdj, Percent, Platform, Ports, PullPolicy, Restart, Ulimits, User, Uts, Volumes, VolumesFrom, }, Extensions, Identifier, ItemOrList, ListOrMap, Map, ShortOrLong, @@ -535,7 +535,7 @@ struct ContainerSecurityContext { privileged: bool, read_only: bool, security_opt: IndexSet, - user: Option, + user: Option, } impl ContainerSecurityContext { @@ -594,14 +594,23 @@ impl ContainerSecurityContext { .se_linux_options = Some(se_linux_options); } - if let Some(user) = user { - let user = match user { - UserOrGroup::Id(user) => user, - UserOrGroup::Name(_) => bail!("only numeric UIDs are supported for `user`"), - }; - security_context - .get_or_insert_with(SecurityContext::default) - .run_as_user = Some(user.into()); + if let Some(User { user, group }) = user { + let user = user + .as_id() + .ok_or_eyre("only numeric UIDs are supported for `user`")? + .into(); + let group = group + .map(|group| { + group + .as_id() + .ok_or_eyre("only numeric GIDs are supported for `user`") + }) + .transpose()? + .map(Into::into); + + let security_context = security_context.get_or_insert_with(SecurityContext::default); + security_context.run_as_user = Some(user); + security_context.run_as_group = group; } Ok(security_context) @@ -699,7 +708,7 @@ struct Unsupported { annotations: ListOrMap, external_links: IndexSet, extra_hosts: IndexMap, - group_add: IndexSet, + group_add: IndexSet, hostname: Option, init: bool, ipc: Option,