Skip to content

Commit 7703bc4

Browse files
committed
feat(sdk): add create-from-JSON-file example (Task 11)
- Create examples/sdk/create_from_json_file.rs demonstrating: - validate() before create to catch config errors early - create_environment_from_file() loading a JSON config from disk - show() to inspect the result - error handling for missing file (ConfigLoadError::FileNotFound) - purge() for cleanup - Uses tempfile to write a self-contained config at runtime - Register sdk_create_from_json_file in Cargo.toml - Add sdk_create_from_json_file to CI workflow test-sdk-examples.yml - Mark Task 11 as Done in phase-2-improvements.md
1 parent 73785d4 commit 7703bc4

4 files changed

Lines changed: 142 additions & 1 deletion

File tree

.github/workflows/test-sdk-examples.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ name: SDK Examples Tests
1212
# validate, destroy, purge). No infrastructure required.
1313
# - sdk_error_handling — local-only operations (create, show, list, purge).
1414
# Demonstrates error matching and SdkError.
15+
# - sdk_create_from_json_file — validate → create_from_file → show → purge.
16+
# Uses a temp file; no infrastructure required.
1517
#
1618
# INCOMPATIBLE EXAMPLES (local only, not run here):
1719
# - sdk_full_deployment — uses configure, release, run, and test which require
@@ -64,6 +66,10 @@ jobs:
6466
cargo run --example sdk_error_handling
6567
echo "✅ SDK error handling example completed at $(date)"
6668
69+
echo "🚀 Running SDK create-from-JSON-file example at $(date)"
70+
cargo run --example sdk_create_from_json_file
71+
echo "✅ SDK create-from-JSON-file example completed at $(date)"
72+
6773
- name: Debug information (on failure)
6874
if: failure()
6975
run: |

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ path = "examples/sdk/full_deployment.rs"
5252
name = "sdk_error_handling"
5353
path = "examples/sdk/error_handling.rs"
5454

55+
[[example]]
56+
name = "sdk_create_from_json_file"
57+
path = "examples/sdk/create_from_json_file.rs"
58+
5559
[dependencies]
5660
tokio = { version = "1.0", features = [ "full" ] }
5761
anyhow = "1.0"

