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}