Skip to content

Commit 90745cd

Browse files
committed
feat(v0.4.0): add typed read-only variables, improve state persistence & UI conditional rendering
1 parent 0ad5b99 commit 90745cd

File tree

17 files changed

+324
-177
lines changed

17 files changed

+324
-177
lines changed

README.md

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ Scenario configurations are defined in TOML files:
2424
```toml
2525
# Basic structure for a scenario configuration
2626
[credentials]
27-
username = "your-username"
28-
password = "your-password" # Optional, will use SSH agent if not provided
27+
username = "your-username" # will be added to the variables
28+
password = "your-password" # optional, will use SSH agent if not provided
2929

3030
[server]
31-
host = "your-server.example.com"
32-
port = "22"
31+
host = "your-server.example.com" # required
32+
port = "22" # optional, default is 22
3333

3434
[execute]
3535
# Define the execution order of tasks
@@ -39,21 +39,18 @@ steps = [
3939
{ task = "copy_config", on-fail = ["rollback_deployment"] } # With error recovery
4040
]
4141

42-
# Variables generated by the app
43-
[variables.special]
44-
timestamp = "%Y-%m-%dT%H%M%S%:z"
42+
# Variables that must be provided by the user
43+
[variables.required]
44+
app_archive = { type = "Path", label = "Application Archive" }
45+
deployment_env = { type = "String", label = "Environment" }
46+
timestamp = { type = "Timestamp", label = "Deployment Time", format = "%Y-%m-%dT%H%M%S%:z", read_only = true }
4547

4648
# Define variables to be used in commands and file paths
4749
[variables.defined]
4850
app_name = "myapp"
4951
app_version = "1.0.0"
5052
remote_app_path = "/opt/{app_name}"
5153

52-
# Variables that must be provided by the user
53-
[variables.required]
54-
"path:app_archive" = "Application Archive" # Special path handling
55-
deployment_env = "Environment" # Format: variable_name = "Label"
56-
5754
# Define tasks that can be referenced in execution steps
5855
[tasks.deploy_app]
5956
type = "RemoteSudo"
@@ -81,6 +78,14 @@ command = "rm -rf {remote_app_path}/*"
8178
error_message = "Failed to rollback deployment"
8279
```
8380

81+
### Variable Types
82+
83+
The application supports different variable types:
84+
85+
- **String**: Regular text input
86+
- **Path**: File path with special handling (automatically extracts basename)
87+
- **Timestamp**: Automatically generated timestamp with specified format
88+
8489
### Inheritance
8590

8691
You can split your configuration across multiple files and use inheritance:
@@ -94,6 +99,9 @@ username = "default-user"
9499
host = "default-host"
95100
port = "22"
96101

102+
[variables.required]
103+
timestamp = { type = "Timestamp", label = "Deployment Time", format = "%Y-%m-%d", read_only = true }
104+
97105
[variables.defined]
98106
app_name = "default-app"
99107
```
@@ -105,6 +113,9 @@ parent = "base.toml" # Will inherit and override from parent
105113
[credentials]
106114
username = "specific-user"
107115

116+
[variables.required]
117+
env_name = { type = "String", label = "Environment Name" }
118+
108119
[variables.defined]
109120
app_version = "1.0.0" # Adds new variable while keeping app_name from parent
110121
```

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cli"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2021"
55

66
[[bin]]

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "core"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2021"
55

66
[dependencies]

