Skip to content

Commit 065602d

Browse files
authored
add: generated files (volume population) (#113)
1 parent 6fccd0f commit 065602d

9 files changed

Lines changed: 212 additions & 70 deletions

File tree

Cargo.lock

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

examples/volume-v2.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,22 @@ data:
77
[url "git@github.com:"]
88
insteadOf = gh:
99
10+
11+
cert:
12+
# generate runs the command in an isolated one-shot container and captures output
13+
generate: |
14+
openssl genrsa -out cert.key 2048
15+
cat cert.key
16+
image: docker.io/alpine/openssl
17+
18+
1019
# files included from paths are non-trivial at the moment for config originating from git
1120
# rather trivial for local files, but I'd rather provide a consistent implementation
1221
#local-file:
1322
# path: ./local.yaml
1423

1524
mounts:
25+
~/certs/my.key: cert
1626
~/placeholder: empty-dir
1727
/work: work
1828
~/.gitconfig: git-config

src/api/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl<'a> ConfigApi<'a> {
6060
RoozVolume::workspace_config_read(workspace_key, "/etc/rooz").to_mount(None),
6161
]),
6262
None,
63+
None,
6364
)
6465
.await?;
6566
Ok(result.data.to_string())

src/api/container.rs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ use bollard::{
2626
};
2727

2828
use crate::model::types::{TargetDir, VolumeFilesSpec};
29-
use bollard_stubs::query_parameters::UploadToContainerOptions;
30-
use futures::{StreamExt, future};
29+
use bollard_stubs::models::MountTypeEnum;
30+
use bollard_stubs::query_parameters::{UploadToContainerOptions, WaitContainerOptions};
31+
use futures::{StreamExt, TryStreamExt, future};
3132
use std::time::{SystemTime, UNIX_EPOCH};
3233
use std::{collections::HashMap, time::Duration};
3334
use tokio::time::{sleep, timeout};
@@ -487,19 +488,29 @@ timeout $TIMEOUT sh -c 'read _ < /tmp/exec_start' || exit 1
487488
488489
echo "Exec session started"
489490
read _ < /tmp/exec_end
490-
echo "Exec session ended"
491-
exit 0"#;
491+
EXEC_EXIT_CODE=$(cat /tmp/exec_exit)
492+
echo "Exec session ended: $EXEC_EXIT_CODE"
493+
exit $EXEC_EXIT_CODE"#;
492494

493495
let epv = inject(&wait_for_exec, "entrypoint.sh");
494496
let entrypoint = epv.iter().map(String::as_str).collect();
497+
let work_dir = "/tmp/one-shot";
495498
let id = self
496499
.create(RunSpec {
497500
reason: name,
498501
image: image.unwrap_or(constants::DEFAULT_IMAGE),
499502
container_name: &id::random_suffix("one-shot"),
500503
command: Some(entrypoint),
501-
mounts,
504+
mounts: mounts.map(|mut m| {
505+
m.extend_from_slice(&[Mount {
506+
target: Some(work_dir.into()),
507+
typ: Some(MountTypeEnum::TMPFS),
508+
..Default::default()
509+
}]);
510+
m
511+
}),
502512
uid: uid.unwrap_or(constants::ROOT_UID),
513+
work_dir: Some(work_dir),
503514
..Default::default()
504515
})
505516
.await
@@ -540,14 +551,13 @@ exit 0"#;
540551
.await;
541552
});
542553
}
543-
544554
Ok(id)
545555
}
546556

547557
fn format_cmd(command: String) -> Vec<String> {
548558
let cmd = format!(
549559
r#"#!/bin/sh
550-
trap 'echo end > /tmp/exec_end' EXIT
560+
trap 'echo $? > /tmp/exec_exit; echo end > /tmp/exec_end' EXIT
551561
echo start > /tmp/exec_start
552562
{}
553563
"#,
@@ -562,11 +572,30 @@ echo start > /tmp/exec_start
562572
command: String,
563573
mounts: Option<Vec<Mount>>,
564574
uid: Option<&str>,
575+
image: Option<&str>,
565576
) -> Result<OneShotResult, AnyError> {
566-
let id = self.make_one_shot(name, mounts, uid, None).await?;
577+
let id = self.make_one_shot(name, mounts, uid, image).await?;
567578
let cmd = Self::format_cmd(command);
568579
let cmd = cmd.iter().map(|x| x.as_str()).collect::<Vec<_>>();
569-
let data = self.exec.output(name, &id.clone(), uid, Some(cmd)).await?;
580+
581+
let id_clone = id.clone();
582+
let client = self.client.clone();
583+
584+
let wait_handle = tokio::spawn(async move {
585+
client
586+
.wait_container(&id_clone, None::<WaitContainerOptions>)
587+
.try_collect::<Vec<_>>()
588+
.await
589+
});
590+
591+
let data = self.exec.output(name, &id, uid, Some(cmd)).await?;
592+
let _ = match wait_handle.await? {
593+
Ok(r) => r,
594+
Err(Error::DockerContainerWaitError { code, .. }) => {
595+
return Err(format!("One-shot cmd failed (exit {}): \n{}", code, data).into());
596+
}
597+
Err(e) => return Err(e.into()),
598+
};
570599

571600
Ok(OneShotResult { data })
572601
}

src/api/system_config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ impl<'a> Api<'a> {
1818
RoozVolume::system_config_read("/tmp/sys").to_mount(None),
1919
]),
2020
None,
21+
None,
2122
)
2223
.await?;
2324

