Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 43 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions examples/volume-v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ data:
[url "git@github.com:"]
insteadOf = gh:


cert:
# generate runs the command in an isolated one-shot container and captures output
generate: |
openssl genrsa -out cert.key 2048
cat cert.key
image: docker.io/alpine/openssl


# files included from paths are non-trivial at the moment for config originating from git
# rather trivial for local files, but I'd rather provide a consistent implementation
#local-file:
# path: ./local.yaml

mounts:
~/certs/my.key: cert
~/placeholder: empty-dir
/work: work
~/.gitconfig: git-config
Expand Down
1 change: 1 addition & 0 deletions src/api/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl<'a> ConfigApi<'a> {
RoozVolume::workspace_config_read(workspace_key, "/etc/rooz").to_mount(None),
]),
None,
None,
)
.await?;
Ok(result.data.to_string())
Expand Down
47 changes: 38 additions & 9 deletions src/api/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ use bollard::{
};

use crate::model::types::{TargetDir, VolumeFilesSpec};
use bollard_stubs::query_parameters::UploadToContainerOptions;
use futures::{StreamExt, future};
use bollard_stubs::models::MountTypeEnum;
use bollard_stubs::query_parameters::{UploadToContainerOptions, WaitContainerOptions};
use futures::{StreamExt, TryStreamExt, future};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{collections::HashMap, time::Duration};
use tokio::time::{sleep, timeout};
Expand Down Expand Up @@ -487,19 +488,29 @@ timeout $TIMEOUT sh -c 'read _ < /tmp/exec_start' || exit 1

echo "Exec session started"
read _ < /tmp/exec_end
echo "Exec session ended"
exit 0"#;
EXEC_EXIT_CODE=$(cat /tmp/exec_exit)
echo "Exec session ended: $EXEC_EXIT_CODE"
exit $EXEC_EXIT_CODE"#;

let epv = inject(&wait_for_exec, "entrypoint.sh");
let entrypoint = epv.iter().map(String::as_str).collect();
let work_dir = "/tmp/one-shot";
let id = self
.create(RunSpec {
reason: name,
image: image.unwrap_or(constants::DEFAULT_IMAGE),
container_name: &id::random_suffix("one-shot"),
command: Some(entrypoint),
mounts,
mounts: mounts.map(|mut m| {
m.extend_from_slice(&[Mount {
target: Some(work_dir.into()),
typ: Some(MountTypeEnum::TMPFS),
..Default::default()
}]);
m
}),
uid: uid.unwrap_or(constants::ROOT_UID),
work_dir: Some(work_dir),
..Default::default()
})
.await
Expand Down Expand Up @@ -540,14 +551,13 @@ exit 0"#;
.await;
});
}

Ok(id)
}

fn format_cmd(command: String) -> Vec<String> {
let cmd = format!(
r#"#!/bin/sh
trap 'echo end > /tmp/exec_end' EXIT
trap 'echo $? > /tmp/exec_exit; echo end > /tmp/exec_end' EXIT
echo start > /tmp/exec_start
{}
"#,
Expand All @@ -562,11 +572,30 @@ echo start > /tmp/exec_start
command: String,
mounts: Option<Vec<Mount>>,
uid: Option<&str>,
image: Option<&str>,
) -> Result<OneShotResult, AnyError> {
let id = self.make_one_shot(name, mounts, uid, None).await?;
let id = self.make_one_shot(name, mounts, uid, image).await?;
let cmd = Self::format_cmd(command);
let cmd = cmd.iter().map(|x| x.as_str()).collect::<Vec<_>>();
let data = self.exec.output(name, &id.clone(), uid, Some(cmd)).await?;

let id_clone = id.clone();
let client = self.client.clone();

let wait_handle = tokio::spawn(async move {
client
.wait_container(&id_clone, None::<WaitContainerOptions>)
.try_collect::<Vec<_>>()
.await
});

let data = self.exec.output(name, &id, uid, Some(cmd)).await?;
let _ = match wait_handle.await? {
Ok(r) => r,
Err(Error::DockerContainerWaitError { code, .. }) => {
return Err(format!("One-shot cmd failed (exit {}): \n{}", code, data).into());
}
Err(e) => return Err(e.into()),
};

Ok(OneShotResult { data })
}
Expand Down
1 change: 1 addition & 0 deletions src/api/system_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl<'a> Api<'a> {
RoozVolume::system_config_read("/tmp/sys").to_mount(None),
]),
None,
None,
)
.await?;

Expand Down
79 changes: 52 additions & 27 deletions src/api/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::path::Path;

use crate::config::config::{DataEntry, DataExt, DataValue, MountSource};
use crate::model::types::{
DataEntryKey, DataEntryVolumeSpec, FileSpec, TargetDir, TargetFile, TargetPath, UserFile,
VolumeFilesSpec, VolumeName, VolumeSpec,
ContentGenerator, DataEntryKey, DataEntryVolumeSpec, FileSpec, OneShotResult, TargetDir,
TargetFile, TargetPath, UserFile, VolumeFilesSpec, VolumeName, VolumeSpec,
};
use crate::util::id;
use crate::util::labels::DATA_ROLE;
Expand Down Expand Up @@ -255,7 +255,7 @@ impl<'a> VolumeApi<'a> {
let expanded_target = Self::expand_home(target.as_str().to_string(), home_dir);
let (real_target, maybe_file) = match source_entry.data.clone() {
DataEntry::File {
content,
generator,
executable,
..
} => {
Expand All @@ -273,7 +273,7 @@ impl<'a> VolumeApi<'a> {
Some(FileSpec {
target_file: TargetFile(shadow_file.to_string_lossy().into_owned()),
user_file: UserFile(expanded_target),
content: content.to_string(),
generator,
executable,
}),
)
Expand Down Expand Up @@ -435,30 +435,55 @@ impl<'a> VolumeApi<'a> {
mount: Mount,
uid: Option<i32>,
) -> Result<(), AnyError> {
let mut cmd = spec
.files
.iter()
.map(|f| {
let parent_dir = Path::new(f.target_file.as_str())
.parent()
.unwrap()
.to_string_lossy()
.into_owned();

format!(
"mkdir -p {} && echo '{}' | base64 -d > {}{}",
parent_dir,
general_purpose::STANDARD.encode(f.content.trim()),
f.target_file.as_str(),
if f.executable {
format!(" && chmod +x {}", f.target_file.as_str())
} else {
"".to_string()
let mut cmds = Vec::new();
for f in &spec.files {
let parent_dir = Path::new(f.target_file.as_str())
.parent()
.unwrap()
.to_string_lossy()
.into_owned();

let content = match &f.generator {
ContentGenerator::Inline(content) => content.to_string(),
ContentGenerator::Script { script, image } => {
match self
.container
.one_shot_output(
&format!("generate file: {}", f.user_file.as_str()),
script.to_string(),
None,
None,
image.as_deref(),
)
.await
{
Ok(OneShotResult { data }) => data,
Err(e) => {
return Err(format!(
"Failed generating file: {}\n{}",
f.user_file.as_str(),
e
)
.into());
}
}
)
})
.collect::<Vec<_>>()
.join(" && ".into());
}
};

cmds.push(format!(
"mkdir -p {} && echo '{}' | base64 -d > {}{}",
parent_dir,
general_purpose::STANDARD.encode(content.trim()),
f.target_file.as_str(),
if f.executable {
format!(" && chmod +x {}", f.target_file.as_str())
} else {
"".to_string()
}
));
}

let mut cmd = cmds.join(" && ".into());

if let Some(uid) = uid
&& uid != constants::ROOT_UID_INT
Expand Down
Loading
Loading