wick_config/config/common/
resources.rs1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use asset_container::{Asset, AssetFlags};
5use url::Url;
6use wick_asset_reference::AssetReference;
7use wick_packet::RuntimeConfig;
8
9use super::template_config::Renderable;
10use crate::config::TemplateConfig;
11use crate::error::ManifestError;
12
13crate::impl_from_for!(ResourceDefinition, TcpPort);
14crate::impl_from_for!(ResourceDefinition, UdpPort);
15crate::impl_from_for!(ResourceDefinition, Volume);
16crate::impl_from_for!(ResourceDefinition, Url, UrlResource);
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum ResourceKind {
21 TcpPort,
22 UdpPort,
23 Url,
24 Volume,
25}
26
27impl std::fmt::Display for ResourceKind {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Self::TcpPort => write!(f, "TcpPort"),
31 Self::UdpPort => write!(f, "UdpPort"),
32 Self::Url => write!(f, "Url"),
33 Self::Volume => write!(f, "Volume"),
34 }
35 }
36}
37
38#[derive(Debug, Clone, derive_asset_container::AssetManager, serde::Serialize, PartialEq, Hash, Eq)]
39#[asset(asset(AssetReference))]
40#[serde(rename_all = "kebab-case")]
42pub enum ResourceDefinition {
43 #[asset(skip)]
45 TcpPort(TcpPort),
46 #[asset(skip)]
48 UdpPort(UdpPort),
49 #[asset(skip)]
51 Url(UrlResource),
52 Volume(Volume),
54}
55
56impl Renderable for ResourceDefinition {
57 fn render_config(
58 &mut self,
59 source: Option<&Path>,
60 root_config: Option<&RuntimeConfig>,
61 env: Option<&HashMap<String, String>>,
62 ) -> Result<(), ManifestError> {
63 match self {
64 ResourceDefinition::TcpPort(v) => v.render_config(source, root_config, env),
65 ResourceDefinition::UdpPort(v) => v.render_config(source, root_config, env),
66 ResourceDefinition::Url(v) => v.render_config(source, root_config, env),
67 ResourceDefinition::Volume(v) => v.render_config(source, root_config, env),
68 }
69 }
70}
71
72impl ResourceDefinition {
73 #[must_use]
74 pub const fn kind(&self) -> ResourceKind {
75 match self {
76 ResourceDefinition::TcpPort(_) => ResourceKind::TcpPort,
77 ResourceDefinition::UdpPort(_) => ResourceKind::UdpPort,
78 ResourceDefinition::Url(_) => ResourceKind::Url,
79 ResourceDefinition::Volume(_) => ResourceKind::Volume,
80 }
81 }
82
83 pub fn try_tcpport(self) -> Result<TcpPort, ManifestError> {
84 self.try_into()
85 }
86
87 pub fn try_udpport(self) -> Result<UdpPort, ManifestError> {
88 self.try_into()
89 }
90
91 pub fn try_url(self) -> Result<UrlResource, ManifestError> {
92 self.try_into()
93 }
94
95 pub fn try_volume(self) -> Result<Volume, ManifestError> {
96 self.try_into()
97 }
98}
99
100impl TryFrom<String> for UrlResource {
101 type Error = crate::Error;
102
103 fn try_from(value: String) -> Result<Self, Self::Error> {
104 url::Url::parse(&value)
105 .map_err(|_| Self::Error::InvalidUrl(value.clone()))
106 .map(Self::new)
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Hash, Eq, derive_builder::Builder, serde::Serialize)]
111#[must_use]
113pub struct Volume {
114 path: TemplateConfig<AssetReference>,
115}
116
117impl Volume {
118 pub fn new(template: String) -> Self {
119 let template = TemplateConfig::new_template(template);
120 Self { path: template }
121 }
122
123 #[cfg(feature = "v1")]
124 pub(crate) fn unrender(&self) -> Result<String, ManifestError> {
125 self.path.unrender()
126 }
127
128 pub fn path(&self) -> Result<PathBuf, ManifestError> {
129 if let Some(path) = &self.path.value {
130 Ok(path.path()?)
131 } else {
132 Err(ManifestError::UnrenderedConfiguration(format!(
133 "{:?}",
134 self.path.template
135 )))
136 }
137 }
138}
139
140impl Renderable for Volume {
141 fn render_config(
142 &mut self,
143 source: Option<&Path>,
144 root_config: Option<&RuntimeConfig>,
145 env: Option<&HashMap<String, String>>,
146 ) -> Result<(), ManifestError> {
147 self.path.set_value(self.path.render(source, root_config, env)?);
148 Ok(())
149 }
150}
151
152impl asset_container::AssetManager for Volume {
153 type Asset = AssetReference;
154
155 fn assets(&self) -> asset_container::Assets<Self::Asset> {
156 asset_container::Assets::new(
157 self
158 .path
159 .value
160 .as_ref()
161 .map_or_else(Vec::new, |v| vec![std::borrow::Cow::Borrowed(v)]),
162 self.get_asset_flags(),
163 )
164 }
165
166 fn set_baseurl(&self, baseurl: &Path) {
167 if let Some(path) = &self.path.value {
168 path.update_baseurl(baseurl);
169 match self.path() {
170 Ok(p) => {
171 if !p.is_dir() {
172 tracing::warn!(%path,"volume path is not a directory");
173 }
174 }
175 Err(e) => {
176 tracing::warn!(%path,error=%e,"volume path could not be resolved");
177 }
178 }
179 }
180 }
181
182 fn get_asset_flags(&self) -> u32 {
183 AssetFlags::Lazy.bits()
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Hash, Eq, property::Property, serde::Serialize)]
188#[must_use]
190#[property(get(public), set(private), mut(disable))]
191pub struct UrlResource {
192 pub(crate) url: TemplateConfig<Url>,
194}
195
196impl UrlResource {
197 pub const fn new(url: Url) -> Self {
199 Self {
200 url: TemplateConfig::new_value(url),
201 }
202 }
203
204 #[must_use]
206 #[allow(clippy::missing_const_for_fn)]
207 pub fn into_inner(self) -> TemplateConfig<Url> {
208 self.url
209 }
210}
211
212impl std::fmt::Display for UrlResource {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 write!(f, "{}", self.url)
215 }
216}
217
218impl Renderable for UrlResource {
219 fn render_config(
220 &mut self,
221 source: Option<&Path>,
222 root_config: Option<&RuntimeConfig>,
223 env: Option<&HashMap<String, String>>,
224 ) -> Result<(), ManifestError> {
225 self.url.set_value(self.url.render(source, root_config, env)?);
226 Ok(())
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Hash, Eq, property::Property, serde::Serialize)]
231#[property(get(public), set(private), mut(disable))]
233pub struct TcpPort {
234 pub(crate) port: TemplateConfig<u16>,
236 pub(crate) host: TemplateConfig<String>,
238}
239
240impl TcpPort {
241 pub fn new<T: Into<String>>(host: T, port: u16) -> Self {
243 Self {
244 port: TemplateConfig::new_value(port),
245 host: TemplateConfig::new_value(host.into()),
246 }
247 }
248
249 #[must_use]
251 pub fn address(&self) -> String {
252 format!("{}:{}", self.host, self.port)
253 }
254}
255
256impl Renderable for TcpPort {
257 fn render_config(
258 &mut self,
259 source: Option<&Path>,
260 root_config: Option<&RuntimeConfig>,
261 env: Option<&HashMap<String, String>>,
262 ) -> Result<(), ManifestError> {
263 self.port.set_value(self.port.render(source, root_config, env)?);
264 self.host.set_value(self.host.render(source, root_config, env)?);
265 Ok(())
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Hash, Eq, property::Property, serde::Serialize)]
270#[property(get(public), set(private), mut(disable))]
272pub struct UdpPort {
273 pub(crate) port: TemplateConfig<u16>,
275 pub(crate) host: TemplateConfig<String>,
277}
278
279impl UdpPort {
280 pub fn new<T: Into<String>>(host: T, port: u16) -> Self {
282 Self {
283 port: TemplateConfig::new_value(port),
284 host: TemplateConfig::new_value(host.into()),
285 }
286 }
287
288 #[must_use]
290 pub fn address(&self) -> String {
291 format!("{}:{}", self.host, self.port)
292 }
293}
294
295impl Renderable for UdpPort {
296 fn render_config(
297 &mut self,
298 source: Option<&Path>,
299 root_config: Option<&RuntimeConfig>,
300 env: Option<&HashMap<String, String>>,
301 ) -> Result<(), ManifestError> {
302 self.port.set_value(self.port.render(source, root_config, env)?);
303 self.host.set_value(self.host.render(source, root_config, env)?);
304 Ok(())
305 }
306}
307
308#[cfg(test)]
309mod test {
310 use anyhow::Result;
311
312 use super::*;
313
314 #[test]
315 fn try_into_urlresource() -> Result<()> {
316 let urlstr = "postgres://postgres:password!12345@nas.glhf.lan:55432/wick_test".to_owned();
317 let url_resource: UrlResource = urlstr.clone().try_into()?;
318 let url: Url = urlstr.parse()?;
319
320 assert_eq!(url_resource.url.value_unchecked().as_str(), &urlstr);
321 assert_eq!(url_resource.url.value_unchecked(), &url);
322
323 Ok(())
324 }
325}