compose_spec/service/
build.rs

1//! Provides long [`Build`] syntax for the `build` field of [`Service`](super::Service).
2
3mod cache;
4mod context;
5mod dockerfile;
6mod network;
7mod ssh_auth;
8
9use std::{net::IpAddr, ops::Not};
10
11use compose_spec_macros::{AsShort, FromShort};
12use indexmap::{IndexMap, IndexSet};
13use serde::{de, Deserialize, Deserializer, Serialize};
14
15use crate::{Extensions, Identifier, ListOrMap, MapKey, ShortOrLong};
16
17pub use self::{
18    cache::{Cache, CacheOption, CacheType, InvalidCacheOptionError, ParseCacheError},
19    context::Context,
20    dockerfile::Dockerfile,
21    network::Network,
22    ssh_auth::{Id as SshAuthId, IdError as SshAuthIdError, SshAuth},
23};
24
25use super::{extra_hosts, ByteValue, ConfigOrSecret, Hostname, Image, Platform, Ulimits};
26
27/// Long syntax build configuration for creating a container image from source.
28///
29/// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md)
30#[derive(Serialize, Deserialize, AsShort, FromShort, Default, Debug, Clone, PartialEq)]
31pub struct Build {
32    /// Path to a directory containing a Dockerfile/Containerfile, or a URL to a git repository.
33    ///
34    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#context)
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    #[as_short(short)]
37    pub context: Option<Context>,
38
39    /// Set an alternate Dockerfile/Containerfile or define its content inline.
40    /// A relative path is resolved from the build context.
41    ///
42    /// Represents either the `dockerfile` or `dockerfile_inline` fields,
43    /// which conflict with each other.
44    ///
45    /// This is (de)serialized by flattening [`Dockerfile`]. When deserializing, if neither the
46    /// `dockerfile` or `dockerfile_inline` fields are present, this is [`None`].
47    /// If both fields are present, or either is repeated, then an error is returned.
48    ///
49    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#dockerfile)
50    #[serde(flatten, with = "dockerfile::option")]
51    pub dockerfile: Option<Dockerfile>,
52
53    /// Build arguments, i.e. Dockerfile/Containerfile `ARG` values.
54    ///
55    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#args)
56    #[serde(default, skip_serializing_if = "ListOrMap::is_empty")]
57    pub args: ListOrMap,
58
59    /// SSH authentications that the image builder should use during image build.
60    ///
61    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#ssh)
62    #[serde(default, skip_serializing_if = "IndexSet::is_empty")]
63    pub ssh: IndexSet<SshAuth>,
64
65    /// Sources the image builder should use for cache resolution.
66    ///
67    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#cache_from)
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    pub cache_from: Vec<Cache>,
70
71    /// Export locations to be used to share build cache with future builds.
72    ///
73    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#cache_to)
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub cache_to: Vec<Cache>,
76
77    /// Named contexts the image builder should use during image build.
78    ///
79    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#additional_contexts)
80    #[serde(
81        default,
82        skip_serializing_if = "IndexMap::is_empty",
83        deserialize_with = "additional_contexts"
84    )]
85    pub additional_contexts: IndexMap<MapKey, Context>,
86
87    /// Extra privileged entitlements to be allowed during the build.
88    ///
89    /// Available values are platform specific.
90    ///
91    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#entitlements)
92    #[serde(default, skip_serializing_if = "Vec::is_empty")]
93    pub entitlements: Vec<String>,
94
95    /// Add hostname mappings at build-time.
96    ///
97    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#extra_hosts)
98    #[serde(
99        default,
100        skip_serializing_if = "IndexMap::is_empty",
101        deserialize_with = "extra_hosts"
102    )]
103    pub extra_hosts: IndexMap<Hostname, IpAddr>,
104
105    /// Specifies a build’s container isolation technology.
106    ///
107    /// Like [`isolation`](super::Service#structfield.isolation),
108    /// supported values are platform specific.
109    ///
110    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#isolation)
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub isolation: Option<String>,
113
114    /// Configure the service image to build with elevated privileges.
115    ///
116    /// Support and actual impacts are platform specific.
117    ///
118    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#privileged)
119    #[serde(default, skip_serializing_if = "Not::not")]
120    pub privileged: bool,
121
122    /// Add metadata to the resulting image.
123    ///
124    /// It's recommended that you use reverse-DNS notation to prevent your labels from conflicting
125    /// with other software.
126    ///
127    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#args)
128    #[serde(default, skip_serializing_if = "ListOrMap::is_empty")]
129    pub labels: ListOrMap,
130
131    /// Disable image builder cache and enforce a full rebuild from source for all image layers.
132    ///
133    /// Only applies to layers declared in the Dockerfile/Containerfile, referenced images could be
134    /// retrieved from a local image store whenever the tag has been updated on registry.
135    ///
136    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#no_cache)
137    #[serde(default, skip_serializing_if = "Not::not")]
138    pub no_cache: bool,
139
140    /// Require the image builder to pull referenced images, even those already available in the
141    /// local image store.
142    ///
143    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#pull)
144    #[serde(default, skip_serializing_if = "Not::not")]
145    pub pull: bool,
146
147    /// Set the network containers connect to during build for `RUN` instructions.
148    ///
149    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#network)
150    #[serde(default, skip_serializing_if = "Option::is_none")]
151    pub network: Option<Network>,
152
153    /// Set the size of the shared memory allocated for building container images.
154    ///
155    /// Corresponds to the `/dev/shm` partition on Linux.
156    ///
157    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#shm_size)
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub shm_size: Option<ByteValue>,
160
161    /// Set the stage to build as defined inside a multi-stage Dockerfile/Containerfile.
162    ///
163    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#target)
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub target: Option<String>,
166
167    /// Grant access to sensitive data defined by [`secrets`](crate::Compose#structfield.secrets) on
168    /// a per-service build basis.
169    ///
170    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#secrets)
171    #[serde(default, skip_serializing_if = "Vec::is_empty")]
172    pub secrets: Vec<ShortOrLong<Identifier, ConfigOrSecret>>,
173
174    /// List of tag mappings that must be associated to the build image.
175    ///
176    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#tags)
177    #[serde(default, skip_serializing_if = "Vec::is_empty")]
178    pub tags: Vec<Image>,
179
180    /// Override the default ulimits for a container.
181    ///
182    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#ulimits)
183    #[serde(default, skip_serializing_if = "Ulimits::is_empty")]
184    pub ulimits: Ulimits,
185
186    /// List of target platforms.
187    ///
188    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/build.md#platforms)
189    #[serde(default, skip_serializing_if = "Vec::is_empty")]
190    pub platforms: Vec<Platform>,
191
192    /// Extension values, which are (de)serialized via flattening.
193    ///
194    /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/11-extension.md)
195    #[serde(flatten)]
196    pub extensions: Extensions,
197}
198
199impl Build {
200    /// Returns `true` if all fields are [`None`] or empty.
201    #[must_use]
202    pub fn is_empty(&self) -> bool {
203        let Self {
204            context,
205            dockerfile,
206            args,
207            ssh,
208            cache_from,
209            cache_to,
210            additional_contexts,
211            entitlements,
212            extra_hosts,
213            isolation,
214            privileged,
215            labels,
216            no_cache,
217            pull,
218            network,
219            shm_size,
220            target,
221            secrets,
222            tags,
223            ulimits,
224            platforms,
225            extensions,
226        } = self;
227
228        context.is_none()
229            && dockerfile.is_none()
230            && args.is_empty()
231            && ssh.is_empty()
232            && cache_from.is_empty()
233            && cache_to.is_empty()
234            && additional_contexts.is_empty()
235            && entitlements.is_empty()
236            && extra_hosts.is_empty()
237            && isolation.is_none()
238            && !privileged
239            && labels.is_empty()
240            && !no_cache
241            && !pull
242            && network.is_none()
243            && shm_size.is_none()
244            && target.is_none()
245            && secrets.is_empty()
246            && tags.is_empty()
247            && ulimits.is_empty()
248            && platforms.is_empty()
249            && extensions.is_empty()
250    }
251}
252
253/// Deserialize `additional_contexts` field of [`Build`].
254///
255/// Converts from [`ListOrMap`].
256fn additional_contexts<'de, D>(deserializer: D) -> Result<IndexMap<MapKey, Context>, D::Error>
257where
258    D: Deserializer<'de>,
259{
260    Ok(ListOrMap::deserialize(deserializer)?
261        .into_map()
262        .map_err(de::Error::custom)?
263        .into_iter()
264        .map(|(key, value)| {
265            let value = value
266                .map(String::from)
267                .map_or_else(Context::default, Context::from);
268            (key, value)
269        })
270        .collect())
271}