Skip to content

Commit e2dff01

Browse files
authored
Merge pull request #94 from PLUS-POSTECH/fileentry-improvements
Refactor FileEntry
2 parents 2fba30d + 4c32f4d commit e2dff01

File tree

11 files changed

+385
-229
lines changed

11 files changed

+385
-229
lines changed

Cargo.lock

Lines changed: 220 additions & 160 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ edition = "2018"
88
[dependencies]
99
bollard = "0.1.3"
1010
clap = "~2.32.0"
11-
crossterm = "0.5.4"
11+
crossterm = "0.5.5"
1212
dirs = "1.0.4"
1313
failure = "0.1.5"
1414
flate2 = "1.0.6"
@@ -17,18 +17,20 @@ fs_extra = "1.1.0"
1717
futures = "0.1.25"
1818
git2 = "0.8.0"
1919
handlebars = "1.1.0"
20-
hyper = "0.12.21"
21-
question = "0.2.2"
20+
hyper = "0.12.23"
2221
remove_dir_all = "0.5.1"
2322
serde = { version = "1.0.85", features = ["derive"] }
2423
serde_cbor = "0.9.0"
2524
tar = "0.4.20"
2625
tempfile = "3.0.5"
27-
tokio = "0.1.14"
26+
tokio = "0.1.15"
2827
toml = "0.4.10"
2928
url = "1.7.2"
3029
whoami = "0.4.1"
3130

31+
[dev-dependencies]
32+
serde_test = "1.0.85"
33+
3234
[lib]
3335
name = "soma"
3436
path = "src/lib.rs"

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,25 @@ soma fetch simple-bof # this will copy public files to the current working dire
2828

2929
### For problem setters
3030

31-
*This section contains a few commands that are under development.*
31+
*This section contains some features that are under development.*
3232

3333
Add `soma.toml` to your project root directory that describes your problem.
3434
The config file below shows an example of it.
3535

3636
```toml
3737
name = "simple-bof"
3838

39-
[[executable]]
39+
[binary]
40+
os = "ubuntu:16.04"
41+
cmd = "./simple-bof"
42+
43+
[[binary.executable]]
4044
path = "build/simple-bof"
4145
public = true
4246

43-
[[readonly]]
47+
[[binary.readonly]]
4448
path = "flag"
4549

46-
[binary]
47-
os = "ubuntu:16.04"
48-
entry = "./simple-bof"
4950
```
5051

5152
That's all! Soma gets enough information to run your binary from these 12 lines of configuration.
@@ -65,10 +66,10 @@ Soma team is hoping to ship it in the first quarter of 2019.
6566

6667
### Roadmap
6768

