compose_validatr/
compose.rs1use std::{collections::HashMap, fmt::Display};
4
5use crate::{
6 configs::Config,
7 errors::{ValidationError, ValidationErrors},
8 networks::Network,
9 secrets::Secret,
10 services::Service,
11 volumes::Volume,
12};
13
14use super::{configs, networks, secrets, services, volumes};
15use serde::{Deserialize, Serialize};
16use serde_yaml;
17
18#[derive(Debug, Clone, Deserialize, Serialize)]
23pub struct Compose {
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub version: Option<String>,
26
27 pub services: HashMap<String, services::Service>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub networks: Option<HashMap<String, Option<networks::Network>>>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub volumes: Option<HashMap<String, Option<volumes::Volume>>>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub configs: Option<HashMap<String, Option<configs::Config>>>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub secrets: Option<HashMap<String, Option<secrets::Secret>>>,
40}
41
42impl Compose {
43 pub fn new(contents: &str) -> Result<Self, ValidationErrors> {
45 let mut errors = ValidationErrors::new();
46 let compose: Result<Self, ValidationError> = serde_yaml::from_str(contents)
47 .map_err(|e| ValidationError::InvalidCompose(e.to_string()));
48
49 match compose {
50 Ok(c) => {
51 if let Some(networks) = &c.networks {
52 Self::validate_networks(&c, networks, &mut errors);
53 };
54 if let Some(volumes) = &c.volumes {
55 Self::validate_volumes(&c, volumes, &mut errors);
56 };
57 if let Some(configs) = &c.configs {
58 Self::validate_configs(&c, configs, &mut errors);
59 };
60 if let Some(secrets) = &c.secrets {
61 Self::validate_secrets(&c, secrets, &mut errors);
62 };
63 Self::validate_services(&c, &c.services, &mut errors);
64 if errors.has_errors() {
65 return Err(errors);
66 }
67 Ok(c)
68 }
69 Err(err) => {
70 errors.add_error(err);
71 Err(errors)
72 }
73 }
74 }
75
76 fn validate_networks(
78 compose: &Compose,
79 networks: &HashMap<String, Option<Network>>,
80 errors: &mut ValidationErrors,
81 ) {
82 for (_, network_attributes) in networks {
83 if let Some(network) = network_attributes {
84 network.validate(compose, errors);
85 }
86 }
87 }
88
89 fn validate_volumes(
91 compose: &Compose,
92 volumes: &HashMap<String, Option<Volume>>,
93 errors: &mut ValidationErrors,
94 ) {
95 for (_, volume_attributes) in volumes {
96 if let Some(volume) = volume_attributes {
97 volume.validate(compose, errors);
98 }
99 }
100 }
101
102 fn validate_configs(
104 compose: &Compose,
105 configs: &HashMap<String, Option<Config>>,
106 errors: &mut ValidationErrors,
107 ) {
108 for (_, config_attributes) in configs {
109 if let Some(config) = config_attributes {
110 config.validate(compose, errors);
111 }
112 }
113 }
114
115 fn validate_secrets(
117 compose: &Compose,
118 secrets: &HashMap<String, Option<Secret>>,
119 errors: &mut ValidationErrors,
120 ) {
121 for (_, secret_attributes) in secrets {
122 if let Some(secret) = secret_attributes {
123 secret.validate(compose, errors);
124 }
125 }
126 }
127
128 fn validate_services(
130 compose: &Compose,
131 services: &HashMap<String, Service>,
132 errors: &mut ValidationErrors,
133 ) {
134 for (_, service) in services {
135 service.validate(compose, errors);
136 }
137 }
138}
139
140pub(crate) trait Validate {
142 fn validate(&self, ctx: &Compose, errors: &mut ValidationErrors);
147}
148
149impl Display for Compose {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 write!(f, "{}", serde_yaml::to_string(&self).unwrap())
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn simple_compose() {
161 let yaml = r#"
162 "#;
163
164 let compose = Compose::new(yaml);
165 assert!(compose.is_err());
166 }
167
168 #[test]
169 fn big_compose() {
170 let yaml = r#"
171 version: '3.9'
172
173 services:
174 gitlab:
175 image: gitlab/gitlab-ce:latest
176 container_name: gitlab
177 hostname: gitlab
178 restart: always
179 depends_on:
180 - postgres
181 ports:
182 - "8080:80"
183 - "8443:443"
184 - "8022:22"
185 environment:
186 GITLAB_ROOT_PASSWORD: eYPkjBbrtzX8eGVc
187 DATABASE_URL: "postgres://gitlab:eYPkjBbrtzX8eGVc@postgres:5432/gitlab"
188 volumes:
189 - ./gitlab/config:/etc/gitlab
190 - ./gitlab/logs:/var/log/gitlab
191 - ./gitlab/data:/var/opt/gitlab
192 shm_size: '256m'
193
194 registry:
195 image: registry:2
196 container_name: registry
197 hostname: registry
198 ports:
199 - "5000:5000"
200 volumes:
201 - registry:/var/lib/registry
202
203 sonarqube:
204 build:
205 context: ./sonarqube_image
206 container_name: sonarqube
207 hostname: sonarqube
208 restart: always
209 ports:
210 - "9000:9000"
211 - "9092:9092"
212 volumes:
213 - sonarqube:/opt/sonarqube/data
214 - sonarqube:/opt/sonarqube/logs
215 - sonarqube:/opt/sonarqube/extensions
216
217 jenkins:
218 build:
219 context: ./jenkins_image
220 container_name: jenkins
221 hostname: jenkins
222 restart: always
223 ports:
224 - "9080:8080"
225 - "50000:50000"
226 volumes:
227 - jenkins:/var/jenkins_home
228 - jenkins-data:/var/jenkins_home
229 - jenkins-docker-certs:/certs/client:ro
230 environment:
231 - JAVA_OPTS=-Djenkins.install.runSetupWizard=false
232 - DOCKER_HOST=tcp://docker:2376
233 - DOCKER_CERT_PATH=/certs/client
234 - DOCKER_TLS_VERIFY=1
235
236 jenkins-docker:
237 image: docker:dind
238 container_name: jenkins-docker
239 hostname: docker
240 privileged: true
241 environment:
242 - DOCKER_TLS_CERTDIR=/certs
243 volumes:
244 - /etc/docker/daemon.json:/etc/docker/daemon.json
245 - jenkins-docker-certs:/certs/client
246 - jenkins-data:/var/jenkins_home
247 ports:
248 - '2376:2376'
249 command: --storage-driver overlay2
250
251 postgres:
252 image: postgres:latest
253 container_name: postgres
254 hostname: postgres
255 restart: always
256 ports:
257 - "5432:5432"
258 volumes:
259 - postgres:/var/lib/postgresql/data
260 environment:
261 POSTGRES_DB: gitlab
262 POSTGRES_USER: gitlab
263 POSTGRES_PASSWORD: eYPkjBbrtzX8eGVc
264
265 volumes:
266 sonarqube:
267 jenkins:
268 jenkins-docker-certs:
269 jenkins-data:
270 postgres:
271 registry:
272
273 networks:
274 default:
275 driver: bridge
276 "#;
277 let compose = Compose::new(yaml);
278 assert!(compose.is_ok());
279 }
280}