1use std::cell::{RefCell, RefMut};
2use std::fmt;
3use std::fs::File;
4use std::io::Read;
5use std::ops::Deref;
6use std::path::Path;
7
8use bollard::Docker;
9use clap::crate_version;
10use hyper::client::connect::Connect;
11use lazy_static::lazy_static;
12use regex::Regex;
13use serde::de::{self, Deserializer, Unexpected, Visitor};
14use serde::ser::Serializer;
15use serde::{Deserialize, Serialize};
16
17use crate::data_dir::DataDirectory;
18use crate::prelude::*;
19use crate::repository::RepositoryManager;
20
21pub mod data_dir;
22pub mod docker;
23pub mod error;
24pub mod ops;
25pub mod prelude;
26pub mod problem;
27pub mod repository;
28pub mod template;
29
30pub const VERSION: &str = crate_version!();
31
32pub trait Printer {
33 type Handle;
34
35 fn get_current_handle(&mut self) -> Self::Handle;
36 fn write_line_at(&mut self, handle: &Self::Handle, message: &str);
37 fn write_line(&mut self, message: &str);
38}
39
40pub struct Environment<'a, C: 'static, P: Printer + 'static> {
41 username: NameString,
42 repo_manager: RepositoryManager<'a>,
43 docker: Docker<C>,
44 printer: RefCell<P>,
45}
46
47impl<'a, C, P> Environment<'a, C, P>
48where
49 C: Connect,
50 P: Printer,
51{
52 pub fn new(
53 username: String,
54 data_dir: &'a mut DataDirectory,
55 docker: Docker<C>,
56 printer: P,
57 ) -> SomaResult<Environment<'a, C, P>> {
58 let repo_manager = data_dir.register::<RepositoryManager>()?;
59 let username = NameString::try_from(username)?;
60
61 Ok(Environment {
62 username,
63 repo_manager,
64 docker,
65 printer: RefCell::new(printer),
66 })
67 }
68
69 pub fn username(&self) -> &NameString {
70 &self.username
71 }
72
73 pub fn printer(&self) -> RefMut<P> {
74 self.printer.borrow_mut()
75 }
76
77 pub fn repo_manager(&self) -> &RepositoryManager<'a> {
78 &self.repo_manager
79 }
80
81 pub fn repo_manager_mut(&mut self) -> &mut RepositoryManager<'a> {
82 &mut self.repo_manager
83 }
84}
85
86#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
87pub struct NameString {
88 inner: String,
89}
90
91lazy_static! {
92 static ref NAME_REGEX: Regex = Regex::new(r"^[a-z0-9]+((?:_|__|[-]*)[a-z0-9]+)*$").unwrap();
93}
94
95impl NameString {
96 pub fn try_from(s: impl AsRef<str>) -> SomaResult<NameString> {
98 let s = s.as_ref();
99 if NAME_REGEX.is_match(s) {
100 Ok(NameString {
101 inner: s.to_owned(),
102 })
103 } else {
104 Err(SomaError::InvalidName)?
105 }
106 }
107}
108
109impl PartialEq<String> for NameString {
110 fn eq(&self, other: &String) -> bool {
111 &self.inner == other
112 }
113}
114
115impl PartialEq<str> for NameString {
116 fn eq(&self, other: &str) -> bool {
117 self.inner == other
118 }
119}
120
121impl PartialEq<NameString> for String {
122 fn eq(&self, other: &NameString) -> bool {
123 self == &other.inner
124 }
125}
126
127impl PartialEq<NameString> for str {
128 fn eq(&self, other: &NameString) -> bool {
129 self == other.inner
130 }
131}
132
133impl fmt::Display for NameString {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 write!(f, "{}", self.inner)
136 }
137}
138
139impl Deref for NameString {
140 type Target = String;
141
142 fn deref(&self) -> &String {
143 &self.inner
144 }
145}
146
147impl AsRef<str> for NameString {
148 fn as_ref(&self) -> &str {
149 &self.inner
150 }
151}
152
153impl AsRef<Path> for NameString {
154 fn as_ref(&self) -> &Path {
155 Path::new(&self.inner)
156 }
157}
158
159impl Serialize for NameString {
160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161 where
162 S: Serializer,
163 {
164 serializer.serialize_str(self.as_ref())
165 }
166}
167
168impl<'de> Deserialize<'de> for NameString {
169 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170 where
171 D: Deserializer<'de>,
172 {
173 deserializer.deserialize_str(NameStringVisitor)
174 }
175}
176
177struct NameStringVisitor;
178
179impl<'de> Visitor<'de> for NameStringVisitor {
180 type Value = NameString;
181
182 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
183 write!(formatter, "a string satisfying docker name component rules")
184 }
185
186 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
187 where
188 E: de::Error,
189 {
190 let name = NameString::try_from(s);
191 match name {
192 Ok(name) => Ok(name),
193 Err(_) => Err(de::Error::invalid_value(Unexpected::Str(s), &self)),
194 }
195 }
196}
197
198fn read_file_contents(path: impl AsRef<Path>) -> SomaResult<Vec<u8>> {
199 let mut file = File::open(path)?;
200 let mut contents = Vec::new();
201 file.read_to_end(&mut contents)?;
202 Ok(contents)
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use serde_test::{assert_de_tokens_error, assert_tokens, Token};
209
210 #[test]
211 fn test_name_eq() {
212 assert_eq!("asdf", &NameString::try_from("asdf").unwrap());
213 assert_eq!(&NameString::try_from("asdf").unwrap(), "asdf");
214 assert_eq!(String::from("asdf"), NameString::try_from("asdf").unwrap());
215 assert_eq!(NameString::try_from("asdf").unwrap(), String::from("asdf"));
216 assert_ne!("qwer", &NameString::try_from("asdf").unwrap());
217 assert_ne!(&NameString::try_from("qwer").unwrap(), "asdf");
218 assert_ne!(String::from("qwer"), NameString::try_from("asdf").unwrap());
219 assert_ne!(NameString::try_from("qwer").unwrap(), String::from("asdf"));
220 }
221
222 #[test]
223 fn test_name_serde() {
224 assert_tokens(
225 &NameString::try_from("asdf0").unwrap(),
226 &[Token::Str("asdf0")],
227 );
228 assert_tokens(
229 &NameString::try_from("asdf0_qwer").unwrap(),
230 &[Token::Str("asdf0_qwer")],
231 );
232 assert_tokens(
233 &NameString::try_from("asdf0__qwer").unwrap(),
234 &[Token::Str("asdf0__qwer")],
235 );
236 assert_tokens(
237 &NameString::try_from("asdf0-qwer").unwrap(),
238 &[Token::Str("asdf0-qwer")],
239 );
240 assert_tokens(
241 &NameString::try_from("asdf0--qwer").unwrap(),
242 &[Token::Str("asdf0--qwer")],
243 );
244 assert_tokens(
245 &NameString::try_from("asdf0---qwer").unwrap(),
246 &[Token::Str("asdf0---qwer")],
247 );
248 }
249
250 #[test]
251 fn test_name_de_error() {
252 assert_de_tokens_error::<NameString>(
253 &[Token::Str("")],
254 "invalid value: string \"\", expected a string satisfying docker name component rules",
255 );
256 assert_de_tokens_error::<NameString>(&[
257 Token::Str("ASDF")
258 ], "invalid value: string \"ASDF\", expected a string satisfying docker name component rules"
259 );
260 assert_de_tokens_error::<NameString>(&[
261 Token::Str("AS@DF")
262 ], "invalid value: string \"AS@DF\", expected a string satisfying docker name component rules"
263 );
264 assert_de_tokens_error::<NameString>(&[
265 Token::Str("asdf0.qwer")
266 ], "invalid value: string \"asdf0.qwer\", expected a string satisfying docker name component rules"
267 );
268 assert_de_tokens_error::<NameString>(&[
269 Token::Str("asdf..qwer")
270 ], "invalid value: string \"asdf..qwer\", expected a string satisfying docker name component rules"
271 );
272 assert_de_tokens_error::<NameString>(&[
273 Token::Str("asdf___qwer")
274 ], "invalid value: string \"asdf___qwer\", expected a string satisfying docker name component rules"
275 );
276 assert_de_tokens_error::<NameString>(&[
277 Token::Str("asdf.")
278 ], "invalid value: string \"asdf.\", expected a string satisfying docker name component rules"
279 );
280 assert_de_tokens_error::<NameString>(&[
281 Token::Str("asdf_")
282 ], "invalid value: string \"asdf_\", expected a string satisfying docker name component rules"
283 );
284 assert_de_tokens_error::<NameString>(&[
285 Token::Str("asdf-")
286 ], "invalid value: string \"asdf-\", expected a string satisfying docker name component rules"
287 );
288 assert_de_tokens_error::<NameString>(&[
289 Token::Str(".asdf")
290 ], "invalid value: string \".asdf\", expected a string satisfying docker name component rules"
291 );
292 assert_de_tokens_error::<NameString>(&[
293 Token::Str("_asdf")
294 ], "invalid value: string \"_asdf\", expected a string satisfying docker name component rules"
295 );
296 assert_de_tokens_error::<NameString>(&[
297 Token::Str("-asdf")
298 ], "invalid value: string \"-asdf\", expected a string satisfying docker name component rules"
299 );
300 }
301}