Skip to content

Commit 5c2ac9f

Browse files
committed
feat: implement run_script for Docker provider
1 parent 4ae1ec4 commit 5c2ac9f

File tree

4 files changed

+117
-8
lines changed

4 files changed

+117
-8
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use std::path::PathBuf;
2+
3+
use zombienet_sdk::{NetworkConfigBuilder, NetworkConfigExt, RunScriptOptions};
4+
5+
#[tokio::main]
6+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
7+
tracing_subscriber::fmt::init();
8+
9+
// Build a simple network configuration
10+
let network = NetworkConfigBuilder::new()
11+
.with_relaychain(|r| {
12+
r.with_chain("rococo-local")
13+
.with_default_command("polkadot")
14+
.with_default_image("docker.io/parity/polkadot:v1.20.2")
15+
.with_validator(|node| node.with_name("alice"))
16+
})
17+
.build()
18+
.unwrap()
19+
.spawn_docker()
20+
.await?;
21+
22+
println!("🚀🚀🚀🚀 network deployed");
23+
24+
let alice = network.get_node("alice")?;
25+
26+
// Create test script if it doesn't exist
27+
let script_path = PathBuf::from("./test_script.sh");
28+
if !script_path.exists() {
29+
println!("Creating test_script.sh...");
30+
std::fs::write(
31+
&script_path,
32+
r#"#!/bin/bash
33+
echo "✅ Script executed successfully!"
34+
echo "Arguments: $@"
35+
echo "NODE_NAME env: $NODE_NAME"
36+
echo "Working dir: $(pwd)"
37+
echo "Hostname: $(hostname)"
38+
exit 0
39+
"#,
40+
)?;
41+
}
42+
43+
println!("Running test_script.sh with args and env vars");
44+
let options = RunScriptOptions::new(&script_path)
45+
.args(vec!["arg1".to_string(), "arg2".to_string()])
46+
.env(vec![("NODE_NAME".to_string(), "alice".to_string())]);
47+
48+
println!("Executing script...");
49+
match alice.run_script(options).await? {
50+
Ok(stdout) => {
51+
println!("\n✅ Script executed successfully!");
52+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━");
53+
println!("STDOUT:\n{}", stdout);
54+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━");
55+
},
56+
Err((status, stderr)) => {
57+
println!("\n❌ Script failed with exit code: {:?}", status.code());
58+
println!("STDERR:\n{}", stderr);
59+
return Err("Script execution failed".into());
60+
},
61+
}
62+
63+
network.destroy().await?;
64+
65+
Ok(())
66+
}

crates/orchestrator/src/network/node.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,22 @@ impl NetworkNode {
273273
Ok(())
274274
}
275275

276+
/// Run a script inside the node's container/environment
277+
///
278+
/// The script will be uploaded to the node, made executable, and executed with
279+
/// the provided arguments and environment variables.
280+
///
281+
/// Returns `Ok(stdout)` on success, or `Err((exit_status, stderr))` on failure.
282+
pub async fn run_script(
283+
&self,
284+
options: provider::types::RunScriptOptions,
285+
) -> Result<provider::types::ExecutionResult, anyhow::Error> {
286+
self.inner
287+
.run_script(options)
288+
.await
289+
.map_err(|e| anyhow!("Failed to run script: {}", e))
290+
}
291+
276292
// Metrics assertions
277293

278294
/// Get metric value 'by name' from Prometheus (exposed by the node)

crates/provider/src/docker/node.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,30 @@ where
501501

502502
async fn run_script(
503503
&self,
504-
_options: RunScriptOptions,
504+
options: RunScriptOptions,
505505
) -> Result<ExecutionResult, ProviderError> {
506-
todo!()
506+
let file_name = options
507+
.local_script_path
508+
.file_name()
509+
.expect(&format!(
510+
"file name should be present at this point {THIS_IS_A_BUG}"
511+
))
512+
.to_string_lossy();
513+
514+
let remote_script_path = PathBuf::from(format!("/tmp/{file_name}"));
515+
516+
// Upload the script to the container and make it executable
517+
self.send_file(&options.local_script_path, &remote_script_path, "0755")
518+
.await?;
519+
520+
// Run the script with provided arguments and environment
521+
self.run_command(RunCommandOptions {
522+
program: remote_script_path.to_string_lossy().to_string(),
523+
args: options.args,
524+
env: options.env,
525+
})
526+
.await
527+
.map_err(|err| ProviderError::RunScriptError(self.name.to_string(), err.into()))
507528
}
508529

509530
async fn send_file(
@@ -524,8 +545,7 @@ where
524545
mode
525546
);
526547

527-
let _ = self
528-
.docker_client
548+
self.docker_client
529549
.container_cp(&self.container_name, local_file_path, remote_file_path)
530550
.await
531551
.map_err(|err| {
@@ -534,15 +554,14 @@ where
534554
self.name.clone(),
535555
err.into(),
536556
)
537-
});
557+
})?;
538558

539-
let _ = self
540-
.docker_client
559+
self.docker_client
541560
.container_exec(
542561
&self.container_name,
543562
vec!["chmod", mode, &remote_file_path.to_string_lossy()],
544563
None,
545-
None,
564+
Some("root"),
546565
)
547566
.await
548567
.map_err(|err| {
@@ -551,6 +570,13 @@ where
551570
local_file_path.to_string_lossy().to_string(),
552571
err.into(),
553572
)
573+
})?
574+
.map_err(|err| {
575+
ProviderError::SendFile(
576+
self.name.clone(),
577+
local_file_path.to_string_lossy().to_string(),
578+
anyhow!("chmod failed: status {}: {}", err.0, err.1),
579+
)
554580
})?;
555581

556582
Ok(())

crates/sdk/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub use orchestrator::{
1010
network::{node::NetworkNode, Network},
1111
sc_chain_spec, AddCollatorOptions, AddNodeOptions, Orchestrator,
1212
};
13+
pub use provider::types::{ExecutionResult, RunScriptOptions};
1314

1415
// Helpers used for interact with the network
1516
pub mod tx_helper {

0 commit comments

Comments
 (0)