Skip to content

Commit 5a9f62f

Browse files
committed
allow specifying custom stdout/err paths for jobs
1 parent 3f32a68 commit 5a9f62f

6 files changed

Lines changed: 111 additions & 69 deletions

File tree

coman/src/cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub enum CliCommands {
3131
},
3232
}
3333

34+
#[allow(clippy::large_enum_variant)]
3435
#[derive(Subcommand, Debug)]
3536
pub enum CscsCommands {
3637
#[clap(about = "Log in to CSCS")]
@@ -87,6 +88,10 @@ pub enum CscsJobCommands {
8788
mount: Vec<(String, String)>,
8889
#[clap(short, long, help = "The docker image to use")]
8990
image: Option<DockerImageUrl>,
91+
#[clap(short, long, help = "Path where stdout of the job gets written to")]
92+
stdout: Option<PathBuf>,
93+
#[clap(short, long, help = "Path where stderr of the job gets written to")]
94+
stderr: Option<PathBuf>,
9095
#[clap(trailing_var_arg = true, help = "The command to run in the container")]
9196
command: Option<Vec<String>>,
9297
},

coman/src/cscs/api_client/client.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use eyre::eyre;
55
use firecrest_client::{
66
client::FirecrestClient,
77
compute_api::{
8-
cancel_compute_system_job, get_compute_system_job, get_compute_system_job_metadata, get_compute_system_jobs,
9-
post_compute_system_job,
8+
JobOptions, cancel_compute_system_job, get_compute_system_job, get_compute_system_job_metadata,
9+
get_compute_system_jobs, post_compute_system_job,
1010
},
1111
filesystem_api::{
1212
get_filesystem_ops_download, get_filesystem_ops_ls, get_filesystem_ops_stat, get_filesystem_ops_tail,
@@ -22,8 +22,21 @@ use crate::{
2222
config::{ComputePlatform, Config},
2323
cscs::api_client::types::{FileStat, Job, JobDetail, PathEntry, S3Upload, System, UserInfo},
2424
trace_dbg,
25+
util::types::DockerImageUrl,
2526
};
2627

28+
#[derive(Debug, Clone, Default)]
29+
pub struct JobStartOptions {
30+
pub script_file: Option<PathBuf>,
31+
pub image: Option<DockerImageUrl>,
32+
pub command: Option<Vec<String>>,
33+
pub stdout: Option<PathBuf>,
34+
pub stderr: Option<PathBuf>,
35+
pub container_workdir: Option<String>,
36+
pub env: Vec<(String, String)>,
37+
pub mount: Vec<(String, String)>,
38+
}
39+
2740
pub struct CscsApi {
2841
client: FirecrestClient,
2942
}
@@ -46,6 +59,7 @@ impl CscsApi {
4659
name: &str,
4760
script_path: PathBuf,
4861
envvars: HashMap<String, String>,
62+
options: JobStartOptions,
4963
) -> Result<()> {
5064
let workingdir = script_path.clone();
5165
let workingdir = workingdir.parent();
@@ -54,10 +68,14 @@ impl CscsApi {
5468
system_name,
5569
account,
5670
name,
57-
None,
58-
Some(script_path),
59-
workingdir.map(|p| p.to_path_buf()),
60-
envvars,
71+
JobOptions {
72+
script: None,
73+
script_path: Some(script_path),
74+
working_dir: workingdir.map(|p| p.to_path_buf()),
75+
envvars,
76+
stdout: options.stdout,
77+
stderr: options.stderr,
78+
},
6179
)
6280
.await?;
6381

@@ -231,10 +249,14 @@ mod tests {
231249
"",
232250
None,
233251
"",
234-
None,
235-
None,
236-
None,
237-
HashMap::new()
252+
JobOptions {
253+
script: None,
254+
script_path: None,
255+
working_dir: None,
256+
envvars: HashMap::new(),
257+
stdout: None,
258+
stderr: None
259+
}
238260
),
239261
Result<PostJobSubmissionResponse>
240262
))
@@ -243,7 +265,14 @@ mod tests {
243265
Result<PostJobSubmissionResponse>
244266
));
245267
let result = client
246-
.start_job("test", None, "test", PathBuf::from("/test"), HashMap::new())
268+
.start_job(
269+
"test",
270+
None,
271+
"test",
272+
PathBuf::from("/test"),
273+
HashMap::new(),
274+
JobStartOptions::default(),
275+
)
247276
.await;
248277
assert_ok!(result);
249278
}