docs/experiments/sdk/phase-2-improvements.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,6 @@ contract. See ADR
278278
| 8 | Progress listener in builder | Medium | Observability | Done |
279279
| 9 | Async operations (provision, etc) | High | Full workflow | Done |
280280
| 10 | Error handling example | Simple | Documentation | |
281-
| 11 | Create-from-JSON-file example | Simple | Documentation | |
281+
| 11 | Create-from-JSON-file example | Simple | Documentation | Done |
282282
| 12 | Validate config example | Trivial | Documentation | |
283283
| 13 | Remove domain type leaks from SDK | Medium | DDD correctness | Done |
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//! Create-from-JSON-file example.
2+
//!
3+
//! Demonstrates how to use `create_environment_from_file()` — the SDK method
4+
//! that mirrors the CLI's `create environment --env-file <path>` flag.
5+
//!
6+
//! Users migrating from the CLI will already have JSON config files in their
7+
//! `envs/` directory. This example shows the typical workflow:
8+
//!
9+
//! 1. **Validate** — check the config file is valid before creating
10+
//! 2. **Create** — load the file and create the environment in one call
11+
//! 3. **Show** — inspect the resulting environment
12+
//! 4. **Purge** — clean up
13+
//!
14+
//! The example also shows how to handle `CreateEnvironmentFromFileError` to
15+
//! distinguish load/parse failures from creation failures.
16+
//!
17+
//! # Running
18+
//!
19+
//! ```bash
20+
//! cargo run --example sdk_create_from_json_file
21+
//! ```
22+
23+
use std::io::Write as _;
24+
use std::path::PathBuf;
25+
26+
use torrust_tracker_deployer_lib::presentation::sdk::{
27+
ConfigLoadError, CreateEnvironmentFromFileError, Deployer,
28+
};
29+
30+
fn main() -> Result<(), Box<dyn std::error::Error>> {
31+
let workspace = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
32+
33+
println!("=== Torrust Tracker Deployer SDK — Create from JSON File ===\n");
34+
35+
let deployer = Deployer::builder().working_dir(&workspace).build()?;
36+
println!(
37+
"[OK] Deployer initialized (workspace: {})\n",
38+
workspace.display()
39+
);
40+
41+
let private_key = workspace.join("fixtures/testing_rsa");
42+
let public_key = workspace.join("fixtures/testing_rsa.pub");
43+
44+
// ------------------------------------------------------------------
45+
// Write an environment config JSON file to a temp location.
46+
//
47+
// In real usage you would already have this file in your envs/ dir;
48+
// here we generate it at runtime so the example is self-contained and
49+
// the SSH key paths are always correct for the current workspace.
50+
// ------------------------------------------------------------------
51+
let json = format!(
52+
r#"{{
53+
"environment": {{ "name": "sdk-from-json-file" }},
54+
"ssh_credentials": {{
55+
"private_key_path": "{private_key}",
56+
"public_key_path": "{public_key}"
57+
}},
58+
"provider": {{
59+
"provider": "lxd",
60+
"profile_name": "torrust-sdk-from-json-file"
61+
}},
62+
"tracker": {{
63+
"core": {{
64+
"database": {{ "driver": "sqlite3", "database_name": "tracker.db" }},
65+
"private": false
66+
}},
67+
"udp_trackers": [{{ "bind_address": "0.0.0.0:6969" }}],
68+
"http_trackers": [{{ "bind_address": "0.0.0.0:7070" }}],
69+
"http_api": {{ "bind_address": "0.0.0.0:1212", "admin_token": "MyAccessToken" }},
70+
"health_check_api": {{ "bind_address": "0.0.0.0:1313" }}
71+
}}
72+
}}"#,
73+
private_key = private_key.display(),
74+
public_key = public_key.display(),
75+
);
76+
77+
let mut tmp = tempfile::NamedTempFile::new()?;
78+
tmp.write_all(json.as_bytes())?;
79+
let config_path = tmp.path();
80+
println!("Config file written to: {}\n", config_path.display());
81+
82+
// ------------------------------------------------------------------
83+
// Step 1: Validate the config file before creating
84+
// ------------------------------------------------------------------
85+
println!("--- Step 1: Validate config ---");
86+
let validation = deployer.validate(config_path)?;
87+
println!(" Config is valid.");
88+
println!(" Environment name : {}", validation.environment_name);
89+
println!(" Provider : {}\n", validation.provider);
90+
91+
// ------------------------------------------------------------------
92+
// Step 2: Create environment from the JSON file
93+
// ------------------------------------------------------------------
94+
println!("--- Step 2: Create from file ---");
95+
let env_name = deployer.create_environment_from_file(config_path)?;
96+
println!(" Created: {env_name}\n");
97+
98+
// ------------------------------------------------------------------
99+
// Step 3: Show environment details
100+
// ------------------------------------------------------------------
101+
println!("--- Step 3: Show environment ---");
102+
let info = deployer.show(&env_name)?;
103+
println!(" Name: {}", info.name);
104+
println!(" State: {}", info.state);
105+
println!(" Provider: {}", info.provider);
106+
println!(" Created at: {}\n", info.created_at);
107+
108+
// ------------------------------------------------------------------
109+
// Step 4: Demonstrate error handling for a missing file
110+
// ------------------------------------------------------------------
111+
println!("--- Step 4: Error — file not found ---");
112+
let bad_path = workspace.join("envs/non-existent-config.json");
113+
match deployer.create_environment_from_file(&bad_path) {
114+
Ok(_) => println!(" Unexpected success."),
115+
Err(CreateEnvironmentFromFileError::Load(ConfigLoadError::FileNotFound { path })) => {
116+
println!(" Caught FileNotFound: {}", path.display());
117+
println!(" (Expected — file does not exist.)\n");
118+
}
119+
Err(e) => println!(" Unexpected error: {e}\n"),
120+
}
121+
122+
// ------------------------------------------------------------------
123+
// Cleanup
124+
// ------------------------------------------------------------------
125+
println!("--- Cleanup ---");
126+
deployer.purge(&env_name)?;
127+
println!(" Environment '{env_name}' purged.");
128+
129+
println!("\n=== Example complete ===");
130+
Ok(())
131+
}

0 commit comments

Comments
 (0)