1use std::io::Write;
6use std::os::unix::fs::OpenOptionsExt;
7
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use xianyun_core::config::latest::ReloaderConfig;
12
13#[derive(Debug, Error)]
15pub enum MirrordError {
16 #[error("reloader type is not 'mirror'")]
18 NotMirrorReloader,
19 #[error("mirror configuration is missing")]
21 MissingMirrorConfig,
22 #[error("target configuration is missing")]
24 MissingTargetConfig,
25 #[error("opening mirrord config file {0}: {1}")]
27 OpenFile(String, std::io::Error),
28 #[error("marshaling mirrord config: {0}")]
30 Marshal(#[from] serde_json::Error),
31 #[error("writing mirrord config file {0}: {1}")]
33 WriteFile(String, std::io::Error),
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Config {
39 pub target: TargetConfig,
40 pub feature: FeatureConfig,
41 pub agent: AgentConfig,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct TargetConfig {
47 pub path: String,
48 pub namespace: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct FeatureConfig {
54 pub network: NetworkConfig,
55 pub fs: FsConfig,
56 pub env: EnvConfig,
57}
58
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct OutgoingConfig {
62 #[serde(skip_serializing_if = "std::ops::Not::not", default)]
63 pub ignore_localhost: bool,
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68pub struct IncomingConfig {
69 #[serde(skip_serializing_if = "String::is_empty", default)]
70 pub mode: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct NetworkConfig {
76 pub incoming: IncomingConfig,
77 pub outgoing: OutgoingConfig,
78}
79
80#[derive(Debug, Clone, Default, Serialize, Deserialize)]
82pub struct FsConfig {
83 pub mode: String,
84 #[serde(skip_serializing_if = "Vec::is_empty", default)]
85 pub read_write: Vec<String>,
86 #[serde(skip_serializing_if = "Vec::is_empty", default)]
87 pub read_only: Vec<String>,
88 #[serde(skip_serializing_if = "Option::is_none", default)]
89 pub mapping: Option<std::collections::HashMap<String, String>>,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct EnvConfig {
96 #[serde(skip_serializing_if = "Vec::is_empty", default)]
97 pub exclude: Vec<String>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct AgentConfig {
103 pub namespace: String,
104}
105
106pub fn generate_config(
110 reloader_config: &ReloaderConfig,
111 filename: &str,
112) -> Result<(), MirrordError> {
113 if reloader_config.reloader_type != "mirror" {
114 return Err(MirrordError::NotMirrorReloader);
115 }
116
117 let mirror = reloader_config
118 .mirror
119 .as_ref()
120 .ok_or(MirrordError::MissingMirrorConfig)?;
121
122 let target = mirror
123 .target
124 .as_ref()
125 .ok_or(MirrordError::MissingTargetConfig)?;
126
127 let mut target_path = format!("{}/{}", target.kubernetes_type, target.name);
129 if !target.container.is_empty() {
130 target_path = format!("{target_path}/container/{}", target.container);
131 }
132
133 let mut outgoing = OutgoingConfig::default();
135 let mut incoming_mode = "steal".to_string();
136
137 if let Some(network) = &mirror.network {
138 if let Some(out) = &network.outgoing {
139 outgoing.ignore_localhost = out.ignore_localhost;
140 }
141
142 if let Some(inc) = &network.incoming
143 && !inc.mode.is_empty()
144 {
145 incoming_mode = inc.mode.clone();
146 }
147 }
148
149 let mut fs_mode = "write".to_string();
151 if let Some(filesystem) = &mirror.filesystem
152 && !filesystem.mode.is_empty()
153 {
154 fs_mode = filesystem.mode.clone();
155 }
156
157 let mut cfg = Config {
158 target: TargetConfig {
159 path: target_path,
160 namespace: target.namespace.clone(),
161 },
162 feature: FeatureConfig {
163 network: NetworkConfig {
164 incoming: IncomingConfig {
165 mode: incoming_mode,
166 },
167 outgoing,
168 },
169 fs: FsConfig {
170 mode: fs_mode,
171 ..Default::default()
172 },
173 env: EnvConfig {
174 exclude: vec!["GODEBUG".to_string()],
177 },
178 },
179 agent: AgentConfig {
180 namespace: target.namespace.clone(),
181 },
182 };
183
184 if let Some(filesystem) = &mirror.filesystem {
185 cfg.feature.fs.read_write = filesystem.read_write.clone();
186 cfg.feature.fs.read_only = filesystem.read_only.clone();
187 cfg.feature.fs.mapping = filesystem
188 .mapping
189 .as_ref()
190 .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
191 }
192
193 write_json_config(&cfg, filename)
194}
195
196fn write_json_config(cfg: &Config, filename: &str) -> Result<(), MirrordError> {
201 let mut file = std::fs::OpenOptions::new()
202 .write(true)
203 .create(true)
204 .truncate(true)
205 .mode(0o600)
206 .open(filename)
207 .map_err(|e| MirrordError::OpenFile(filename.to_string(), e))?;
208
209 let body = serde_json::to_vec_pretty(cfg)?;
210
211 file.write_all(&body)
212 .and_then(|()| file.write_all(b"\n"))
213 .map_err(|e| MirrordError::WriteFile(filename.to_string(), e))
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use xianyun_core::config::latest::{
220 FilesystemConfig, MirrorConfig, MirrorIncomingConfig, MirrorNetworkConfig,
221 MirrorOutgoingConfig, MirrorTargetConfig,
222 };
223 use xianyun_core::orderedmap::OrderedMap;
224
225 fn read_config(path: &std::path::Path) -> serde_json::Value {
226 let data = std::fs::read_to_string(path).unwrap();
227 serde_json::from_str(&data).unwrap()
228 }
229
230 #[test]
232 fn test_generate_config_success() {
233 let tmp_dir = tempfile::tempdir().unwrap();
234 let config_file = tmp_dir.path().join("mirrord-config.json");
235
236 let reloader_config = ReloaderConfig {
237 reloader_type: "mirror".to_string(),
238 mirror: Some(MirrorConfig {
239 target: Some(MirrorTargetConfig {
240 namespace: "gitlab".to_string(),
241 kubernetes_type: "pod".to_string(),
242 name: "gitlab-gitaly-0".to_string(),
243 ..Default::default()
244 }),
245 filesystem: Some(FilesystemConfig {
246 read_write: vec!["^/home/git/repositories".to_string()],
247 read_only: vec![
248 "^/etc/gitaly".to_string(),
249 "^/srv/gitlab-shell".to_string(),
250 "^/etc/gitlab-secrets".to_string(),
251 ],
252 ..Default::default()
253 }),
254 ..Default::default()
255 }),
256 ..Default::default()
257 };
258
259 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
260 assert!(config_file.exists());
261
262 let cfg = read_config(&config_file);
263 assert_eq!(cfg["target"]["path"], "pod/gitlab-gitaly-0");
264 assert_eq!(cfg["target"]["namespace"], "gitlab");
265 assert_eq!(cfg["feature"]["network"]["incoming"]["mode"], "steal");
266 assert_eq!(
267 cfg["feature"]["network"]["outgoing"]["ignore_localhost"],
268 serde_json::Value::Null
269 );
270 assert_eq!(cfg["feature"]["fs"]["mode"], "write");
271 assert_eq!(
272 cfg["feature"]["fs"]["read_write"],
273 serde_json::json!(["^/home/git/repositories"])
274 );
275 assert_eq!(
276 cfg["feature"]["fs"]["read_only"],
277 serde_json::json!(["^/etc/gitaly", "^/srv/gitlab-shell", "^/etc/gitlab-secrets"])
278 );
279 assert_eq!(cfg["agent"]["namespace"], "gitlab");
280 }
281
282 #[test]
284 fn test_generate_config_without_filesystem() {
285 let tmp_dir = tempfile::tempdir().unwrap();
286 let config_file = tmp_dir.path().join("mirrord-config.json");
287
288 let reloader_config = ReloaderConfig {
289 reloader_type: "mirror".to_string(),
290 mirror: Some(MirrorConfig {
291 target: Some(MirrorTargetConfig {
292 namespace: "gitlab".to_string(),
293 kubernetes_type: "deployment".to_string(),
294 name: "gitlab-webservice-default-0".to_string(),
295 ..Default::default()
296 }),
297 ..Default::default()
298 }),
299 ..Default::default()
300 };
301
302 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
303
304 let cfg = read_config(&config_file);
305 assert_eq!(
306 cfg["target"]["path"],
307 "deployment/gitlab-webservice-default-0"
308 );
309 assert_eq!(cfg["target"]["namespace"], "gitlab");
310 assert_eq!(cfg["feature"]["fs"]["mode"], "write");
311 assert!(cfg["feature"]["fs"].get("read_write").is_none());
312 assert!(cfg["feature"]["fs"].get("read_only").is_none());
313 }
314
315 #[test]
317 fn test_generate_config_different_resource_types() {
318 let cases = [
319 ("pod", "gitlab-gitaly-0", "pod/gitlab-gitaly-0"),
320 (
321 "deployment",
322 "gitlab-webservice",
323 "deployment/gitlab-webservice",
324 ),
325 ("statefulset", "gitlab-gitaly", "statefulset/gitlab-gitaly"),
326 ("daemonset", "gitlab-runner", "daemonset/gitlab-runner"),
327 ];
328
329 for (kubernetes_type, resource_name, expected_path) in cases {
330 let tmp_dir = tempfile::tempdir().unwrap();
331 let config_file = tmp_dir.path().join("mirrord-config.json");
332
333 let reloader_config = ReloaderConfig {
334 reloader_type: "mirror".to_string(),
335 mirror: Some(MirrorConfig {
336 target: Some(MirrorTargetConfig {
337 namespace: "gitlab".to_string(),
338 kubernetes_type: kubernetes_type.to_string(),
339 name: resource_name.to_string(),
340 ..Default::default()
341 }),
342 ..Default::default()
343 }),
344 ..Default::default()
345 };
346
347 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
348
349 let cfg = read_config(&config_file);
350 assert_eq!(
351 cfg["target"]["path"], expected_path,
352 "type={kubernetes_type}"
353 );
354 }
355 }
356
357 #[test]
359 fn test_generate_config_target_container() {
360 let cases = [
361 (
362 "webservice",
363 "deployment/gitlab-webservice/container/webservice",
364 ),
365 ("", "deployment/gitlab-webservice"),
366 ];
367
368 for (container, expected_path) in cases {
369 let tmp_dir = tempfile::tempdir().unwrap();
370 let config_file = tmp_dir.path().join("mirrord-config.json");
371
372 let reloader_config = ReloaderConfig {
373 reloader_type: "mirror".to_string(),
374 mirror: Some(MirrorConfig {
375 target: Some(MirrorTargetConfig {
376 namespace: "gitlab".to_string(),
377 kubernetes_type: "deployment".to_string(),
378 name: "gitlab-webservice".to_string(),
379 container: container.to_string(),
380 }),
381 ..Default::default()
382 }),
383 ..Default::default()
384 };
385
386 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
387
388 let cfg = read_config(&config_file);
389 assert_eq!(
390 cfg["target"]["path"], expected_path,
391 "container={container}"
392 );
393 }
394 }
395
396 #[test]
398 fn test_generate_config_incoming_mode() {
399 let cases = [
400 (None, "steal"),
401 (Some("steal"), "steal"),
402 (Some("mirror"), "mirror"),
403 (Some("off"), "off"),
404 ];
405
406 for (incoming, expected_mode) in cases {
407 let tmp_dir = tempfile::tempdir().unwrap();
408 let config_file = tmp_dir.path().join("mirrord-config.json");
409
410 let reloader_config = ReloaderConfig {
411 reloader_type: "mirror".to_string(),
412 mirror: Some(MirrorConfig {
413 target: Some(MirrorTargetConfig {
414 namespace: "default".to_string(),
415 kubernetes_type: "deployment".to_string(),
416 name: "my-app".to_string(),
417 ..Default::default()
418 }),
419 network: Some(MirrorNetworkConfig {
420 incoming: incoming.map(|mode| MirrorIncomingConfig {
421 mode: mode.to_string(),
422 }),
423 ..Default::default()
424 }),
425 ..Default::default()
426 }),
427 ..Default::default()
428 };
429
430 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
431
432 let cfg = read_config(&config_file);
433 assert_eq!(cfg["feature"]["network"]["incoming"]["mode"], expected_mode);
434 }
435 }
436
437 #[test]
439 fn test_generate_config_omits_kubeconfig() {
440 let tmp_dir = tempfile::tempdir().unwrap();
441 let config_file = tmp_dir.path().join("mirrord-config.json");
442
443 let reloader_config = ReloaderConfig {
444 reloader_type: "mirror".to_string(),
445 mirror: Some(MirrorConfig {
446 target: Some(MirrorTargetConfig {
447 namespace: "gitlab".to_string(),
448 kubernetes_type: "pod".to_string(),
449 name: "gitlab-gitaly-0".to_string(),
450 ..Default::default()
451 }),
452 ..Default::default()
453 }),
454 ..Default::default()
455 };
456
457 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
458
459 let data = std::fs::read_to_string(&config_file).unwrap();
460 assert!(!data.contains("kubeconfig"));
461 }
462
463 #[test]
465 fn test_generate_config_not_mirror_repository() {
466 let tmp_dir = tempfile::tempdir().unwrap();
467 let config_file = tmp_dir.path().join("mirrord-config.json");
468
469 let reloader_config = ReloaderConfig {
470 reloader_type: "helm".to_string(),
471 ..Default::default()
472 };
473
474 let err = generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap_err();
475 assert!(matches!(err, MirrordError::NotMirrorReloader));
476 assert!(!config_file.exists());
477 }
478
479 #[test]
481 fn test_generate_config_missing_mirror_config() {
482 let tmp_dir = tempfile::tempdir().unwrap();
483 let config_file = tmp_dir.path().join("mirrord-config.json");
484
485 let reloader_config = ReloaderConfig {
486 reloader_type: "mirror".to_string(),
487 mirror: None,
488 ..Default::default()
489 };
490
491 let err = generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap_err();
492 assert!(matches!(err, MirrordError::MissingMirrorConfig));
493 assert!(!config_file.exists());
494 }
495
496 #[test]
498 fn test_generate_config_missing_target_config() {
499 let tmp_dir = tempfile::tempdir().unwrap();
500 let config_file = tmp_dir.path().join("mirrord-config.json");
501
502 let reloader_config = ReloaderConfig {
503 reloader_type: "mirror".to_string(),
504 mirror: Some(MirrorConfig {
505 target: None,
506 ..Default::default()
507 }),
508 ..Default::default()
509 };
510
511 let err = generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap_err();
512 assert!(matches!(err, MirrordError::MissingTargetConfig));
513 assert!(!config_file.exists());
514 }
515
516 #[test]
518 fn test_generate_config_invalid_path() {
519 let config_file = "/nonexistent/directory/mirrord-config.json";
520
521 let reloader_config = ReloaderConfig {
522 reloader_type: "mirror".to_string(),
523 mirror: Some(MirrorConfig {
524 target: Some(MirrorTargetConfig {
525 namespace: "gitlab".to_string(),
526 kubernetes_type: "pod".to_string(),
527 name: "gitlab-gitaly-0".to_string(),
528 ..Default::default()
529 }),
530 ..Default::default()
531 }),
532 ..Default::default()
533 };
534
535 let err = generate_config(&reloader_config, config_file).unwrap_err();
536 assert!(err.to_string().contains("opening mirrord config file"));
537 }
538
539 #[test]
541 fn test_generate_config_json_format() {
542 let tmp_dir = tempfile::tempdir().unwrap();
543 let config_file = tmp_dir.path().join("mirrord-config.json");
544
545 let reloader_config = ReloaderConfig {
546 reloader_type: "mirror".to_string(),
547 mirror: Some(MirrorConfig {
548 target: Some(MirrorTargetConfig {
549 namespace: "gitlab".to_string(),
550 kubernetes_type: "pod".to_string(),
551 name: "gitlab-gitaly-0".to_string(),
552 ..Default::default()
553 }),
554 filesystem: Some(FilesystemConfig {
555 read_write: vec!["^/home/git/repositories".to_string()],
556 read_only: vec!["^/etc/gitaly".to_string()],
557 ..Default::default()
558 }),
559 ..Default::default()
560 }),
561 ..Default::default()
562 };
563
564 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
565
566 let data = std::fs::read_to_string(&config_file).unwrap();
567 let _: serde_json::Value = serde_json::from_str(&data).unwrap();
568
569 assert!(data.contains('\n'));
570 assert!(data.contains(" "));
571 }
572
573 #[test]
575 fn test_generate_config_filesystem_mode() {
576 let cases = [
577 (None, "write"),
578 (Some("write"), "write"),
579 (Some("local"), "local"),
580 (Some("localwithoverrides"), "localwithoverrides"),
581 (Some("read"), "read"),
582 ];
583
584 for (mode, expected_mode) in cases {
585 let tmp_dir = tempfile::tempdir().unwrap();
586 let config_file = tmp_dir.path().join("mirrord-config.json");
587
588 let filesystem = mode.map(|m| FilesystemConfig {
589 mode: m.to_string(),
590 ..Default::default()
591 });
592
593 let reloader_config = ReloaderConfig {
594 reloader_type: "mirror".to_string(),
595 mirror: Some(MirrorConfig {
596 target: Some(MirrorTargetConfig {
597 namespace: "gitlab".to_string(),
598 kubernetes_type: "pod".to_string(),
599 name: "gitlab-webservice-0".to_string(),
600 ..Default::default()
601 }),
602 filesystem,
603 ..Default::default()
604 }),
605 ..Default::default()
606 };
607
608 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
609
610 let cfg = read_config(&config_file);
611 assert_eq!(cfg["feature"]["fs"]["mode"], expected_mode);
612 }
613 }
614
615 #[test]
617 fn test_generate_config_ignore_localhost_outgoing() {
618 let tmp_dir = tempfile::tempdir().unwrap();
619 let config_file = tmp_dir.path().join("mirrord-config.json");
620
621 let reloader_config = ReloaderConfig {
622 reloader_type: "mirror".to_string(),
623 mirror: Some(MirrorConfig {
624 target: Some(MirrorTargetConfig {
625 namespace: "gitlab".to_string(),
626 kubernetes_type: "pod".to_string(),
627 name: "gitlab-webservice-0".to_string(),
628 ..Default::default()
629 }),
630 network: Some(MirrorNetworkConfig {
631 outgoing: Some(MirrorOutgoingConfig {
632 ignore_localhost: true,
633 }),
634 ..Default::default()
635 }),
636 ..Default::default()
637 }),
638 ..Default::default()
639 };
640
641 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
642
643 let cfg = read_config(&config_file);
644 assert_eq!(
645 cfg["feature"]["network"]["outgoing"]["ignore_localhost"],
646 true
647 );
648 }
649
650 #[test]
652 fn test_generate_config_without_network() {
653 let tmp_dir = tempfile::tempdir().unwrap();
654 let config_file = tmp_dir.path().join("mirrord-config.json");
655
656 let reloader_config = ReloaderConfig {
657 reloader_type: "mirror".to_string(),
658 mirror: Some(MirrorConfig {
659 target: Some(MirrorTargetConfig {
660 namespace: "gitlab".to_string(),
661 kubernetes_type: "pod".to_string(),
662 name: "gitlab-webservice-0".to_string(),
663 ..Default::default()
664 }),
665 ..Default::default()
666 }),
667 ..Default::default()
668 };
669
670 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
671
672 let cfg = read_config(&config_file);
673 assert_eq!(
674 cfg["feature"]["network"]["outgoing"]["ignore_localhost"],
675 serde_json::Value::Null
676 );
677
678 let data = std::fs::read_to_string(&config_file).unwrap();
679 assert!(!data.contains("ignore_localhost"));
680 }
681
682 #[test]
684 fn test_generate_config_with_fs_mapping() {
685 let tmp_dir = tempfile::tempdir().unwrap();
686 let config_file = tmp_dir.path().join("mirrord-config.json");
687
688 let mut mapping: OrderedMap<String, String> = OrderedMap::new();
689 mapping.set(
690 r"^/home/(?<user>\S+)/dev/tomcat".to_string(),
691 "/etc/tomcat".to_string(),
692 );
693 mapping.set(
694 r"^/home/(?<user>\S+)/dev/config".to_string(),
695 "/mnt/configs".to_string(),
696 );
697
698 let reloader_config = ReloaderConfig {
699 reloader_type: "mirror".to_string(),
700 mirror: Some(MirrorConfig {
701 target: Some(MirrorTargetConfig {
702 namespace: "gitlab".to_string(),
703 kubernetes_type: "pod".to_string(),
704 name: "gitlab-gitaly-0".to_string(),
705 ..Default::default()
706 }),
707 filesystem: Some(FilesystemConfig {
708 mapping: Some(mapping),
709 ..Default::default()
710 }),
711 ..Default::default()
712 }),
713 ..Default::default()
714 };
715
716 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
717
718 let data = std::fs::read_to_string(&config_file).unwrap();
719 assert!(!data.contains(r"\u003c"));
721 assert!(!data.contains(r"\u003e"));
722 assert!(data.contains("(?<user>"));
723
724 let cfg = read_config(&config_file);
725 let got_mapping = &cfg["feature"]["fs"]["mapping"];
726 assert_eq!(got_mapping.as_object().unwrap().len(), 2);
727 assert_eq!(
728 got_mapping[r"^/home/(?<user>\S+)/dev/tomcat"],
729 "/etc/tomcat"
730 );
731 assert_eq!(
732 got_mapping[r"^/home/(?<user>\S+)/dev/config"],
733 "/mnt/configs"
734 );
735 }
736
737 #[test]
739 fn test_generate_config_without_fs_mapping() {
740 let tmp_dir = tempfile::tempdir().unwrap();
741 let config_file = tmp_dir.path().join("mirrord-config.json");
742
743 let reloader_config = ReloaderConfig {
744 reloader_type: "mirror".to_string(),
745 mirror: Some(MirrorConfig {
746 target: Some(MirrorTargetConfig {
747 namespace: "gitlab".to_string(),
748 kubernetes_type: "pod".to_string(),
749 name: "gitlab-gitaly-0".to_string(),
750 ..Default::default()
751 }),
752 filesystem: Some(FilesystemConfig {
753 read_write: vec!["^/home/git/repositories".to_string()],
754 ..Default::default()
755 }),
756 ..Default::default()
757 }),
758 ..Default::default()
759 };
760
761 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
762
763 let data = std::fs::read_to_string(&config_file).unwrap();
764 assert!(!data.contains("\"mapping\""));
765 }
766
767 #[test]
769 fn test_generate_config_file_permissions() {
770 use std::os::unix::fs::PermissionsExt;
771
772 let tmp_dir = tempfile::tempdir().unwrap();
773 let config_file = tmp_dir.path().join("mirrord-config.json");
774
775 let reloader_config = ReloaderConfig {
776 reloader_type: "mirror".to_string(),
777 mirror: Some(MirrorConfig {
778 target: Some(MirrorTargetConfig {
779 namespace: "gitlab".to_string(),
780 kubernetes_type: "pod".to_string(),
781 name: "gitlab-gitaly-0".to_string(),
782 ..Default::default()
783 }),
784 ..Default::default()
785 }),
786 ..Default::default()
787 };
788
789 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
790
791 let meta = std::fs::metadata(&config_file).unwrap();
792 assert_eq!(meta.permissions().mode() & 0o777, 0o600);
793 }
794
795 #[test]
797 fn example_generate_config() {
798 let tmp_dir = tempfile::tempdir().unwrap();
799 let config_file = tmp_dir.path().join("example-mirrord-config.json");
800
801 let reloader_config = ReloaderConfig {
802 reloader_type: "mirror".to_string(),
803 mirror: Some(MirrorConfig {
804 target: Some(MirrorTargetConfig {
805 namespace: "gitlab".to_string(),
806 kubernetes_type: "pod".to_string(),
807 name: "gitlab-gitaly-0".to_string(),
808 ..Default::default()
809 }),
810 filesystem: Some(FilesystemConfig {
811 read_write: vec!["^/home/git/repositories".to_string()],
812 read_only: vec![
813 "^/etc/gitaly".to_string(),
814 "^/srv/gitlab-shell".to_string(),
815 "^/etc/gitlab-secrets".to_string(),
816 ],
817 ..Default::default()
818 }),
819 ..Default::default()
820 }),
821 ..Default::default()
822 };
823
824 generate_config(&reloader_config, config_file.to_str().unwrap()).unwrap();
825
826 let data = std::fs::read_to_string(&config_file).unwrap();
827
828 let expected = r#"{
829 "target": {
830 "path": "pod/gitlab-gitaly-0",
831 "namespace": "gitlab"
832 },
833 "feature": {
834 "network": {
835 "incoming": {
836 "mode": "steal"
837 },
838 "outgoing": {}
839 },
840 "fs": {
841 "mode": "write",
842 "read_write": [
843 "^/home/git/repositories"
844 ],
845 "read_only": [
846 "^/etc/gitaly",
847 "^/srv/gitlab-shell",
848 "^/etc/gitlab-secrets"
849 ]
850 },
851 "env": {
852 "exclude": [
853 "GODEBUG"
854 ]
855 }
856 },
857 "agent": {
858 "namespace": "gitlab"
859 }
860}
861"#;
862
863 assert_eq!(data, expected);
864 }
865}