Skip to content

Commit 62d90d2

Browse files
authored
rework config management (#43)
* rework config management to allow better updates * update readme
1 parent 3cd6a1f commit 62d90d2

11 files changed

Lines changed: 490 additions & 127 deletions

File tree

Cargo.lock

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

README.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55

66
Compute Manager for managing HPC compute
77

8+
Table of contents
9+
=================
10+
11+
<!--ts-->
12+
* [Installation](#installation)
13+
* [Linux](#linux)
14+
* [Macos](#macos)
15+
* [Windows](#windows)
16+
* [Usage](#usage)
17+
* [Logging in](#logging-in)
18+
* [CLI](#cli)
19+
* [Terminal UI](#tui)
20+
* [coman.toml config file](#comantoml-config-file)
21+
* [Editing the config](#editing-the-config)
22+
* [Development](#development)
23+
* [Prerequisites](#prerequisites)
24+
* [Install binaries](#install-binaries)
25+
<!--te-->
826

927
## Installation
1028

@@ -188,6 +206,24 @@ Get the logs from a job
188206
coman cscs job log <id>
189207
```
190208

209+
You can also manage files with coman.
210+
List a remote directory:
211+
212+
```shell
213+
coman cscs file list /capstor/scratch/cscs/your_user
214+
```
215+
216+
Download a file:
217+
218+
```shell
219+
coman cscs file download /capstor/scratch/cscs/your_user/your_file /local/target_file
220+
```
221+
222+
Upload a file:
223+
224+
```shell
225+
coman cscs file upload /my/local/file /capstor/scratch/cscs/your_user/your_file
226+
```
191227

192228
### TUI
193229

@@ -199,7 +235,73 @@ The TUI should be pretty self-explanatory. It gives an overview of your jobs on
199235
refreshed every couple of seconds, lets you see the logs and all the other functionality of the CLI,
200236
just in an interactive way.
201237

238+
### coman.toml config file
239+
240+
The config file options look as follows:
241+
242+
```toml
243+
name = "myproject" # the name of the project, used to generate job names
244+
245+
[cscs]
246+
# check https://docs.cscs.ch/access/firecrest/#firecrest-deployment-on-alps for possible system and platform combinations
247+
current_system = "daint" # what system/cluster to execute commands on
248+
current_platform = "HPC" # what platform to execute commands on (valid: HPC, ML or CW)
249+
250+
251+
image = "ubuntu" # default docker image to use
252+
253+
command = ["sleep", "1"] # command to execute within the container, i.e. the job you want to run
254+
255+
# the sbatch script you want to execute
256+
# this gets templated with values specified in the {{}} and {% %} expressions (see https://keats.github.io/tera/docs/#templates for
257+
# more information on the template language). Note, this can also just be hardcoded without any template parameters.
258+
# Available parameters:
259+
# name: the name of the job
260+
# environment_file: the path to the edf environment toml file in the cluster
261+
# command: the command to run
262+
# container_workdir: the working directory inside the container
263+
sbatch_script_template = """
264+
#!/bin/bash
265+
#SBATCH --job-name={{name}}
266+
#SBATCH --ntasks=1
267+
#SBATCH --time=10:00
268+
srun {% if environment_file %}--environment={{environment_file}}{% endif %} {{command}}
269+
"""
270+
271+
# the edf environment toml file template
272+
# this gets templated with values specified in the {{}} and {% %} expressions (see https://keats.github.io/tera/docs/#templates for
273+
# more information on the template language). Note, this can also just be hardcoded without any template parameters.
274+
# Available parameters:
275+
# edf_image: the container image to use, in edf format
276+
# container_workdir: the working directory to use within the container
277+
# env: a dictionary of key/value pairs for environment variables to set in the container
278+
# mount: a dictionary of key/value pairs for folders to mount to the container, with key being the path in the cluster and value being the path in the container
279+
edf_file_template = """
280+
{% if edf_image %}image = "{{edf_image}}"{% endif %}
281+
mounts = [{% for source, target in mount %}"{{source}}:{{target}}",{% endfor %}]
282+
workdir = "{{container_workdir}}"
283+
284+
[env]
285+
{% for key, value in env %}
286+
{{key}} = "{{value}}"
287+
{% endfor %}
288+
"""
289+
290+
# set environment variables that should be passed to a job
291+
[cscs.env]
292+
ENV_VAR = "env_value"
293+
294+
```
295+
#### Editing the config
202296

297+
You can edit the config file directly or (safer) use coman commands to do so:
298+
```shell
299+
coman config get cscs.current_system
300+
```
301+
302+
```shell
303+
coman config set cscs.current_system "daint"
304+
```
203305

204306
## Development
205307

@@ -235,4 +337,4 @@ If you want to use cargo to install `coman`, make sure to remove any version of
235337

236338
```
237339
cargo install --path ./coman
238-
```
340+
```

coman/.config/config.toml

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
[cscs]
2-
current_system = "daint"
3-
current_platform = "HPC"
2+
# check https://docs.cscs.ch/access/firecrest/#firecrest-deployment-on-alps for possible system and platform combinations
3+
current_system = "daint" # what system/cluster to execute commands on
4+
current_platform = "HPC" # what platform to execute commands on (valid: HPC, ML or CW)
45

5-
image = "ubuntu"
66

7-
command = ["sleep", "1"]
7+
image = "ubuntu" # default docker image to use
88

9+
command = ["sleep", "1"] # command to execute within the container, i.e. the job you want to run
10+
11+
# the sbatch script you want to execute
12+
# this gets templated with values specified in the {{}} and {% %} expressions (see https://keats.github.io/tera/docs/#templates for
13+
# more information on the template language). Note, this can also just be hardcoded without any template parameters.
14+
# Available parameters:
15+
# name: the name of the job
16+
# environment_file: the path to the edf environment toml file in the cluster
17+
# command: the command to run
18+
# container_workdir: the working directory inside the container
919
sbatch_script_template = """
1020
#!/bin/bash
1121
#SBATCH --job-name={{name}}
@@ -14,6 +24,14 @@ sbatch_script_template = """
1424
srun {% if environment_file %}--environment={{environment_file}}{% endif %} {{command}}
1525
"""
1626

27+
# the edf environment toml file template
28+
# this gets templated with values specified in the {{}} and {% %} expressions (see https://keats.github.io/tera/docs/#templates for
29+
# more information on the template language). Note, this can also just be hardcoded without any template parameters.
30+
# Available parameters:
31+
# edf_image: the container image to use, in edf format
32+
# container_workdir: the working directory to use within the container
33+
# env: a dictionary of key/value pairs for environment variables to set in the container
34+
# mount: a dictionary of key/value pairs for folders to mount to the container, with key being the path in the cluster and value being the path in the container
1735
edf_file_template = """
1836
{% if edf_image %}image = "{{edf_image}}"{% endif %}
1937
mounts = [{% for source, target in mount %}"{{source}}:{{target}}",{% endfor %}]
@@ -25,8 +43,9 @@ workdir = "{{container_workdir}}"
2543
{% endfor %}
2644
"""
2745

46+
# set environment variables that should be passed to a job
2847
[cscs.env]
29-
48+
# env_var = "env_value"
3049

3150
[cscs.systems]
3251

@@ -35,3 +54,12 @@ architecture = ["arm64"]
3554

3655
[cscs.systems.eiger]
3756
architecture = ["amd64"]
57+
58+
[cscs.systems.bristen]
59+
architecture = ["amd64"]
60+
61+
[cscs.systems.clariden]
62+
architecture = ["amd64"]
63+
64+
[cscs.systems.santis]
65+
architecture = ["arm64"]

coman/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ chrono = "0.4.42"
7272
openssl = { version = "0.10.75", features = ["vendored"] }
7373
tui-realm-treeview = "3.0.0"
7474
aws-sdk-s3 = "1.115.0"
75+
toml_edit = "0.23.9"
7576

7677
[build-dependencies]
7778
anyhow = "1.0.90"

coman/src/cli.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use std::{error::Error, path::PathBuf};
22

33
use clap::{Args, Parser, Subcommand, builder::TypedValueParser};
4+
use color_eyre::Result;
45
use strum::VariantNames;
56

67
use crate::{
7-
config::{ComputePlatform, get_config_dir, get_data_dir, get_project_local_config_file},
8+
config::{ComputePlatform, Config, get_config_dir, get_data_dir, get_project_local_config_file},
89
cscs::api_client::client::{EdfSpec as EdfSpecEnum, ScriptSpec as ScriptSpecEnum},
910
util::types::DockerImageUrl,
1011
};
@@ -27,8 +28,37 @@ pub enum CliCommands {
2728
},
2829
#[clap(about = "Create a new project configuration file")]
2930
Init {
30-
#[clap(help = "Destination folder to create config in (default = current directory)")]
31+
#[clap(help = "destination folder to create config in (default = current directory)")]
3132
destination: Option<PathBuf>,
33+
#[clap(help = "project name to use")]
34+
name: Option<String>,
35+
},
36+
#[clap(about = "Manage configuration")]
37+
Config {
38+
#[command(subcommand)]
39+
command: ConfigCommands,
40+
},
41+
}
42+
43+
#[derive(Subcommand, Debug)]
44+
pub enum ConfigCommands {
45+
#[clap(about = "Set config values")]
46+
Set {
47+
#[clap(
48+
short,
49+
long,
50+
action,
51+
help = "whether to change the global config or the project local one"
52+
)]
53+
global: bool,
54+
#[clap(help = "Config key path, e.g. `cscs.current_system`")]
55+
key_path: String,
56+
#[clap(help = "Value to set", value_parser = parse_toml_value)]
57+
value: toml_edit::Value,
58+
},
59+
Get {
60+
#[clap(help = "Config key path, e.g. `cscs.current_system`")]
61+
key_path: String,
3262
},
3363
}
3464

@@ -251,6 +281,17 @@ Data directory: {data_dir_path}"
251281
)
252282
}
253283

284+
pub fn set_config<V: Into<toml_edit::Value>>(key_path: String, value: V, global: bool) -> Result<()> {
285+
let mut config = Config::new()?;
286+
config.set(&key_path, value, global)?;
287+
Ok(())
288+
}
289+
290+
pub fn get_config(key_path: String) -> Result<String> {
291+
let config = Config::new()?;
292+
config.get(&key_path)
293+
}
294+
254295
fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
255296
where
256297
T: std::str::FromStr,
@@ -275,3 +316,22 @@ where
275316
.ok_or_else(|| format!("invalid KEY:value: no `:` found in `{s}`"))?;
276317
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
277318
}
319+
320+
pub fn parse_toml_value(value_str: &str) -> Result<toml_edit::Value, toml_edit::TomlError> {
321+
match value_str.parse() {
322+
Ok(value) => Ok(value),
323+
Err(_) if is_bare_string(value_str) => Ok(value_str.into()),
324+
Err(err) => Err(err),
325+
}
326+
}
327+
fn is_bare_string(value_str: &str) -> bool {
328+
// leading whitespace isn't ignored when parsing TOML value expression, but
329+
// "\n[]" doesn't look like a bare string.
330+
let trimmed = value_str.trim_ascii().as_bytes();
331+
if let (Some(&first), Some(&last)) = (trimmed.first(), trimmed.last()) {
332+
// string, array, or table constructs?
333+
!matches!(first, b'"' | b'\'' | b'[' | b'{') && !matches!(last, b'"' | b'\'' | b']' | b'}')
334+
} else {
335+
true // empty or whitespace only
336+
}
337+
}

coman/src/components/status_bar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ impl StatusBar {
4242
last_updated: Instant::now(),
4343
current_status: None,
4444
status_clear_time: Duration::from_secs(10),
45-
current_platform: config.cscs.current_platform.to_string(),
46-
current_system: config.cscs.current_system,
45+
current_platform: config.values.cscs.current_platform.to_string(),
46+
current_system: config.values.cscs.current_system,
4747
}
4848
}
4949
}

0 commit comments

Comments
 (0)