shrub_rs/models/
project.rs1use crate::models::builtin::EvgCommandType;
7use crate::models::commands::EvgCommand;
8use crate::models::task::EvgTask;
9use crate::models::task_group::EvgTaskGroup;
10use crate::models::variant::BuildVariant;
11use serde::{Deserialize, Serialize};
12use simple_error::bail;
13use std::{collections::HashMap, error::Error};
14use yaml_merge_keys::merge_keys;
15use yaml_rust::{YamlEmitter, YamlLoader};
16
17#[derive(Serialize, Deserialize, Debug, Clone)]
24pub struct EvgParameter {
25 pub key: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub value: Option<String>,
30 pub description: String,
32}
33
34#[derive(Serialize, Deserialize, Debug, Clone)]
36pub struct EvgModule {
37 pub name: String,
39 pub repo: String,
41 pub branch: String,
43 pub prefix: String,
45}
46
47#[derive(Serialize, Deserialize, Debug, Clone)]
49#[serde(untagged)]
50pub enum FunctionDefinition {
51 SingleCommand(EvgCommand),
53 CommandList(Vec<EvgCommand>),
55}
56
57#[derive(Serialize, Deserialize, Debug, Default)]
59pub struct EvgProject {
60 pub buildvariants: Vec<BuildVariant>,
62 pub tasks: Vec<EvgTask>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub task_groups: Option<Vec<EvgTaskGroup>>,
67 #[serde(skip_serializing_if = "HashMap::is_empty")]
69 pub functions: HashMap<String, FunctionDefinition>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub pre: Option<Vec<EvgCommand>>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub post: Option<Vec<EvgCommand>>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub timeout: Option<Vec<EvgCommand>>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub modules: Option<Vec<EvgModule>>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub stepback: Option<bool>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub pre_error_fails_task: Option<bool>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub oom_tracker: Option<bool>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub command_type: Option<EvgCommandType>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub ignore: Option<Vec<String>>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub parameters: Option<Vec<EvgParameter>>,
102}
103
104impl EvgProject {
105 pub fn from_yaml_str(yaml_contents: &str) -> Result<EvgProject, Box<dyn Error>> {
107 let mut raw = YamlLoader::load_from_str(yaml_contents)?;
110 if raw.len() != 1 {
111 bail!("Expected 1 and only 1 yaml document")
112 }
113 let raw = raw.remove(0);
114 let merged = merge_keys(raw)?;
115
116 let mut out_str = String::new();
117 {
118 let mut emitter = YamlEmitter::new(&mut out_str);
119 emitter.dump(&merged)?;
120 }
121
122 Ok(serde_yaml::from_str(&out_str)?)
123 }
124
125 pub fn build_variant_map(&self) -> HashMap<String, &BuildVariant> {
127 let mut map = HashMap::with_capacity(self.buildvariants.len());
128 self.buildvariants.iter().for_each(|bv| {
129 map.insert(bv.name.to_string(), bv);
130 });
131 map
132 }
133
134 pub fn task_def_map(&self) -> HashMap<String, &EvgTask> {
136 let mut map = HashMap::with_capacity(self.tasks.len());
137 self.tasks.iter().for_each(|t| {
138 map.insert(t.name.to_string(), t);
139 });
140 map
141 }
142}
143
144#[cfg(test)]
145mod test {
146 use super::*;
147
148 #[test]
149 fn test_an_empty_document_fails() {
150 let document = "";
151
152 let result = EvgProject::from_yaml_str(document);
153 assert!(result.is_err());
154 }
155
156 #[test]
157 fn test_invalid_yaml_fails() {
158 let document = "garbage input";
159
160 let result = EvgProject::from_yaml_str(document);
161 assert!(result.is_err());
162 }
163 #[test]
164 fn test_valid_document() {
165 let document = r#"
166functions:
167 "example function" :
168 - command: git.get_project
169 params:
170 directory: src
171 - command: shell.exec
172 params:
173 working_dir: src
174 script: |
175 ls
176 - command: s3.put
177 params:
178 aws_key: "test_key"
179 aws_secret: "test_secret"
180 remote_file: remote_file
181 bucket: bucket-name
182 permissions: private
183 local_file: local-file
184 content_type: application/gzip
185 skip_existing: "true"
186 - command: s3.put
187 params:
188 aws_key: "test_key"
189 aws_secret: "test_secret"
190 aws_session_token: "test_token"
191 remote_file: remote_file
192 bucket: bucket-name
193 permissions: private
194 local_file: local-file
195 content_type: application/gzip
196 skip_existing: "true"
197 - command: s3.put
198 params:
199 role_arn: my_arn
200 remote_file: remote_file
201 bucket: bucket-name
202 permissions: private
203 local_file: local-file
204 content_type: application/gzip
205 skip_existing: true
206
207tasks:
208- name: compile
209 depends_on: []
210 commands:
211 - func: "example function"
212
213buildvariants:
214- name: ubuntu
215 display_name: Ubuntu
216 modules: ["render-module"]
217 run_on:
218 - ubuntu1404-test
219 expansions:
220 test_flags: "blah blah"
221 tasks:
222 - name: compile
223"#;
224
225 let result = EvgProject::from_yaml_str(document);
226 assert!(result.is_ok());
227 }
228}