1use clap::{crate_name, crate_version, App, Arg, ArgMatches};
2use config::{Config as ConfigCache, ConfigError};
3use serde::{Deserialize, Serialize};
4use std::path::Path;
5
6const REGISTRY_URL: &str = "registry-url";
7const ENV_REGISTRY_URL: &str = "SOGAR_REGISTRY_URL";
8const USERNAME: &str = "username";
9const ENV_USERNAME: &str = "SOGAR_REGISTRY_USERNAME";
10const PASSWORD: &str = "password";
11const ENV_PASSWORD: &str = "SOGAR_REGISTRY_PASSWORD";
12const MEDIA_TYPE: &str = "media-type";
13const VALUE_MEDIA_TYPE: &str = "MEDIA_TYPE";
14const REFERENCE: &str = "reference";
15const VALUE_REFERENCE: &str = "REFERENCE";
16const FILE_PATH: &str = "filepath";
17const VALUE_FILE_PATH: &str = "FILE_PATH";
18const EXPORT_ARTIFACT: &str = "export-artifact";
19const VALUE_EXPORT_ARTIFACT: &str = "EXPORT_ARTIFACT";
20const IMPORT_ARTIFACT: &str = "import-artifact";
21const VALUE_IMPORT_ARTIFACT: &str = "IMPORT_ARTIFACT";
22const ENV_REGISTRY_CACHE: &str = "SOGAR_REGISTRY_CACHE";
23const REGISTRY_CACHE: &str = "registry-cache";
24const COMMAND_DATA: &str = "command_data";
25const COMMAND_TYPE: &str = "command_type";
26
27#[derive(Serialize, Deserialize, Clone, Debug)]
28pub enum CommandType {
29 Export,
30 Import,
31}
32
33#[derive(Serialize, Deserialize, Clone, Debug)]
34pub struct CommandData {
35 #[serde(rename = "media-type")]
36 pub media_type: String,
37 pub reference: String,
38 pub filepath: Vec<String>,
39}
40
41#[derive(Serialize, Deserialize, Clone, Debug)]
42pub struct Settings {
43 #[serde(rename = "registry-url")]
44 pub registry_url: String,
45 pub username: String,
46 pub password: String,
47 pub command_type: CommandType,
48 pub command_data: CommandData,
49 #[serde(rename = "registry-cache")]
50 pub registry_cache: Option<String>,
51}
52
53impl From<CommandType> for config::Value {
54 fn from(val: CommandType) -> Self {
55 match val {
56 CommandType::Export => config::Value::from(String::from("Export")),
57 CommandType::Import => config::Value::from(String::from("Import")),
58 }
59 }
60}
61
62pub fn match_arguments(matches: &ArgMatches, config_cache: &mut ConfigCache) -> Result<(), ConfigError> {
63 if let Some(registry_url) = matches.value_of(REGISTRY_URL) {
64 config_cache.set(REGISTRY_URL, registry_url.to_string())?;
65 }
66
67 if let Some(username) = matches.value_of(USERNAME) {
68 config_cache.set(USERNAME, username.to_string())?;
69 }
70
71 if let Some(password) = matches.value_of(PASSWORD) {
72 config_cache.set(PASSWORD, password.to_string())?;
73 }
74
75 if let Some(namespace) = matches.value_of(REFERENCE) {
76 config_cache.set(
77 format!("{}.{}", COMMAND_DATA, REFERENCE).as_str(),
78 namespace.to_string(),
79 )?;
80 }
81
82 let file_paths = matches
83 .values_of(FILE_PATH)
84 .unwrap_or_default()
85 .map(|plugin| plugin.to_string())
86 .collect::<Vec<String>>();
87
88 for filepath in &file_paths {
89 match matches.value_of(MEDIA_TYPE) {
90 Some(media_type) => {
91 config_cache.set(
92 format!("{}.{}", COMMAND_DATA, MEDIA_TYPE).as_str(),
93 media_type.to_string(),
94 )?;
95 }
96 None => {
97 config_cache.set(
98 format!("{}.{}", COMMAND_DATA, MEDIA_TYPE).as_str(),
99 get_mime_type_from_file_extension(filepath),
100 )?;
101 }
102 }
103 }
104
105 config_cache.set(format!("{}.{}", COMMAND_DATA, FILE_PATH).as_str(), file_paths)?;
106
107 if let Some(sogar_cache) = matches.value_of(REGISTRY_CACHE) {
108 config_cache.set(REGISTRY_CACHE, sogar_cache.to_string())?;
109 }
110
111 if matches.is_present(EXPORT_ARTIFACT) {
112 config_cache.set(COMMAND_TYPE, CommandType::Export)?;
113 }
114
115 if matches.is_present(IMPORT_ARTIFACT) {
116 config_cache.set(COMMAND_TYPE, CommandType::Import)?;
117 }
118
119 Ok(())
120}
121
122pub fn get_mime_type_from_file_extension(file_name: &str) -> String {
123 if let Some(extension) = Path::new(file_name).extension() {
126 return match format!(".{}", extension.to_str().unwrap()).as_str() {
127 ".zip" => "application/zip".to_string(),
128 ".bz" => "application/x-bzip".to_string(),
129 ".bz2" => "application/x-bzip2".to_string(),
130 ".tar" => "application/x-tar".to_string(),
131 ".7z" => "application/x-7z-compressed".to_string(),
132 ".pdf" => "application/pdf".to_string(),
133 ".json" => "application/json".to_string(),
134 ".js" => "text/javascript".to_string(),
135 ".htm" => "text/html".to_string(),
136 ".html" => "text/html".to_string(),
137 ".rtf" => "application/rtf".to_string(),
138 ".txt" => "text/plain".to_string(),
139 ".bmp" => "image/bmp".to_string(),
140 ".gif" => "image/gif".to_string(),
141 ".ico" => "image/x-icon".to_string(),
142 ".jpeg" => "image/jpeg".to_string(),
143 ".jpg" => "image/jpeg".to_string(),
144 ".png" => "image/png".to_string(),
145 ".svg" => "image/svg+xml".to_string(),
146 ".tif" => "image/tiff".to_string(),
147 ".tiff" => "image/tiff".to_string(),
148 ".webp" => "image/webp".to_string(),
149 ".mp4" => "video/mp4".to_string(),
150 ".mkv" => "video/x-matroska".to_string(),
151 ".mov" => "video/quicktime".to_string(),
152 ".avi" => "video/x-msvideo".to_string(),
153 ".wmv" => "video/x-ms-wmv".to_string(),
154 ".3gp" => "video/3gpp".to_string(),
155 ".flv" => "video/x-flv".to_string(),
156 ".webm" => "video/webm".to_string(),
157 ".mp3" => "audio/mpeg".to_string(),
158 ".wav" => "audio/wav".to_string(),
159 ".weba" => "audio/webm".to_string(),
160 _ => "application/octet-stream".to_string(),
161 };
162 };
163
164 String::from("application/octet-stream")
165}
166
167pub fn create_command_line_app<'a, 'b>() -> App<'a, 'b> {
168 App::new(crate_name!())
169 .author("Devolutions Inc.")
170 .version(concat!(crate_version!(), "\n"))
171 .about("Sogar is a generic implementation of [OCI Artifacts](https://github.com/opencontainers/artifacts)")
172 .arg(
173 Arg::with_name(REGISTRY_URL)
174 .long(REGISTRY_URL)
175 .value_name(ENV_REGISTRY_URL)
176 .help("Registry url to where the artifacts will be pushed.")
177 .takes_value(true)
178 .env(ENV_REGISTRY_URL)
179 .required(true)
180 .empty_values(false),
181 )
182 .arg(
183 Arg::with_name(USERNAME)
184 .short("u")
185 .long(USERNAME)
186 .value_name(ENV_USERNAME)
187 .help("Registry username.")
188 .takes_value(true)
189 .env(ENV_USERNAME)
190 .required(true)
191 .empty_values(false),
192 )
193 .arg(
194 Arg::with_name(PASSWORD)
195 .short("p")
196 .long(PASSWORD)
197 .value_name(ENV_PASSWORD)
198 .help("Registry password.")
199 .takes_value(true)
200 .env(ENV_PASSWORD)
201 .required(true)
202 .empty_values(false),
203 )
204 .arg(
205 Arg::with_name(EXPORT_ARTIFACT)
206 .long(EXPORT_ARTIFACT)
207 .value_name(VALUE_EXPORT_ARTIFACT)
208 .help("Command to export the file to the registry.")
209 .takes_value(false)
210 .requires_all(&[REFERENCE, FILE_PATH]),
211 )
212 .arg(
213 Arg::with_name(MEDIA_TYPE)
214 .long(MEDIA_TYPE)
215 .value_name(VALUE_MEDIA_TYPE)
216 .help("Media type of the file. If not set the default one will be used.")
217 .takes_value(true)
218 .empty_values(false),
219 )
220 .arg(
221 Arg::with_name(REFERENCE)
222 .long(REFERENCE)
223 .value_name(VALUE_REFERENCE)
224 .help("Namespace of the registry where to push the file.")
225 .takes_value(true)
226 .empty_values(false),
227 )
228 .arg(
229 Arg::with_name(FILE_PATH)
230 .long(FILE_PATH)
231 .value_name(VALUE_FILE_PATH)
232 .help("Path of the file that will be exported.")
233 .takes_value(true)
234 .empty_values(false)
235 .multiple(true)
236 .use_delimiter(true)
237 .value_delimiter(";")
238 .number_of_values(1),
239 )
240 .arg(
241 Arg::with_name(IMPORT_ARTIFACT)
242 .long(IMPORT_ARTIFACT)
243 .value_name(VALUE_IMPORT_ARTIFACT)
244 .help("Command to import the file to the registry.")
245 .takes_value(false)
246 .requires_all(&[REFERENCE, FILE_PATH]),
247 )
248 .arg(
249 Arg::with_name(REGISTRY_CACHE)
250 .long(REGISTRY_CACHE)
251 .value_name(ENV_REGISTRY_CACHE)
252 .help("Path to the directory where cache will be located.")
253 .takes_value(true)
254 .env(ENV_REGISTRY_CACHE)
255 .empty_values(false),
256 )
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_map_contains_value() {
265 let res = get_mime_type_from_file_extension("test.json");
266
267 assert_eq!(String::from("application/json"), res);
268 }
269
270 #[test]
271 fn test_map_not_contains_value() {
272 let res = get_mime_type_from_file_extension("test.pcap");
273
274 assert_eq!(String::from("application/octet-stream"), res);
275 }
276}