From 3c4877e66ce1ee5c1abf2673794520e248e04ecb Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Fri, 12 Apr 2024 18:35:20 -0500 Subject: [PATCH 01/16] fix(service)!: allow for unlimited `pids_limit` Generalize `service::MemswapLimit` into `service::Limit`. `Service.memswap_limit` is now a `Option>`. `Service.pids_limit` is now a `Option>`. `service::deploy::resources::Limits.pids` is now a `Option>`. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/service.rs | 8 +-- src/service/deploy/resources.rs | 7 ++- src/service/limit.rs | 91 +++++++++++++++++++++++++++++++++ src/service/memswap_limit.rs | 87 ------------------------------- 6 files changed, 102 insertions(+), 95 deletions(-) create mode 100644 src/service/limit.rs delete mode 100644 src/service/memswap_limit.rs diff --git a/Cargo.lock b/Cargo.lock index 72d9b71..b67b320 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "compose_spec" -version = "0.1.1-beta.1" +version = "0.2.0-alpha.1" dependencies = [ "compose_spec_macros", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index fd951b4..564c36d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ serde_yaml = "0.9" [package] name = "compose_spec" -version = "0.1.1-beta.1" +version = "0.2.0-alpha.1" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/src/service.rs b/src/service.rs index c0d74e4..6e19899 100644 --- a/src/service.rs +++ b/src/service.rs @@ -14,7 +14,7 @@ mod expose; pub mod healthcheck; mod hostname; pub mod image; -mod memswap_limit; +mod limit; pub mod network_config; pub mod platform; pub mod ports; @@ -62,7 +62,7 @@ pub use self::{ healthcheck::Healthcheck, hostname::{Hostname, InvalidHostnameError}, image::Image, - memswap_limit::MemswapLimit, + limit::Limit, network_config::{MacAddress, NetworkConfig}, platform::Platform, ports::Ports, @@ -470,7 +470,7 @@ pub struct Service { /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#memswap_limit) #[serde(default, skip_serializing_if = "Option::is_none")] - pub memswap_limit: Option, + pub memswap_limit: Option>, /// Whether to disable the OOM killer for the container. /// @@ -498,7 +498,7 @@ pub struct Service { /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#pids_limit) #[serde(default, skip_serializing_if = "Option::is_none")] - pub pids_limit: Option, + pub pids_limit: Option>, /// Target platform for the container to run on. /// diff --git a/src/service/deploy/resources.rs b/src/service/deploy/resources.rs index acb2558..2823fd4 100644 --- a/src/service/deploy/resources.rs +++ b/src/service/deploy/resources.rs @@ -17,7 +17,10 @@ use serde::{ use thiserror::Error; use crate::{ - impl_from_str, impl_try_from, serde::forward_visitor, service::ByteValue, Extensions, ListOrMap, + impl_from_str, impl_try_from, + serde::forward_visitor, + service::{ByteValue, Limit}, + Extensions, ListOrMap, }; /// Physical resource constraints for the service container to run on the platform. @@ -102,7 +105,7 @@ pub struct Limits { /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#pids) #[serde(default, skip_serializing_if = "Option::is_none")] - pub pids: Option, + pub pids: Option>, /// Extension values, which are (de)serialized via flattening. /// diff --git a/src/service/limit.rs b/src/service/limit.rs new file mode 100644 index 0000000..81a5c0d --- /dev/null +++ b/src/service/limit.rs @@ -0,0 +1,91 @@ +//! Provides [`Limit`] for the `memswap_limit` and `pids_limit` fields of +//! [`Service`](super::Service). + +use std::{ + fmt::{self, Formatter}, + marker::PhantomData, +}; + +use serde::{ + de::{self, IntoDeserializer, Unexpected}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +use super::ByteValue; + +/// A limit on a [`Service`](super::Service) container resource. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Limit { + /// Amount of the resource the container may use. + Value(T), + + /// Allow the container to use an unlimited amount of the resource. + /// + /// (De)serializes from/to `-1`. + #[default] + Unlimited, +} + +impl From for Limit { + fn from(value: u32) -> Self { + Self::Value(value) + } +} + +impl From for Limit { + fn from(value: ByteValue) -> Self { + Self::Value(value) + } +} + +impl Serialize for Limit { + fn serialize(&self, serializer: S) -> Result { + match self { + Self::Value(value) => value.serialize(serializer), + Self::Unlimited => serializer.serialize_i8(-1), + } + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for Limit { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(Visitor { value: PhantomData }) + } +} + +/// [`de::Visitor`] for deserializing [`Limit`]. +struct Visitor { + /// The type the [`Limit`] contains. + value: PhantomData, +} + +impl<'de, T: Deserialize<'de>> de::Visitor<'de> for Visitor { + type Value = Limit; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a value or -1") + } + + fn visit_i64(self, v: i64) -> Result { + match v { + ..=-2 => Err(E::invalid_value( + Unexpected::Signed(v), + &"-1 or positive integer", + )), + -1 => Ok(Limit::Unlimited), + 0.. => self.visit_u64(v.unsigned_abs()), + } + } + + fn visit_u64(self, v: u64) -> Result { + T::deserialize(v.into_deserializer()).map(Limit::Value) + } + + fn visit_str(self, v: &str) -> Result { + T::deserialize(v.into_deserializer()).map(Limit::Value) + } + + fn visit_string(self, v: String) -> Result { + T::deserialize(v.into_deserializer()).map(Limit::Value) + } +} diff --git a/src/service/memswap_limit.rs b/src/service/memswap_limit.rs deleted file mode 100644 index 8dc4519..0000000 --- a/src/service/memswap_limit.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Provides [`MemswapLimit`] for the `memswap_limit` field of [`Service`](super::Service). - -use std::fmt::{self, Formatter}; - -use serde::{ - de::{self, IntoDeserializer, Unexpected}, - Deserialize, Deserializer, Serialize, Serializer, -}; - -use crate::serde::forward_visitor; - -use super::ByteValue; - -/// The amount of memory a [`Service`](super::Service) container is allowed to swap to disk. -/// -/// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#memswap_limit) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MemswapLimit { - /// Amount of swap memory a container may use in bytes. - Bytes(ByteValue), - - /// Allow the container to use an unlimited amount of swap memory. - /// - /// (De)serializes from/to `-1`. - Unlimited, -} - -impl Default for MemswapLimit { - fn default() -> Self { - Self::Bytes(ByteValue::default()) - } -} - -impl Serialize for MemswapLimit { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Bytes(bytes) => bytes.serialize(serializer), - Self::Unlimited => serializer.serialize_i8(-1), - } - } -} - -impl<'de> Deserialize<'de> for MemswapLimit { - fn deserialize>(deserializer: D) -> Result { - deserializer.deserialize_any(Visitor) - } -} - -/// [`de::Visitor`] for deserializing [`MemswapLimit`]. -struct Visitor; - -impl<'de> de::Visitor<'de> for Visitor { - type Value = MemswapLimit; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("a byte value (string or integer) or -1") - } - - forward_visitor! { - visit_i8, - visit_i16: i16, - visit_i32: i32, - visit_i64: i64, - visit_i128: i128, - } - - fn visit_i8(self, v: i8) -> Result { - match v { - ..=-2 => Err(E::invalid_value( - Unexpected::Signed(v.into()), - &"-1 or positive integer", - )), - -1 => Ok(MemswapLimit::Unlimited), - 0.. => Ok(MemswapLimit::Bytes(ByteValue::Bytes( - v.unsigned_abs().into(), - ))), - } - } - - fn visit_u64(self, v: u64) -> Result { - ByteValue::deserialize(v.into_deserializer()).map(MemswapLimit::Bytes) - } - - fn visit_str(self, v: &str) -> Result { - ByteValue::deserialize(v.into_deserializer()).map(MemswapLimit::Bytes) - } -} From 5650cd69d9105e7a2f9c130ee0e92757eb207023 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sat, 13 Apr 2024 17:52:31 -0500 Subject: [PATCH 02/16] feat(service): add `into_short()` to `volumes::mount::{Volume, Bind}` --- src/service/volumes/mount.rs | 125 +++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 49 deletions(-) diff --git a/src/service/volumes/mount.rs b/src/service/volumes/mount.rs index fe6dd0f..55f0e5a 100644 --- a/src/service/volumes/mount.rs +++ b/src/service/volumes/mount.rs @@ -188,34 +188,8 @@ impl Mount { /// Returns ownership if this long syntax cannot be represented as the short syntax. pub fn into_short(self) -> Result { match self { - Self::Volume(Volume { - source, - volume: None, - common, - }) if common.is_short_compatible() => Ok(ShortVolume { - container_path: common.target, - options: source.map(|source| ShortOptions { - source: source.into(), - read_only: common.read_only, - selinux: None, - }), - }), - Self::Bind(Bind { - source, - bind, - common, - }) if bind.as_ref().map_or(true, BindOptions::is_short_compatible) - && common.is_short_compatible() => - { - Ok(ShortVolume { - container_path: common.target, - options: Some(ShortOptions { - source: source.into(), - read_only: common.read_only, - selinux: bind.and_then(|bind| bind.selinux), - }), - }) - } + Self::Volume(volume) => volume.into_short().map_err(Self::Volume), + Self::Bind(bind) => bind.into_short().map_err(Self::Bind), _ => Err(self), } } @@ -289,6 +263,37 @@ impl Volume { common, } } + + /// Convert into the [`ShortVolume`] syntax if possible. + /// + /// # Errors + /// + /// Returns ownership if this long syntax cannot be represented as the short syntax. + pub fn into_short(self) -> Result { + match self { + Self { + source, + volume, + common: + Common { + target: container_path, + read_only, + consistency: None, + extensions, + }, + } if volume.as_ref().map_or(true, VolumeOptions::is_empty) && extensions.is_empty() => { + Ok(ShortVolume { + container_path, + options: source.map(|source| ShortOptions { + source: source.into(), + read_only, + selinux: None, + }), + }) + } + _ => Err(self), + } + } } impl From for Volume { @@ -388,6 +393,49 @@ impl Bind { common, } } + + /// Convert into the [`ShortVolume`] syntax if possible. + /// + /// # Errors + /// + /// Returns ownership if this long syntax cannot be represented as the short syntax. + pub fn into_short(self) -> Result { + match self { + Self { + source, + bind, + common: + Common { + target: container_path, + read_only, + consistency: None, + extensions, + }, + } if bind.as_ref().map_or( + true, + |BindOptions { + propagation, + create_host_path, + selinux: _, + extensions, + }| { + propagation.is_none() && *create_host_path && extensions.is_empty() + }, + ) && extensions.is_empty() => + { + Ok(ShortVolume { + container_path, + options: Some(ShortOptions { + source: source.into(), + read_only, + selinux: bind.and_then(|options| options.selinux), + }), + }) + } + + _ => Err(self), + } + } } impl From<(HostPath, Common)> for Bind { @@ -453,20 +501,6 @@ impl From for BindOptions { } impl BindOptions { - /// Returns `true` if these bind [`Mount`] options are compatible with the [`ShortVolume`] - /// syntax. - #[must_use] - fn is_short_compatible(&self) -> bool { - let Self { - propagation, - create_host_path, - selinux: _, - extensions, - } = self; - - propagation.is_none() && *create_host_path && extensions.is_empty() - } - /// Returns `true` if all fields are [`None`], `false`, or empty. #[must_use] pub fn is_empty(&self) -> bool { @@ -746,13 +780,6 @@ impl Common { extensions: Extensions::default(), } } - - /// Returns `true` if these common [`Mount`] options are compatible with the [`ShortVolume`] - /// syntax. - #[must_use] - fn is_short_compatible(&self) -> bool { - self.consistency.is_none() && self.extensions.is_empty() - } } impl PartialEq for Common { From da0f325848e906775c9149dc4190537432524d88 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 14 Apr 2024 03:22:07 -0500 Subject: [PATCH 03/16] feat(service): add `blkio_config::Weight` `Display` impl --- src/service/blkio_config.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/service/blkio_config.rs b/src/service/blkio_config.rs index e5ecacc..eb665b3 100644 --- a/src/service/blkio_config.rs +++ b/src/service/blkio_config.rs @@ -2,6 +2,7 @@ use std::{ convert::Infallible, + fmt::{self, Display, Formatter}, num::{NonZeroU16, TryFromIntError}, path::PathBuf, }; @@ -177,6 +178,12 @@ impl Default for Weight { } } +impl Display for Weight { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + impl TryFrom for Weight { type Error = WeightRangeError; From a92a76c5280d4e4bfd89d67ec52e3f08e92cbba0 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 14 Apr 2024 04:06:55 -0500 Subject: [PATCH 04/16] feat(service): add `impl From for String` --- src/service/user_or_group.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/service/user_or_group.rs b/src/service/user_or_group.rs index 96f0172..fc2d3c0 100644 --- a/src/service/user_or_group.rs +++ b/src/service/user_or_group.rs @@ -96,6 +96,15 @@ impl Display for UserOrGroup { } } +impl From for String { + fn from(value: UserOrGroup) -> Self { + match value { + UserOrGroup::Id(id) => id.to_string(), + UserOrGroup::Name(name) => name.into(), + } + } +} + impl<'de> Deserialize<'de> for UserOrGroup { fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_any(Visitor) From 1aa212daa743c0deeedb12954e7e56d125643dfc Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 14 Apr 2024 23:49:03 -0500 Subject: [PATCH 05/16] feat: impl `PartialEq` and `PartialEq<&str>` for key types --- src/common/keys.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/keys.rs b/src/common/keys.rs index e4fd12f..ffffb10 100644 --- a/src/common/keys.rs +++ b/src/common/keys.rs @@ -216,6 +216,18 @@ macro_rules! key_impls { } } + impl ::std::cmp::PartialEq for $Ty { + fn eq(&self, other: &str) -> bool { + self.as_str().eq(other) + } + } + + impl ::std::cmp::PartialEq<&str> for $Ty { + fn eq(&self, other: &&str) -> bool { + self.as_str().eq(*other) + } + } + impl ::std::fmt::Display for $Ty { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { f.write_str(self.as_str()) From 5e73845c514ebc555d832dfcde6143053b447af2 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Fri, 19 Apr 2024 04:16:37 -0500 Subject: [PATCH 06/16] fix(service)!: container paths must be absolute `Service.tmpfs` is now an `Option>`. `Service.working_dir` is now an `Option`. `service::blkio_config::BpsLimit.path` is now an `AbsolutePath`. `service::blkio_config::IopsLimit.path` is now an `AbsolutePath`. `service::blkio_config::WeightDevice.path` is now an `AbsolutePath`. `service::develop::WatchRule.target` is now an `Option`. `service::Device` no longer implements `Default` (this was a mistake). `service::Device.container_path` is now an `AbsolutePath`. Add `service::device::ParseDeviceError::ContainerPathAbsolute`. Also fixed `full_round_trip` and other tests to account for the changes. --- src/service.rs | 6 +++--- src/service/blkio_config.rs | 9 ++++----- src/service/develop.rs | 4 +++- src/service/device.rs | 27 +++++++++++++++++++-------- src/service/volumes.rs | 34 +++++++++++++++++++++------------- src/test-full.yaml | 22 +++++++++++----------- 6 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/service.rs b/src/service.rs index 6e19899..81d551d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -68,7 +68,7 @@ pub use self::{ ports::Ports, ulimit::{InvalidResourceError, Resource, Ulimit, Ulimits}, user_or_group::UserOrGroup, - volumes::Volumes, + volumes::{AbsolutePath, Volumes}, }; /// A service is an abstract definition of a computing resource within an application which can be @@ -629,7 +629,7 @@ pub struct Service { /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#tmpfs) #[serde(default, skip_serializing_if = "Option::is_none")] - pub tmpfs: Option>, + pub tmpfs: Option>, /// Whether to run the container with a TTY. /// @@ -679,7 +679,7 @@ pub struct Service { /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#working_dir) #[serde(default, skip_serializing_if = "Option::is_none")] - pub working_dir: Option, + pub working_dir: Option, /// Extension values, which are (de)serialized via flattening. /// diff --git a/src/service/blkio_config.rs b/src/service/blkio_config.rs index eb665b3..87b1f35 100644 --- a/src/service/blkio_config.rs +++ b/src/service/blkio_config.rs @@ -4,13 +4,12 @@ use std::{ convert::Infallible, fmt::{self, Display, Formatter}, num::{NonZeroU16, TryFromIntError}, - path::PathBuf, }; use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::ByteValue; +use super::{AbsolutePath, ByteValue}; /// Configuration options to set block IO limits for a [`Service`](super::Service). /// @@ -82,7 +81,7 @@ impl BlkioConfig { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct BpsLimit { /// Symbolic path to the affected device. - pub path: PathBuf, + pub path: AbsolutePath, /// Bytes per second rate limit. pub rate: ByteValue, @@ -94,7 +93,7 @@ pub struct BpsLimit { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct IopsLimit { /// Symbolic path to the affected device. - pub path: PathBuf, + pub path: AbsolutePath, /// Operations per second rate limit. pub rate: u64, @@ -106,7 +105,7 @@ pub struct IopsLimit { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct WeightDevice { /// Symbolic path to the affected device. - pub path: PathBuf, + pub path: AbsolutePath, /// Proportion of bandwidth allocated to the device. pub weight: Weight, diff --git a/src/service/develop.rs b/src/service/develop.rs index b086254..2c20992 100644 --- a/src/service/develop.rs +++ b/src/service/develop.rs @@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::Extensions; +use super::AbsolutePath; + /// Development constraints and workflows for maintaining a container in sync with source. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/develop.md) @@ -48,7 +50,7 @@ pub struct WatchRule { /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/develop.md#target) #[serde(default, skip_serializing_if = "Option::is_none")] - pub target: Option, + pub target: Option, /// Extension values, which are (de)serialized via flattening. /// diff --git a/src/service/device.rs b/src/service/device.rs index 54c3e5d..0cad3ec 100644 --- a/src/service/device.rs +++ b/src/service/device.rs @@ -11,19 +11,21 @@ use std::{ use compose_spec_macros::{DeserializeFromStr, SerializeDisplay}; use thiserror::Error; +use super::{volumes::AbsolutePathError, AbsolutePath}; + /// Device mapping from the host to the [`Service`](super::Service) container. /// /// (De)serializes from/to a string in the format `{host_path}:{container_path}[:{permissions}]` /// e.g. `/host:/container:rwm`. /// -/// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#device) -#[derive(SerializeDisplay, DeserializeFromStr, Debug, Default, Clone, PartialEq, Eq, Hash)] +/// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#devices) +#[derive(SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, Hash)] pub struct Device { /// Path on the host of the device. pub host_path: PathBuf, /// Path inside the container to bind mount the device to. - pub container_path: PathBuf, + pub container_path: AbsolutePath, /// Device cgroup permissions. pub permissions: Permissions, @@ -40,7 +42,7 @@ impl FromStr for Device { let container_path = split .next() .ok_or(ParseDeviceError::ContainerPathMissing)? - .into(); + .parse()?; let permissions = split.next().unwrap_or_default().parse()?; Ok(Self { @@ -70,6 +72,10 @@ pub enum ParseDeviceError { #[error("device must have a container path")] ContainerPathMissing, + /// Device container path was not absolute. + #[error("device container path must be absolute")] + ContainerPathAbsolute(#[from] AbsolutePathError), + /// Error parsing [`Permissions`]. #[error("error parsing device permissions")] Permissions(#[from] ParsePermissionsError), @@ -83,7 +89,12 @@ impl Display for Device { permissions, } = self; - write!(f, "{}:{}", host_path.display(), container_path.display())?; + write!( + f, + "{}:{}", + host_path.display(), + container_path.as_path().display(), + )?; if permissions.any() { write!(f, ":{permissions}")?; @@ -430,7 +441,7 @@ mod tests { fn from_str() { let device = Device { host_path: "/host".into(), - container_path: "/container".into(), + container_path: "/container".parse().unwrap(), permissions: Permissions { read: true, write: true, @@ -444,7 +455,7 @@ mod tests { fn display() { let device = Device { host_path: "/host".into(), - container_path: "/container".into(), + container_path: "/container".parse().unwrap(), permissions: Permissions { read: true, write: true, @@ -474,7 +485,7 @@ mod tests { prop_compose! { fn device()( host_path in path_no_colon(), - container_path in path_no_colon(), + container_path: AbsolutePath, permissions in permissions() ) -> Device { Device { host_path, container_path, permissions } diff --git a/src/service/volumes.rs b/src/service/volumes.rs index 7dfc5fe..4205489 100644 --- a/src/service/volumes.rs +++ b/src/service/volumes.rs @@ -710,15 +710,33 @@ impl<'de> Deserialize<'de> for SELinux { #[cfg(test)] mod tests { use proptest::{ - arbitrary::any, + arbitrary::{any, Arbitrary}, option, prop_assert_eq, prop_compose, prop_oneof, proptest, - strategy::{Just, Strategy}, + strategy::{BoxedStrategy, Just, Strategy}, }; use crate::service::tests::path_no_colon; use super::*; + impl Arbitrary for AbsolutePath { + type Parameters = (); + + type Strategy = BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + path_no_colon() + .prop_map(|path| { + if path.is_absolute() { + Self(path) + } else { + Self(Path::new("/").join(path)) + } + }) + .boxed() + } + } + mod short_volume { use super::*; @@ -737,7 +755,7 @@ mod tests { prop_compose! { fn short_volume()( - container_path in absolute_path(), + container_path: AbsolutePath, options in option::of(short_options()), ) -> ShortVolume { ShortVolume { @@ -775,16 +793,6 @@ mod tests { }) } - fn absolute_path() -> impl Strategy { - path_no_colon().prop_map(|path| { - if path.is_absolute() { - AbsolutePath(path) - } else { - AbsolutePath(Path::new("/").join(path)) - } - }) - } - fn selinux() -> impl Strategy { prop_oneof![Just(SELinux::Shared), Just(SELinux::Private)] } diff --git a/src/test-full.yaml b/src/test-full.yaml index cfc00b1..d2c9c07 100644 --- a/src/test-full.yaml +++ b/src/test-full.yaml @@ -93,20 +93,20 @@ services: blkio_config: blkio_config: device_read_bps: - - path: path + - path: /path rate: 1kb device_read_iops: - - path: path + - path: /path rate: 100 device_write_bps: - - path: path + - path: /path rate: 1mb device_write_iops: - - path: path + - path: /path rate: 100 weight: 10 weight_device: - - path: path + - path: /path weight: 1000 cgroup-host: @@ -271,7 +271,7 @@ services: ignore: - ignore path: path - target: target + target: /target x-test: test dns-string: @@ -426,11 +426,11 @@ services: key: value tmpfs-string: - tmpfs: tmpfs + tmpfs: /tmpfs tmpfs-list: tmpfs: - - tmpfs + - /tmpfs other: attach: false @@ -463,8 +463,8 @@ services: - b 2:2 m - a 3:3 rwm devices: - - host:container - - host:container:rwm + - host:/container + - host:/container:rwm dns_opt: - opt domainname: domainname @@ -613,7 +613,7 @@ services: - service - container:container - container:container:ro - working_dir: working_dir + working_dir: /working_dir x-test: test networks: From 45d42145e3e76aa59e2c2bf1650689e5a64d0452 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Fri, 19 Apr 2024 04:23:36 -0500 Subject: [PATCH 07/16] docs(service): fix `deploy::Resources::is_empty()` example --- src/service/deploy/resources.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/service/deploy/resources.rs b/src/service/deploy/resources.rs index 2823fd4..8af32bd 100644 --- a/src/service/deploy/resources.rs +++ b/src/service/deploy/resources.rs @@ -54,7 +54,7 @@ impl Resources { /// # Examples /// /// ``` - /// use compose_spec::service::deploy::{Resources, resources::Limits}; + /// use compose_spec::service::{deploy::{Resources, resources::Limits}, Limit}; /// /// let mut resources = Resources::default(); /// assert!(resources.is_empty()); @@ -63,7 +63,7 @@ impl Resources { /// assert!(resources.is_empty()); /// /// resources.limits = Some(Limits { - /// pids: Some(100), + /// pids: Some(Limit::Value(100)), /// ..Limits::default() /// }); /// assert!(!resources.is_empty()); From 0c7a8f564b688aff1b2796462802e749f7b25950 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Fri, 19 Apr 2024 04:32:23 -0500 Subject: [PATCH 08/16] feat(service): add `volumes::mount::Tmpfs::from_target()` Also added `impl From for Tmpfs` using the new function. --- src/service/volumes/mount.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/service/volumes/mount.rs b/src/service/volumes/mount.rs index 55f0e5a..d70b11a 100644 --- a/src/service/volumes/mount.rs +++ b/src/service/volumes/mount.rs @@ -628,6 +628,12 @@ impl Tmpfs { common, } } + + /// Create a [`Tmpfs`] [`Mount`] from the `target` path within the container. + #[must_use] + pub fn from_target(target: AbsolutePath) -> Self { + Self::new(Common::new(target)) + } } impl From for Tmpfs { @@ -636,6 +642,12 @@ impl From for Tmpfs { } } +impl From for Tmpfs { + fn from(target: AbsolutePath) -> Self { + Self::from_target(target) + } +} + /// Additional [`Tmpfs`] [`Mount`] options. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#long-syntax-5) From ce35bbc02b46c5afcc469129249cb4f0d18778ff Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Mon, 22 Apr 2024 22:34:22 -0500 Subject: [PATCH 09/16] fix(service): `ShortVolume::into_long()` set `create_host_path: true` When converting a `service::volumes::ShortVolume` into a `service::volumes::mount::Bind`, the `create_host_path` field in `service::volumes::mount::BindOptions` should be set to `true`. --- src/service/volumes.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/service/volumes.rs b/src/service/volumes.rs index 4205489..9eb4995 100644 --- a/src/service/volumes.rs +++ b/src/service/volumes.rs @@ -24,7 +24,7 @@ use thiserror::Error; use crate::{impl_try_from, Identifier, InvalidIdentifierError, ShortOrLong}; pub use self::mount::Mount; -use self::mount::{Bind, Common, Volume}; +use self::mount::{Bind, BindOptions, Common, Volume}; /// [`Volume`](crate::Volume)s to mount within a [`Service`](super::Service) container. /// @@ -101,7 +101,11 @@ impl ShortVolume { match source { Source::HostPath(source) => Mount::Bind(Bind { source, - bind: selinux.map(Into::into), + bind: Some(BindOptions { + create_host_path: true, + selinux, + ..BindOptions::default() + }), common, }), Source::Volume(source) => Mount::Volume(Volume { From 2f159f4dfd34a921ae09b693ade02bcdd2bad201 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 00:10:55 -0500 Subject: [PATCH 10/16] docs: fix `ListOrMap::into_list()` docs Last line was a normal comment instead of a doc comment. --- src/common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common.rs b/src/common.rs index 4694bb9..c283837 100644 --- a/src/common.rs +++ b/src/common.rs @@ -188,7 +188,7 @@ impl ListOrMap { /// like so `{key}={value}`. If the value is [`None`], the key is put into the list as is. /// /// All places [`ListOrMap`] is used within [`Compose`](super::Compose) support the use of the - // `{key}={value}` syntax. + /// `{key}={value}` syntax. #[must_use] pub fn into_list(self) -> IndexSet { match self { From dc1268f55d7b5df4b9996f650c26ea6eff54745d Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 01:15:11 -0500 Subject: [PATCH 11/16] feat(service)!: add `entitlements` field to `Build` New public field `entitlements` added to `service::Build` which has all public fields and is exhaustive, making this a breaking change. Closes: #15 --- src/service/build.rs | 10 ++++++++++ src/test-full.yaml | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/service/build.rs b/src/service/build.rs index 390257c..d42e1bb 100644 --- a/src/service/build.rs +++ b/src/service/build.rs @@ -84,6 +84,14 @@ pub struct Build { )] pub additional_contexts: IndexMap, + /// Extra privileged entitlements to be allowed during the build. + /// + /// Available values are platform specific. + /// + /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#entitlements) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub entitlements: Vec, + /// Add hostname mappings at build-time. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#extra_hosts) @@ -200,6 +208,7 @@ impl Build { cache_from, cache_to, additional_contexts, + entitlements, extra_hosts, isolation, privileged, @@ -223,6 +232,7 @@ impl Build { && cache_from.is_empty() && cache_to.is_empty() && additional_contexts.is_empty() + && entitlements.is_empty() && extra_hosts.is_empty() && isolation.is_none() && !privileged diff --git a/src/test-full.yaml b/src/test-full.yaml index d2c9c07..d5fabd8 100644 --- a/src/test-full.yaml +++ b/src/test-full.yaml @@ -52,6 +52,9 @@ services: additional_contexts: additional_context: https://example.com/ additional_context2: /path + entitlements: + - network.host + - security.insecure extra_hosts: host4: 127.0.0.1 host6: ::1 From 7724bdcab2287503ce2494eff0e42ae172b57a8f Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 08:18:26 -0500 Subject: [PATCH 12/16] docs(changelog): add git-cliff configuration --- Cargo.toml | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 564c36d..30e1a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,136 @@ compose_spec_macros = { version = "=0.1.0", path = "compose_spec_macros" } serde = "1.0.147" serde_yaml = "0.9" +# git-cliff ~ configuration +# Run with `GITHUB_TOKEN=$(gh auth token) git cliff --bump -up CHANGELOG.md` +# https://git-cliff.org/docs/configuration +[workspace.metadata.git-cliff.bump] +features_always_bump_minor = false +breaking_always_bump_major = false + +[workspace.metadata.git-cliff.remote.github] +owner = "k9withabone" +repo = "compose_spec_rs" + +[workspace.metadata.git-cliff.changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{% if version -%} + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else -%} + ## [Unreleased] +{% endif -%} + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {%- for commit in commits %} + - {% if commit.breaking %}**BREAKING** {% endif -%} + {% if commit.scope %}*({{ commit.scope }})* {% endif -%} + {{ commit.message | upper_first | trim }}\ + {% if commit.github.username and commit.github.username != remote.github.owner %} by \ + [@{{ commit.github.username }}](https://github.com/{{ commit.github.username }})\ + {%- endif -%} + {% if commit.github.pr_number %} in \ + [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }})\ + {%- endif -%} + {%- set fixes = commit.footers | filter(attribute="token", value="Fixes") -%} + {%- set closes = commit.footers | filter(attribute="token", value="Closes") -%} + {% for footer in fixes | concat(with=closes) -%} + {%- set issue_number = footer.value | trim_start_matches(pat="#") %} \ + ([{{ footer.value }}]({{ self::remote_url() }}/issues/{{ issue_number }}))\ + {%- endfor -%} + {% if commit.body %} + {%- for section in commit.body | trim | split(pat="\n\n") %} + {% raw %} {% endraw %}- {{ section | replace(from="\n", to=" ") }} + {%- endfor -%} + {%- endif -%} + {% endfor %} +{% endfor %} + +{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} + ### New Contributors +{%- endif -%} + +{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + - @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} in \ + [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} +{%- endfor %}\n +""" +# template for the changelog footer +footer = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{% for release in releases -%} + {% if release.version -%} + {% if release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: \ + {{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }} + {% endif -%} + {% else -%} + [Unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD + {% endif -%} +{%- endfor -%} +{#- compare against the initial commit for the first version -#} +[0.1.0]: {{ self::remote_url() }}/compare/51a31d82c34c13cf8881bf8a9cbda74a6b6aa9b6...v0.1.0 +""" +# remove the leading and trailing whitespace from the templates +trim = true + +[workspace.metadata.git-cliff.git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "New Features" }, + { body = ".*security", group = "Security" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^perf", group = "Performance" }, + { message = "^doc", group = "Documentation" }, + { message = "^test", group = "Tests" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Style" }, + { message = "^chore", group = "Miscellaneous" }, + { message = "^ci", default_scope = "ci", group = "Miscellaneous" }, + { message = "^release", skip = true }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = true +# filter out the commits that are not matched by commit parsers +filter_commits = true +# regex for matching git tags +tag_pattern = "v[0-9].*" +# regex for skipping tags +skip_tags = "v0.1.0-beta.1" +# regex for ignoring tags +ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" + [package] name = "compose_spec" version = "0.2.0-alpha.1" From 87750359f6b1ac8a64442d4716b6a16c54616f35 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 08:18:43 -0500 Subject: [PATCH 13/16] chore(deps): update dependencies --- Cargo.lock | 75 +++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b67b320..353778f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -224,9 +224,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -259,9 +259,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -313,9 +313,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", @@ -344,18 +344,18 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -400,18 +400,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -504,13 +504,14 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -519,42 +520,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" From 821d85220f3c460b068641756b14685234e2c790 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 08:32:14 -0500 Subject: [PATCH 14/16] ci: bump `typos` to v1.20.9 --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0a050d3..8924279 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -94,7 +94,7 @@ jobs: uses: actions/checkout@v4 - name: Typos - uses: crate-ci/typos@v1.20.4 + uses: crate-ci/typos@v1.20.9 msrv: runs-on: ubuntu-latest From ae3f57120b29ad24de33a1d937b9726af97db2b2 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 09:11:27 -0500 Subject: [PATCH 15/16] test(service): fix `ports::ShortRanges` generation --- src/service/ports.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/service/ports.rs b/src/service/ports.rs index 632cfbe..8a3aec4 100644 --- a/src/service/ports.rs +++ b/src/service/ports.rs @@ -1031,16 +1031,21 @@ pub(super) mod tests { } } - prop_compose! { - fn short_ranges()(range in range())( - range in Just(range), - offset in ..u16::MAX - range.end.unwrap_or(range.start) - ) -> ShortRanges { - ShortRanges { + fn short_ranges() -> impl Strategy { + range() + .prop_flat_map(|range| { + let range_end = range.end.unwrap_or(range.start); + let offset = if range_end == u16::MAX { + Just(0).boxed() + } else { + (..u16::MAX - range_end).boxed() + }; + (Just(range), offset) + }) + .prop_map(|(range, offset)| ShortRanges { host: (offset != 0).then(|| range + offset), container: range, - } - } + }) } pub(in super::super) fn range() -> impl Strategy { From 21528cd9b357c58fc3271cc58869f8421f0b44f9 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Tue, 23 Apr 2024 09:11:42 -0500 Subject: [PATCH 16/16] release: compose_spec v0.2.0 --- CHANGELOG.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa446d6..67f9134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] - 2024-04-24 + +### New Features +- *(volume)* Add `Volume::is_empty()`. +- *(network)* Add `Network::is_empty()`. + - Add `is_empty()` methods to `network::{Network, Ipam, IpamConfig}`. +- *(service)* Add `service::Logging::is_empty()`. +- *(service)* Add `service::healthcheck::Command::is_empty()`. +- *(service)* Add `service::Build::is_empty()`. +- *(service)* Add `service::BlkioConfig::is_empty()`. +- *(service)* Add `is_empty()` methods to `service::volumes::mount` types. + - Add `is_empty()` methods to `service::volumes::mount::{VolumeOptions, BindOptions, TmpfsOptions}`. +- *(service)* Implement `Default` for `service::deploy::resources::Device`. +- *(service)* Add `service::deploy::Resources::is_empty()`. + - Add `is_empty()` methods to `service::deploy::resources::{Resources, Limits, Reservations, Device, GenericResource, DiscreteResourceSpec}`. +- *(service)* Add `service::Deploy::is_empty()`. + - Add `is_empty()` methods to `service::deploy::{Deploy, Placement, Preference, RestartPolicy, UpdateRollbackConfig}`. +- *(service)* Implement `Default` for `service::Healthcheck`. +- *(service)* Add `into_short()` methods to `service::volumes::mount::{Volume, Bind}`. +- *(service)* Implement `Display` for `service::blkio_config::Weight`. +- *(service)* Implement `From` for `String`. +- Implement `PartialEq` and `PartialEq<&str>` for key types. + - `compose_spec::{Identifier, MapKey, ExtensionKey, service::{build::CacheOption, user_or_group::Name, Hostname, Resource}}` +- *(service)* Add `service::volumes::mount::Tmpfs::from_target()`. + - Also implemented `From` for `service::volumes::mount::Tmpfs` using the new function. +- **BREAKING** *(service)* Add `entitlements` field to `service::Build` ([#15](https://github.com/k9withabone/compose_spec_rs/issues/15)). + +### Bug Fixes +- **BREAKING** *(service)* Allow for unlimited `pids_limit`. + - Generalize `service::MemswapLimit` into `service::Limit`. + - `Service.memswap_limit` is now an `Option>`. + - `Service.pids_limit` is now an `Option>`. + - `service::deploy::resources::Limits.pids` is now an `Option>`. +- **BREAKING** `service::Device` no longer implements `Default` (this was a mistake). +- **BREAKING** *(service)* Container paths must be absolute. + - `Service.tmpfs` is now an `Option>`. + - `Service.working_dir` is now an `Option`. + - `service::blkio_config::BpsLimit.path` is now an `AbsolutePath`. + - `service::blkio_config::IopsLimit.path` is now an `AbsolutePath`. + - `service::blkio_config::WeightDevice.path` is now an `AbsolutePath`. + - `service::develop::WatchRule.target` is now an `Option`. + - `service::Device.container_path` is now an `AbsolutePath`. + - Add `service::device::ParseDeviceError::ContainerPathAbsolute` variant. +- *(service)* `ShortVolume::into_long()` set `create_host_path: true`. + - When converting a `service::volumes::ShortVolume` into a `service::volumes::mount::Bind`, the `create_host_path` field in `service::volumes::mount::BindOptions` should be set to `true`. + +### Documentation +- Fix `ListOrMap::into_list()` docs. + - Last line was a normal comment instead of a doc comment. +- *(changelog)* Add [git-cliff](https://github.com/orhun/git-cliff) configuration + +### Tests +- *(service)* Fix `service::ports::ShortRanges` generation. + - The `offset` range could become `0..0` which caused `proptest` to panic. + +### Miscellaneous +- *(ci)* Add semver-checks job. + - Use [cargo-semver-checks](https://github.com/obi1kenobi/cargo-semver-checks) to make sure the package version is increased correctly when making changes. +- *(deps)* Update dependencies. +- *(ci)* Bump `typos` to v1.20.9. + ## [0.1.0] - 2024-04-05 The initial release of `compose_spec`! @@ -16,3 +77,6 @@ The initial release of `compose_spec`! - Completely documented. - Conversion between short and long syntax forms of values. - Conversion between `std::time::Duration` and the duration string format from the compose-spec. + +[0.2.0]: https://github.com/k9withabone/compose_spec_rs/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/k9withabone/compose_spec_rs/compare/51a31d82c34c13cf8881bf8a9cbda74a6b6aa9b6...v0.1.0 diff --git a/Cargo.lock b/Cargo.lock index 353778f..34d2626 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "compose_spec" -version = "0.2.0-alpha.1" +version = "0.2.0" dependencies = [ "compose_spec_macros", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index 30e1a5d..5c620ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -226,7 +226,7 @@ sort_commits = "oldest" [package] name = "compose_spec" -version = "0.2.0-alpha.1" +version = "0.2.0" authors.workspace = true edition.workspace = true license.workspace = true