1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use serde::{Deserialize, Serialize};
use crate::compose::{Compose, Validate};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Volumes {
String(String),
Short(ShortVolumeOptions),
Long(LongVolumeOptions),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ShortVolumeOptions {
pub volume: String,
pub container_path: String,
pub access_mode: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LongVolumeOptions {
#[serde(rename = "type")]
pub volume_type: VolumeType,
pub source: String,
pub target: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub read_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bind: Option<Bind>,
#[serde(skip_serializing_if = "Option::is_none")]
pub volume: Option<VolumeOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tmpfs: Option<Tmpfs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub consistency: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum VolumeType {
Volume,
Bind,
Tmpfs,
Npipe,
Cluster,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Bind {
pub propagation: String,
pub create_host_path: String,
pub selinux: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VolumeOptions {
pub nocopy: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Tmpfs {
pub size: String,
pub mode: String,
}
impl Validate for Volumes {
/// Note: Currently, this implementation of volumes always assumes references to volumes will
/// be named volumes from the top level volumes section. However, the official docs say that
/// the services:volumes should also allow for path references. Since this library does not
/// care about the host system, maybe there should be additional options to check for host
/// references.
fn validate(&self, _ctx: &Compose, _errors: &mut crate::errors::ValidationErrors) {
// match self {
// Volumes::String(s) => {
// ctx.volumes.as_ref().map(|available_volumes| {
// if !available_volumes.contains_key(s) {
// errors.add_error(crate::errors::ValidationError::InvalidValue(format!(
// "Service volumes reference unknown volume: {s}"
// )));
// }
// });
// }
// Volumes::Short(s) => {
// ctx.volumes.as_ref().map(|available_volumes| {
// if !available_volumes.contains_key(s.volume.as_str()) {
// errors.add_error(crate::errors::ValidationError::InvalidValue(format!(
// "Service volumes reference unknown volume: {}",
// s.volume
// )));
// }
// });
// }
// Volumes::Long(s) => {
// ctx.volumes.as_ref().map(|available_volumes| {
// if !available_volumes.contains_key(s.source.as_str()) {
// errors.add_error(crate::errors::ValidationError::InvalidValue(format!(
// "Service volumes reference unknown volume: {}",
// s.source
// )));
// }
// });
// }
// }
()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_volume_reference() {
let yaml = r#"
services:
backend:
image: example/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
volumes:
hello:
"#;
let compose = Compose::new(yaml);
assert!(compose.is_ok());
// See above comment about validation
// assert!(compose.is_err());
}
#[test]
fn valid_volume_reference() {
let yaml = r#"
services:
backend:
image: example/backend
volumes:
- type: volume
source: hello
target: /data
volume:
nocopy: true
volumes:
hello:
"#;
let compose = Compose::new(yaml);
assert!(compose.is_ok());
}
}