68-
- [ ] Implement core commands. ([#4][issue #4] and [#66][issue #66]) (priority: high)
69+
- [x] ~~Implement core commands. ([#4][issue #4] and [#66][issue #66]) (priority: high)~~
6970
- [x] ~~Write tests. (priority: high)~~
7071
- [ ] Better documentation of features. (priority: medium)
71-
- [ ] Support multiple problems in a single repository. (priority: medium)
72+
- [ ] Support multiple problems in a single repository. ([#36][issue #36]) (priority: medium)
7273
- [ ] Support multiple containers for a single problem. (priority: medium)
7374
- [ ] Support cloud deployment such as AWS, GCP, Azure as well as local deployment. (priority: low)
7475

@@ -83,6 +84,7 @@ Soma team is hoping to ship it in the first quarter of 2019.
8384
* `rustup component add rustfmt`
8485
* Copy files in `hooks` directory to `.git/hooks`.
8586

87+
8688
### Testing, Building, and Running
8789

8890
Soma is written with Rust and utilizes Cargo as a building and testing system.
@@ -110,5 +112,6 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
110112

111113

112114
[issue #4]: https://github.com/PLUS-POSTECH/soma/issues/4
115+
[issue #36]: https://github.com/PLUS-POSTECH/soma/issues/36
113116
[issue #66]: https://github.com/PLUS-POSTECH/soma/issues/66
114117
[`0.1.0` milestone]: https://github.com/PLUS-POSTECH/soma/milestone/1

src/data_dir.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::ops::Deref;
66
use std::path::{Path, PathBuf};
77

88
use fs2::FileExt;
9-
use question::{Answer, Question};
109

1110
use crate::prelude::*;
1211

@@ -34,15 +33,8 @@ impl DataDirectory {
3433
};
3534

3635
if !path.exists() {
37-
if let Answer::NO = Question::new(&format!(
38-
"It seems that you use Soma for the first time\nThe data directory will be initialized at {:?} [y/n]\n",
39-
path.as_os_str(),
40-
))
41-
.confirm()
42-
{
43-
Err(SomaError::DataDirectoryAccessError)?;
44-
}
4536
fs::create_dir_all(&path)?;
37+
println!("Created Soma data directory at: {}", path.to_string_lossy());
4638
}
4739

4840
DataDirectory::initialize_and_lock(path)

src/ops.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ pub fn fetch(
8484
let repo_path = repository.local_path();
8585

8686
let manifest = load_manifest(repo_path.join(MANIFEST_FILE_NAME))?;
87-
let executables = manifest.executable().iter();
88-
let readonly = manifest.readonly().iter();
87+
let binary = manifest.binary();
88+
let executables = binary.executable().iter();
89+
let readonly = binary.readonly().iter();
8990

9091
executables
9192
.chain(readonly)
@@ -261,7 +262,7 @@ pub fn stop(
261262
}
262263

263264
let container_list = docker::containers_from_repo(container_list, problem_name);
264-
let states_to_stop = vec!["paused", "restarting", "running"];
265+
let states_to_stop = &["paused", "restarting", "running"];
265266

266267
let containers_to_stop = container_list
267268
.iter()

src/repo.rs

Lines changed: 135 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use std::path::{Path, PathBuf};
66
use fs_extra::dir::{copy, CopyOptions};
77
use git2::{BranchType, ObjectType, Repository as GitRepository, ResetType};
88
use remove_dir_all::remove_dir_all;
9+
use serde::de::{self, Deserializer, Unexpected, Visitor};
10+
use serde::ser::Serializer;
911
use serde::{Deserialize, Serialize};
1012

1113
use crate::prelude::*;
@@ -103,25 +105,57 @@ impl<'a> Repository<'a> {
103105
pub struct Manifest {
104106
name: String,
105107
work_dir: Option<PathBuf>,
106-
executable: Vec<FileEntry>,
107-
readonly: Vec<FileEntry>,
108108
binary: BinaryConfig,
109109
}
110110

111111
#[derive(Serialize)]
112112
pub struct SolidManifest {
113113
name: String,
114114
work_dir: PathBuf,
115-
executable: Vec<SolidFileEntry>,
116-
readonly: Vec<SolidFileEntry>,
117-
binary: BinaryConfig,
115+
binary: SolidBinaryConfig,
118116
}
119117

120118
impl Manifest {
121119
pub fn name(&self) -> &String {
122120
&self.name
123121
}
124122

123+
pub fn binary(&self) -> &BinaryConfig {
124+
&self.binary
125+
}
126+
127+
pub fn solidify(&self) -> SomaResult<SolidManifest> {
128+
let work_dir = match &self.work_dir {
129+
Some(path) => path.clone(),
130+
None => PathBuf::from(format!("/home/{}", self.name)),
131+
};
132+
133+
let binary = self.binary.solidify(&work_dir)?;
134+
135+
Ok(SolidManifest {
136+
name: self.name.clone(),
137+
work_dir,
138+
binary,
139+
})
140+
}
141+
}
142+
143+
#[derive(Deserialize)]
144+
pub struct BinaryConfig {
145+
os: String,
146+
cmd: String,
147+
executable: Vec<FileEntry>,
148+
readonly: Vec<FileEntry>,
149+
}
150+
151+
#[derive(Serialize)]
152+
struct SolidBinaryConfig {
153+
os: String,
154+
cmd: String,
155+
file_entries: Vec<SolidFileEntry>,
156+
}
157+
158+
impl BinaryConfig {
125159
pub fn executable(&self) -> &Vec<FileEntry> {
126160
&self.executable
127161
}
@@ -130,32 +164,81 @@ impl Manifest {
130164
&self.readonly
131165
}
132166

133-
pub fn solidify(&self) -> SomaResult<SolidManifest> {
134-
let work_dir = match &self.work_dir {
135-
Some(path) => path.clone(),
136-
None => PathBuf::from(format!("/home/{}", self.name)),
137-
};
167+
fn solidify(&self, work_dir: impl AsRef<Path>) -> SomaResult<SolidBinaryConfig> {
138168
let executable = self
139169
.executable
140170
.iter()
141-
.map(|file| file.solidify(&work_dir))
142-
.collect::<SomaResult<Vec<_>>>()?;
171+
.map(|file| file.solidify(&work_dir, FilePermissions::Executable));
143172
let readonly = self
144173
.readonly
145174
.iter()
146-
.map(|file| file.solidify(&work_dir))
147-
.collect::<SomaResult<Vec<_>>>()?;
175+
.map(|file| file.solidify(&work_dir, FilePermissions::ReadOnly));
176+
let file_entries = executable.chain(readonly).collect::<SomaResult<Vec<_>>>()?;
148177

149-
Ok(SolidManifest {
150-
name: self.name.clone(),
151-
work_dir,
152-
executable,
153-
readonly,
154-
binary: self.binary.clone(),
178+
Ok(SolidBinaryConfig {
179+
os: self.os.clone(),
180+
cmd: self.cmd.clone(),
181+
file_entries,
155182
})
156183
}
157184
}
158185

186+
#[derive(Debug, PartialEq)]
187+
enum FilePermissions {
188+
Custom(u16),
189+
Executable,
190+
ReadOnly,
191+
// Reserved: FetchOnly, ReadWrite
192+
}
193+
194+
impl Serialize for FilePermissions {
195+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
196+
where
197+
S: Serializer,
198+
{
199+
let permissions_string = match self {
200+
FilePermissions::Custom(permissions) => format!("{:o}", permissions),
201+
FilePermissions::Executable => "550".to_owned(),
202+
FilePermissions::ReadOnly => "440".to_owned(),
203+
};
204+
serializer.serialize_str(&permissions_string)
205+
}
206+
}
207+
208+
impl<'de> Deserialize<'de> for FilePermissions {
209+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210+
where
211+
D: Deserializer<'de>,
212+
{
213+
deserializer.deserialize_str(PermissionsString)
214+
}
215+
}
216+
217+
struct PermissionsString;
218+
219+
impl<'de> Visitor<'de> for PermissionsString {
220+
type Value = FilePermissions;
221+
222+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
223+
write!(
224+
formatter,
225+
"a file permissions string in octal number format"
226+
)
227+
}
228+
229+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
230+
where
231+
E: de::Error,
232+
{
233+
let permissions = u16::from_str_radix(s, 8);
234+
match permissions {
235+
// Support sticky bits later
236+
Ok(permissions) if permissions <= 0o777 => Ok(FilePermissions::Custom(permissions)),
237+
_ => Err(de::Error::invalid_value(Unexpected::Str(s), &self)),
238+
}
239+
}
240+
}
241+
159242
// target_path is defined as String instead of PathBuf to support Windows
160243
#[derive(Deserialize)]
161244
pub struct FileEntry {
@@ -165,10 +248,11 @@ pub struct FileEntry {
165248
}
166249

167250
#[derive(Serialize)]
168-
pub struct SolidFileEntry {
251+
struct SolidFileEntry {
169252
path: PathBuf,
170253
public: bool,
171254
target_path: String,
255+
permissions: FilePermissions,
172256
}
173257

174258
impl FileEntry {
@@ -180,11 +264,11 @@ impl FileEntry {
180264
self.public.unwrap_or(false)
181265
}
182266

183-
pub fn target_path(&self) -> &Option<String> {
184-
&self.target_path
185-
}
186-
187-
pub fn solidify(&self, work_dir: impl AsRef<Path>) -> SomaResult<SolidFileEntry> {
267+
fn solidify(
268+
&self,
269+
work_dir: impl AsRef<Path>,
270+
permissions: FilePermissions,
271+
) -> SomaResult<SolidFileEntry> {
188272
let target_path = match &self.target_path {
189273
Some(path) => path.clone(),
190274
None => {
@@ -206,16 +290,11 @@ impl FileEntry {
206290
path: self.path.clone(),
207291
public: self.public.unwrap_or(false),
208292
target_path,
293+
permissions,
209294
})
210295
}
211296
}
212297

213-
#[derive(Clone, Deserialize, Serialize)]
214-
struct BinaryConfig {
215-
os: String,
216-
entry: String,
217-
}
218-
219298
fn read_file_contents(path: impl AsRef<Path>) -> SomaResult<Vec<u8>> {
220299
let mut file = File::open(path)?;
221300
let mut contents = Vec::new();
@@ -226,3 +305,27 @@ fn read_file_contents(path: impl AsRef<Path>) -> SomaResult<Vec<u8>> {
226305
pub fn load_manifest(manifest_path: impl AsRef<Path>) -> SomaResult<Manifest> {
227306
Ok(toml::from_slice(&read_file_contents(manifest_path)?)?)
228307
}
308+
309+
#[cfg(test)]
310+
mod tests {
311+
use super::*;
312+
use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, Token};
313+
314+
#[test]
315+
fn test_file_permissions_ser() {
316+
assert_ser_tokens(&FilePermissions::Executable, &[Token::Str("550")]);
317+
assert_ser_tokens(&FilePermissions::ReadOnly, &[Token::Str("440")]);
318+
assert_ser_tokens(&FilePermissions::Custom(0o777), &[Token::Str("777")]);
319+
}
320+
321+
#[test]
322+
fn test_file_permissions_de() {
323+
assert_de_tokens(&FilePermissions::Custom(0o550), &[Token::Str("550")]);
324+
assert_de_tokens(&FilePermissions::Custom(0o440), &[Token::Str("440")]);
325+
assert_de_tokens(&FilePermissions::Custom(0o777), &[Token::Str("777")]);
326+
assert_de_tokens_error::<FilePermissions>(&[
327+
Token::Str("1000")
328+
], "invalid value: string \"1000\", expected a file permissions string in octal number format"
329+
);
330+
}
331+
}

src/template.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ pub enum Templates {
1313
}
1414

1515
impl Templates {
16-
fn templates(&self) -> Vec<(&'static str, &'static str)> {
16+
fn templates(&self) -> &[(&str, &str)] {
1717
match self {
18-
Templates::Binary => vec![
18+
Templates::Binary => &[
1919
("Dockerfile", include_str!("../templates/binary/Dockerfile")),
2020
("start.sh", include_str!("../templates/binary/start.sh")),
2121
],

0 commit comments

Comments
 (0)