src/api/volume.rs

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::path::Path;
33

44
use crate::config::config::{DataEntry, DataExt, DataValue, MountSource};
55
use crate::model::types::{
6-
DataEntryKey, DataEntryVolumeSpec, FileSpec, TargetDir, TargetFile, TargetPath, UserFile,
7-
VolumeFilesSpec, VolumeName, VolumeSpec,
6+
ContentGenerator, DataEntryKey, DataEntryVolumeSpec, FileSpec, OneShotResult, TargetDir,
7+
TargetFile, TargetPath, UserFile, VolumeFilesSpec, VolumeName, VolumeSpec,
88
};
99
use crate::util::id;
1010
use crate::util::labels::DATA_ROLE;
@@ -255,7 +255,7 @@ impl<'a> VolumeApi<'a> {
255255
let expanded_target = Self::expand_home(target.as_str().to_string(), home_dir);
256256
let (real_target, maybe_file) = match source_entry.data.clone() {
257257
DataEntry::File {
258-
content,
258+
generator,
259259
executable,
260260
..
261261
} => {
@@ -273,7 +273,7 @@ impl<'a> VolumeApi<'a> {
273273
Some(FileSpec {
274274
target_file: TargetFile(shadow_file.to_string_lossy().into_owned()),
275275
user_file: UserFile(expanded_target),
276-
content: content.to_string(),
276+
generator,
277277
executable,
278278
}),
279279
)
@@ -435,30 +435,55 @@ impl<'a> VolumeApi<'a> {
435435
mount: Mount,
436436
uid: Option<i32>,
437437
) -> Result<(), AnyError> {
438-
let mut cmd = spec
439-
.files
440-
.iter()
441-
.map(|f| {
442-
let parent_dir = Path::new(f.target_file.as_str())
443-
.parent()
444-
.unwrap()
445-
.to_string_lossy()
446-
.into_owned();
447-
448-
format!(
449-
"mkdir -p {} && echo '{}' | base64 -d > {}{}",
450-
parent_dir,
451-
general_purpose::STANDARD.encode(f.content.trim()),
452-
f.target_file.as_str(),
453-
if f.executable {
454-
format!(" && chmod +x {}", f.target_file.as_str())
455-
} else {
456-
"".to_string()
438+
let mut cmds = Vec::new();
439+
for f in &spec.files {
440+
let parent_dir = Path::new(f.target_file.as_str())
441+
.parent()
442+
.unwrap()
443+
.to_string_lossy()
444+
.into_owned();
445+
446+
let content = match &f.generator {
447+
ContentGenerator::Inline(content) => content.to_string(),
448+
ContentGenerator::Script { script, image } => {
449+
match self
450+
.container
451+
.one_shot_output(
452+
&format!("generate file: {}", f.user_file.as_str()),
453+
script.to_string(),
454+
None,
455+
None,
456+
image.as_deref(),
457+
)
458+
.await
459+
{
460+
Ok(OneShotResult { data }) => data,
461+
Err(e) => {
462+
return Err(format!(
463+
"Failed generating file: {}\n{}",
464+
f.user_file.as_str(),
465+
e
466+
)
467+
.into());
468+
}
457469
}
458-
)
459-
})
460-
.collect::<Vec<_>>()
461-
.join(" && ".into());
470+
}
471+
};
472+
473+
cmds.push(format!(
474+
"mkdir -p {} && echo '{}' | base64 -d > {}{}",
475+
parent_dir,
476+
general_purpose::STANDARD.encode(content.trim()),
477+
f.target_file.as_str(),
478+
if f.executable {
479+
format!(" && chmod +x {}", f.target_file.as_str())
480+
} else {
481+
"".to_string()
482+
}
483+
));
484+
}
485+
486+
let mut cmd = cmds.join(" && ".into());
462487

463488
if let Some(uid) = uid
464489
&& uid != constants::ROOT_UID_INT

0 commit comments

Comments
 (0)