core/src/config.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::scenario::errors::ScenarioConfigError;
22
use serde::Deserialize;
3-
use std::collections::BTreeMap;
43
use std::{
54
collections::HashMap,
65
ops::{Deref, DerefMut},
@@ -215,15 +214,12 @@ pub struct VariablesConfig {
215214
#[serde(default)]
216215
pub required: RequiredVariablesConfig,
217216
#[serde(default)]
218-
pub special: SpecialVariablesConfig,
219-
#[serde(default)]
220217
pub defined: DefinedVariablesConfig,
221218
}
222219

223220
#[derive(Deserialize, Clone, Debug)]
224221
pub struct PartialVariablesConfig {
225222
pub required: Option<RequiredVariablesConfig>,
226-
pub special: Option<SpecialVariablesConfig>,
227223
pub defined: Option<DefinedVariablesConfig>,
228224
}
229225

@@ -249,14 +245,6 @@ impl PartialVariablesConfig {
249245

250246
PartialVariablesConfig {
251247
required: Some(merged_required),
252-
special: match (&self.special, &other.special) {
253-
(Some(self_special), Some(other_special)) => {
254-
Some(self_special.merge(other_special))
255-
}
256-
(None, Some(special)) => Some(special.clone()),
257-
(Some(special), None) => Some(special.clone()),
258-
(None, None) => None,
259-
},
260248
defined: Some(merged_defined),
261249
}
262250
}
@@ -268,17 +256,27 @@ impl TryFrom<PartialVariablesConfig> for VariablesConfig {
268256
fn try_from(partial: PartialVariablesConfig) -> Result<Self, Self::Error> {
269257
Ok(VariablesConfig {
270258
required: partial.required.unwrap_or_default(),
271-
special: partial.special.unwrap_or_default(),
272259
defined: partial.defined.unwrap_or_default(),
273260
})
274261
}
275262
}
276263

264+
#[derive(Deserialize, Clone, Debug)]
265+
pub struct RequiredVariableConfig {
266+
#[serde(flatten)]
267+
pub var_type: VariableTypeConfig,
268+
#[serde(default)]
269+
pub label: Option<String>,
270+
#[serde(default)]
271+
pub read_only: bool,
272+
}
273+
277274
#[derive(Deserialize, Clone, Debug, Default)]
278-
pub struct RequiredVariablesConfig(pub BTreeMap</* name */ String, /* label */ String>);
275+
pub struct RequiredVariablesConfig(pub HashMap<String, RequiredVariableConfig>);
279276

280277
impl Deref for RequiredVariablesConfig {
281-
type Target = BTreeMap<String, String>;
278+
type Target = HashMap<String, RequiredVariableConfig>;
279+
282280
fn deref(&self) -> &Self::Target {
283281
&self.0
284282
}
@@ -300,6 +298,16 @@ impl RequiredVariablesConfig {
300298
}
301299
}
302300

301+
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
302+
#[serde(tag = "type")]
303+
pub enum VariableTypeConfig {
304+
String,
305+
Path,
306+
Timestamp {
307+
format: String,
308+
},
309+
}
310+
303311
#[derive(Deserialize, Clone, Debug, Default)]
304312
pub struct SpecialVariablesConfig(HashMap<String, String>);
305313

core/src/scenario/variables.rs

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
pub mod required;
22

3+
34
use crate::{
4-
config::{SpecialVariablesConfig, VariablesConfig},
5+
config::VariablesConfig,
56
scenario::{
67
errors::PlaceholderResolutionError, utils::HasPlaceholders,
78
variables::required::RequiredVariables,
89
},
910
};
10-
use chrono::Local;
11-
use std::{collections::HashMap, ops::Deref, path::PathBuf, str::FromStr};
11+
use std::{collections::HashMap, ops::Deref};
1212

1313
#[derive(Clone, Debug)]
1414
pub struct Variables {
@@ -27,27 +27,10 @@ impl Default for Variables {
2727

2828
impl From<&VariablesConfig> for Variables {
2929
fn from(config: &VariablesConfig) -> Self {
30-
let mut variables_map = HashMap::<String, String>::new();
31-
variables_map.extend(config.defined.deref().clone());
32-
33-
for (key, value) in &variables_map.clone() {
34-
if key.starts_with("path:") {
35-
PathBuf::from_str(value.as_str())
36-
.ok()
37-
.and_then(|path| path.file_name().map(|file_name| file_name.to_owned()))
38-
.and_then(|file_name| file_name.to_str().map(|s| s.to_string()))
39-
.map(|file_name| {
40-
let basename_key = key.replace("path:", "basename:");
41-
variables_map.insert(basename_key, file_name.to_string());
42-
});
43-
}
44-
}
45-
let mut variables = Variables {
30+
Variables {
4631
required: RequiredVariables::from(&config.required),
47-
defined: variables_map,
48-
};
49-
variables._resolve_special_variables(&config.special);
50-
variables
32+
defined: config.defined.deref().clone(),
33+
}
5134
}
5235
}
5336

@@ -87,7 +70,7 @@ impl Variables {
8770
loop {
8871
let previous = output.clone();
8972

90-
for (key, value) in &variables {
73+
for (key, value) in dbg!(&variables) {
9174
let placeholder = format!("{{{}}}", key);
9275
output = output.replace(&placeholder, value);
9376
}
@@ -104,13 +87,6 @@ impl Variables {
10487
}
10588
}
10689

107-
fn _resolve_special_variables(&mut self, config: &SpecialVariablesConfig) {
108-
if let Some(timestamp_format) = &config.get("timestamp") {
109-
let timestamp: String = Local::now().format(timestamp_format).to_string();
110-
self.defined.insert("timestamp".to_string(), timestamp);
111-
}
112-
}
113-
11490
fn _resolve_placeholders(&self) -> Result<HashMap<String, String>, PlaceholderResolutionError> {
11591
let mut resolved_variables = self.defined.clone();
11692
self.required.iter().for_each(|(name, required_variable)| {

0 commit comments

Comments
 (0)