coman/src/cscs/cli.rs

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ use tokio::{
1818
use crate::{
1919
config::ComputePlatform,
2020
cscs::{
21-
api_client::types::JobStatus,
21+
api_client::{client::JobStartOptions, types::JobStatus},
2222
handlers::{
2323
cscs_file_download, cscs_file_list, cscs_file_upload, cscs_job_cancel, cscs_job_details, cscs_job_list,
2424
cscs_job_log, cscs_login, cscs_start_job, cscs_system_list, cscs_system_set,
2525
},
2626
},
27-
util::types::DockerImageUrl,
2827
};
2928

3029
pub(crate) async fn cli_cscs_login() -> Result<()> {
@@ -103,30 +102,12 @@ pub(crate) async fn cli_cscs_job_log(
103102
#[allow(clippy::too_many_arguments)]
104103
pub(crate) async fn cli_cscs_job_start(
105104
name: Option<String>,
106-
script_file: Option<PathBuf>,
107-
image: Option<DockerImageUrl>,
108-
command: Option<Vec<String>>,
109-
workdir: Option<String>,
110-
env: Vec<(String, String)>,
111-
mount: Vec<(String, String)>,
105+
options: JobStartOptions,
112106
system: Option<String>,
113107
platform: Option<ComputePlatform>,
114108
account: Option<String>,
115109
) -> Result<()> {
116-
match cscs_start_job(
117-
name,
118-
script_file,
119-
image,
120-
command,
121-
workdir,
122-
env,
123-
mount,
124-
system,
125-
platform,
126-
account,
127-
)
128-
.await
129-
{
110+
match cscs_start_job(name, options, system, platform, account).await {
130111
Ok(_) => {
131112
println!("Job started");
132113
Ok(())

coman/src/cscs/handlers.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,15 @@ use crate::{
1111
config::{ComputePlatform, Config},
1212
cscs::{
1313
api_client::{
14-
client::CscsApi,
14+
client::{CscsApi, JobStartOptions},
1515
types::{FileStat, FileSystemType, Job, JobDetail, PathEntry, PathType, S3Upload, System, UserInfo},
1616
},
1717
oauth2::{
1818
CLIENT_ID_SECRET_NAME, CLIENT_SECRET_SECRET_NAME, client_credentials_login, finish_cscs_device_login,
1919
start_cscs_device_login,
2020
},
2121
},
22-
util::{
23-
keyring::{Secret, get_secret, store_secret},
24-
types::DockerImageUrl,
25-
},
22+
util::keyring::{Secret, get_secret, store_secret},
2623
};
2724

2825
const CSCS_MAX_DIRECT_SIZE: usize = 5242880;
@@ -169,12 +166,7 @@ pub async fn cscs_job_cancel(job_id: i64, system: Option<String>, platform: Opti
169166
#[allow(clippy::too_many_arguments)]
170167
pub async fn cscs_start_job(
171168
name: Option<String>,
172-
script_file: Option<PathBuf>,
173-
image: Option<DockerImageUrl>,
174-
command: Option<Vec<String>>,
175-
container_workdir: Option<String>,
176-
env: Vec<(String, String)>,
177-
mount: Vec<(String, String)>,
169+
options: JobStartOptions,
178170
system: Option<String>,
179171
platform: Option<ComputePlatform>,
180172
account: Option<String>,
@@ -202,12 +194,15 @@ pub async fn cscs_start_job(
202194
return Err(eyre!("couldn't get system description for {}", current_system));
203195
}
204196
};
205-
let container_workdir = container_workdir.unwrap_or(config.cscs.workdir.unwrap_or("/scratch".to_owned()));
197+
let container_workdir = options
198+
.container_workdir
199+
.clone()
200+
.unwrap_or(config.cscs.workdir.unwrap_or("/scratch".to_owned()));
206201
let base_path = scratch.join(user_info.name.clone()).join(&job_name);
207202

208203
let mut envvars = config.cscs.env.clone();
209-
envvars.extend(env);
210-
let mut mount: HashMap<String, String> = mount.into_iter().collect();
204+
envvars.extend(options.env.clone());
205+
let mut mount: HashMap<String, String> = options.mount.clone().into_iter().collect();
211206
mount.entry("${SCRATCH}".to_owned()).or_insert("/scratch".to_owned());
212207

213208
let mut tera = tera::Tera::default();
@@ -216,7 +211,7 @@ pub async fn cscs_start_job(
216211
let environment_template = config.cscs.edf_file_template;
217212
tera.add_raw_template("environment.toml", &environment_template)?;
218213

219-
let docker_image = image.unwrap_or(config.cscs.image.try_into()?);
214+
let docker_image = options.image.clone().unwrap_or(config.cscs.image.try_into()?);
220215
let meta = docker_image.inspect().await?;
221216
if let Some(system_info) = config.cscs.systems.get(current_system) {
222217
let mut compatible = false;
@@ -255,13 +250,18 @@ pub async fn cscs_start_job(
255250

256251
// upload script
257252
let script_path = base_path.join("script.sh");
258-
let script_template = script_file
253+
let script_template = options
254+
.script_file
255+
.clone()
259256
.map(std::fs::read_to_string)
260257
.unwrap_or(Ok(config.cscs.sbatch_script_template))?;
261258
tera.add_raw_template("script.sh", &script_template)?;
262259
let mut context = tera::Context::new();
263260
context.insert("name", &job_name);
264-
context.insert("command", &command.unwrap_or(config.cscs.command).join(" "));
261+
context.insert(
262+
"command",
263+
&options.command.clone().unwrap_or(config.cscs.command).join(" "),
264+
);
265265
context.insert("environment_file", &environment_path);
266266
context.insert("container_workdir", &container_workdir);
267267
let script = tera.render("script.sh", &context)?;
@@ -271,7 +271,7 @@ pub async fn cscs_start_job(
271271

272272
// start job
273273
api_client
274-
.start_job(current_system, account, &job_name, script_path, envvars)
274+
.start_job(current_system, account, &job_name, script_path, envvars, options)
275275
.await?;
276276
Ok(())
277277
}

coman/src/main.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::{
2424
},
2525
config::Config,
2626
cscs::{
27+
api_client::client::JobStartOptions,
2728
cli::{
2829
cli_cscs_file_download, cli_cscs_file_list, cli_cscs_file_upload, cli_cscs_job_cancel, cli_cscs_job_detail,
2930
cli_cscs_job_list, cli_cscs_job_log, cli_cscs_job_start, cli_cscs_login, cli_cscs_set_system,
@@ -78,15 +79,21 @@ async fn main() -> Result<()> {
7879
workdir,
7980
env,
8081
mount,
82+
stdout,
83+
stderr,
8184
} => {
8285
cli_cscs_job_start(
8386
name,
84-
script_file,
85-
image,
86-
command,
87-
workdir,
88-
env,
89-
mount,
87+
JobStartOptions {
88+
script_file,
89+
image,
90+
command,
91+
container_workdir: workdir,
92+
env,
93+
mount,
94+
stdout,
95+
stderr,
96+
},
9097
system,
9198
platform,
9299
account,

firecrest_client/src/compute_api.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{collections::HashMap, path::PathBuf};
22

3-
use eyre::{Result, eyre};
3+
use eyre::{Result, WrapErr, eyre};
44
use serde_json::json;
55

66
use crate::{
@@ -11,34 +11,54 @@ use crate::{
1111
},
1212
};
1313

14-
#[allow(clippy::too_many_arguments)]
15-
pub async fn post_compute_system_job(
14+
#[derive(Debug, Clone, Default)]
15+
pub struct JobOptions<'a> {
16+
pub script: Option<&'a str>,
17+
pub script_path: Option<PathBuf>,
18+
pub working_dir: Option<PathBuf>,
19+
pub envvars: HashMap<String, String>,
20+
pub stdout: Option<PathBuf>,
21+
pub stderr: Option<PathBuf>,
22+
}
23+
24+
pub async fn post_compute_system_job<'a>(
1625
client: &FirecrestClient,
1726
system_name: &str,
1827
account: Option<String>,
1928
name: &str,
20-
script: Option<&str>,
21-
script_path: Option<PathBuf>,
22-
working_dir: Option<PathBuf>,
23-
envvars: HashMap<String, String>,
29+
options: JobOptions<'a>,
2430
) -> Result<PostJobSubmissionResponse> {
25-
if script.is_none() && script_path.is_none() {
31+
if options.script.is_none() && options.script_path.is_none() {
2632
return Err(eyre!("either script or script_path must be set"));
2733
}
2834
let body = PostJobSubmitRequest {
2935
job: JobDescriptionModel {
3036
name: Some(name.to_string()),
31-
script: script.map(|s| s.to_owned()),
32-
script_path: script_path
37+
script: options.script.map(|s| s.to_owned()),
38+
script_path: options
39+
.script_path
3340
.map(|s| s.into_os_string().into_string())
3441
.transpose()
3542
.map_err(|_| eyre!("couldn't convert script path"))?,
36-
working_directory: working_dir
43+
working_directory: options
44+
.working_dir
3745
.map(|s| s.into_os_string().into_string())
3846
.transpose()
3947
.map_err(|_| eyre!("couldn't convert working dir path"))?
4048
.unwrap_or("/".to_owned()),
41-
env: Some(JobDescriptionModelEnv::Object(json!(envvars))),
49+
env: Some(JobDescriptionModelEnv::Object(json!(options.envvars))),
50+
standard_output: options
51+
.stdout
52+
.map(|p| p.into_os_string().into_string())
53+
.transpose()
54+
.map_err(|e| eyre!("Path:{}", e.display()))
55+
.wrap_err("stdout is not a valid path")?,
56+
standard_error: options
57+
.stderr
58+
.map(|p| p.into_os_string().into_string())
59+
.transpose()
60+
.map_err(|e| eyre!("Path:{}", e.display()))
61+
.wrap_err("stderr is not a valid path")?,
4262
account,
4363
..Default::default()
4464
},

0 commit comments

Comments
 (0)