log streams in watched cli executions
This commit is contained in:
60
Cargo.lock
generated
60
Cargo.lock
generated
@@ -201,7 +201,7 @@ version = "1.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -212,7 +212,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell_polyfill",
|
"once_cell_polyfill",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -535,6 +535,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"tar",
|
"tar",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"terminal_size",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-test",
|
"tokio-test",
|
||||||
@@ -1131,7 +1132,7 @@ version = "3.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1795,7 +1796,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1979,7 +1980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3006,9 +3007,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lapin"
|
name = "lapin"
|
||||||
version = "4.3.0"
|
version = "4.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1586ef35d652d6c47ed7449277a4483805b73b84ab368c85af44205fe3457972"
|
checksum = "39338badb3f992d800f6964501b056b575bdf142eb288202f973d218fe253b90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"amq-protocol",
|
"amq-protocol",
|
||||||
"async-rs",
|
"async-rs",
|
||||||
@@ -3209,9 +3210,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
@@ -3334,7 +3335,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3466,7 +3467,7 @@ version = "5.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.21.7",
|
||||||
"chrono",
|
"chrono",
|
||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
"http",
|
"http",
|
||||||
@@ -4204,12 +4205,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "1.0.5"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b36964393906eb775b89b25b05b7b95685b8dd14062f1663a31ff93e75c452e5"
|
checksum = "f44e94c96d8870a387d88ce3de3fdd608cbfc0705f03cb343cdde91509d3e49a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"arcstr",
|
"arcstr",
|
||||||
|
"async-lock",
|
||||||
"backon",
|
"backon",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -4566,7 +4568,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4640,7 +4642,7 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs",
|
"webpki-root-certs",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5094,7 +5096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5456,7 +5458,17 @@ dependencies = [
|
|||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5591,9 +5603,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.50.0"
|
version = "1.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -5624,9 +5636,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.6.1"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -6052,9 +6064,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -6398,7 +6410,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
10
Cargo.toml
10
Cargo.toml
@@ -20,7 +20,7 @@ repository = "https://git.rdrx.app/attune-system/attune"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Async runtime
|
# Async runtime
|
||||||
tokio = { version = "1.50", features = ["full"] }
|
tokio = { version = "1.51", features = ["full"] }
|
||||||
tokio-util = { version = "0.7", features = ["io"] }
|
tokio-util = { version = "0.7", features = ["io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["sync"] }
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ config = "0.15"
|
|||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
# UUID
|
# UUID
|
||||||
uuid = { version = "1.22", features = ["v4", "serde"] }
|
uuid = { version = "1.23", features = ["v4", "serde"] }
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
validator = { version = "0.20", features = ["derive"] }
|
validator = { version = "0.20", features = ["derive"] }
|
||||||
@@ -62,9 +62,9 @@ clap = { version = "4.6", features = ["derive"] }
|
|||||||
|
|
||||||
# Message queue / PubSub
|
# Message queue / PubSub
|
||||||
# RabbitMQ
|
# RabbitMQ
|
||||||
lapin = "4.3"
|
lapin = "4.4"
|
||||||
# Redis
|
# Redis
|
||||||
redis = { version = "1.0", features = ["tokio-comp", "connection-manager"] }
|
redis = { version = "1.2", features = ["tokio-comp", "connection-manager"] }
|
||||||
|
|
||||||
# JSON Schema
|
# JSON Schema
|
||||||
schemars = { version = "1.2", features = ["chrono04"] }
|
schemars = { version = "1.2", features = ["chrono04"] }
|
||||||
@@ -91,7 +91,7 @@ regex = "1.12"
|
|||||||
# HTTP client
|
# HTTP client
|
||||||
reqwest = { version = "0.13", features = ["json"] }
|
reqwest = { version = "0.13", features = ["json"] }
|
||||||
reqwest-eventsource = "0.6"
|
reqwest-eventsource = "0.6"
|
||||||
hyper = { version = "1.8", features = ["full"] }
|
hyper = { version = "1.9", features = ["full"] }
|
||||||
|
|
||||||
# File system utilities
|
# File system utilities
|
||||||
walkdir = "2.5"
|
walkdir = "2.5"
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ sha2 = { workspace = true }
|
|||||||
colored = "3.1"
|
colored = "3.1"
|
||||||
comfy-table = { version = "7.2", features = ["custom_styling"] }
|
comfy-table = { version = "7.2", features = ["custom_styling"] }
|
||||||
dialoguer = "0.12"
|
dialoguer = "0.12"
|
||||||
|
terminal_size = "0.4"
|
||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
jsonwebtoken = { workspace = true }
|
jsonwebtoken = { workspace = true }
|
||||||
|
|||||||
@@ -175,11 +175,11 @@ attune action execute core.echo --param message="Hello World" --param count=3
|
|||||||
# With JSON parameters
|
# With JSON parameters
|
||||||
attune action execute core.echo --params-json '{"message": "Hello", "count": 5}'
|
attune action execute core.echo --params-json '{"message": "Hello", "count": 5}'
|
||||||
|
|
||||||
# Wait for completion
|
# Watch until completion
|
||||||
attune action execute core.long_task --wait
|
attune action execute core.long_task --watch
|
||||||
|
|
||||||
# Wait with custom timeout (default 300 seconds)
|
# Watch with custom timeout (default 300 seconds)
|
||||||
attune action execute core.long_task --wait --timeout 600
|
attune action execute core.long_task --watch --timeout 600
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rule Management
|
## Rule Management
|
||||||
@@ -588,4 +588,4 @@ Key dependencies:
|
|||||||
- `colored`: Terminal colors
|
- `colored`: Terminal colors
|
||||||
- `comfy-table`: Table formatting
|
- `comfy-table`: Table formatting
|
||||||
- `dialoguer`: Interactive prompts
|
- `dialoguer`: Interactive prompts
|
||||||
- `indicatif`: Progress indicators (for future use)
|
- `indicatif`: Progress indicators (for future use)
|
||||||
|
|||||||
@@ -68,17 +68,17 @@ pub enum ActionCommands {
|
|||||||
#[arg(long, conflicts_with = "param")]
|
#[arg(long, conflicts_with = "param")]
|
||||||
params_json: Option<String>,
|
params_json: Option<String>,
|
||||||
|
|
||||||
/// Wait for execution to complete
|
/// Watch execution until it completes
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
wait: bool,
|
watch: bool,
|
||||||
|
|
||||||
/// Timeout in seconds when waiting (default: 300)
|
/// Timeout in seconds when watching (default: 300)
|
||||||
#[arg(long, default_value = "300", requires = "wait")]
|
#[arg(long, default_value = "300", requires = "watch")]
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
|
|
||||||
/// Notifier WebSocket base URL (e.g. ws://localhost:8081).
|
/// Notifier WebSocket base URL (e.g. ws://localhost:8081).
|
||||||
/// Derived from --api-url automatically when not set.
|
/// Derived from --api-url automatically when not set.
|
||||||
#[arg(long, requires = "wait")]
|
#[arg(long, requires = "watch")]
|
||||||
notifier_url: Option<String>,
|
notifier_url: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ pub async fn handle_action_command(
|
|||||||
action_ref,
|
action_ref,
|
||||||
param,
|
param,
|
||||||
params_json,
|
params_json,
|
||||||
wait,
|
watch,
|
||||||
timeout,
|
timeout,
|
||||||
notifier_url,
|
notifier_url,
|
||||||
} => {
|
} => {
|
||||||
@@ -196,7 +196,7 @@ pub async fn handle_action_command(
|
|||||||
params_json,
|
params_json,
|
||||||
profile,
|
profile,
|
||||||
api_url,
|
api_url,
|
||||||
wait,
|
watch,
|
||||||
timeout,
|
timeout,
|
||||||
notifier_url,
|
notifier_url,
|
||||||
output_format,
|
output_format,
|
||||||
@@ -307,7 +307,7 @@ async fn handle_show(
|
|||||||
if let Some(params) = action.param_schema {
|
if let Some(params) = action.param_schema {
|
||||||
if !params.is_null() {
|
if !params.is_null() {
|
||||||
output::print_section("Parameters Schema");
|
output::print_section("Parameters Schema");
|
||||||
println!("{}", serde_json::to_string_pretty(¶ms)?);
|
output::print_schema(¶ms)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -428,7 +428,7 @@ async fn handle_execute(
|
|||||||
params_json: Option<String>,
|
params_json: Option<String>,
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
api_url: &Option<String>,
|
api_url: &Option<String>,
|
||||||
wait: bool,
|
watch: bool,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
notifier_url: Option<String>,
|
notifier_url: Option<String>,
|
||||||
output_format: OutputFormat,
|
output_format: OutputFormat,
|
||||||
@@ -468,7 +468,7 @@ async fn handle_execute(
|
|||||||
let path = "/executions/execute".to_string();
|
let path = "/executions/execute".to_string();
|
||||||
let execution: Execution = client.post(&path, &request).await?;
|
let execution: Execution = client.post(&path, &request).await?;
|
||||||
|
|
||||||
if !wait {
|
if !watch {
|
||||||
match output_format {
|
match output_format {
|
||||||
OutputFormat::Json | OutputFormat::Yaml => {
|
OutputFormat::Json | OutputFormat::Yaml => {
|
||||||
output::print_output(&execution, output_format)?;
|
output::print_output(&execution, output_format)?;
|
||||||
@@ -492,22 +492,22 @@ async fn handle_execute(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let verbose = matches!(output_format, OutputFormat::Table);
|
let interactive_wait = true;
|
||||||
let watch_task = if verbose {
|
let stream_live_logs = true;
|
||||||
Some(spawn_execution_output_watch(
|
let debug_wait = false;
|
||||||
ApiClient::from_config(&config, api_url),
|
let watch_task = Some(spawn_execution_output_watch(
|
||||||
execution.id,
|
ApiClient::from_config(&config, api_url),
|
||||||
verbose,
|
execution.id,
|
||||||
))
|
interactive_wait,
|
||||||
} else {
|
stream_live_logs,
|
||||||
None
|
debug_wait,
|
||||||
};
|
));
|
||||||
let summary = wait_for_execution(WaitOptions {
|
let summary = wait_for_execution(WaitOptions {
|
||||||
execution_id: execution.id,
|
execution_id: execution.id,
|
||||||
timeout_secs: timeout,
|
timeout_secs: timeout,
|
||||||
api_client: &mut client,
|
api_client: &mut client,
|
||||||
notifier_ws_url: notifier_url,
|
notifier_ws_url: notifier_url,
|
||||||
verbose,
|
verbose: debug_wait,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let suppress_final_stdout = watch_task
|
let suppress_final_stdout = watch_task
|
||||||
|
|||||||
@@ -124,17 +124,17 @@ enum Commands {
|
|||||||
#[arg(long, conflicts_with = "param")]
|
#[arg(long, conflicts_with = "param")]
|
||||||
params_json: Option<String>,
|
params_json: Option<String>,
|
||||||
|
|
||||||
/// Wait for execution to complete
|
/// Watch execution until it completes
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
wait: bool,
|
watch: bool,
|
||||||
|
|
||||||
/// Timeout in seconds when waiting (default: 300)
|
/// Timeout in seconds when watching (default: 300)
|
||||||
#[arg(long, default_value = "300", requires = "wait")]
|
#[arg(long, default_value = "300", requires = "watch")]
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
|
|
||||||
/// Notifier WebSocket base URL (e.g. ws://localhost:8081).
|
/// Notifier WebSocket base URL (e.g. ws://localhost:8081).
|
||||||
/// Derived from --api-url automatically when not set.
|
/// Derived from --api-url automatically when not set.
|
||||||
#[arg(long, requires = "wait")]
|
#[arg(long, requires = "watch")]
|
||||||
notifier_url: Option<String>,
|
notifier_url: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ async fn main() {
|
|||||||
action_ref,
|
action_ref,
|
||||||
param,
|
param,
|
||||||
params_json,
|
params_json,
|
||||||
wait,
|
watch,
|
||||||
timeout,
|
timeout,
|
||||||
notifier_url,
|
notifier_url,
|
||||||
} => {
|
} => {
|
||||||
@@ -254,7 +254,7 @@ async fn main() {
|
|||||||
action_ref,
|
action_ref,
|
||||||
param,
|
param,
|
||||||
params_json,
|
params_json,
|
||||||
wait,
|
watch,
|
||||||
timeout,
|
timeout,
|
||||||
notifier_url,
|
notifier_url,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use colored::Colorize;
|
|||||||
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Cell, Color, Table};
|
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Cell, Color, Table};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use terminal_size::{terminal_size, Width};
|
||||||
|
|
||||||
/// Output format for CLI commands
|
/// Output format for CLI commands
|
||||||
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
|
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
|
||||||
@@ -88,14 +89,69 @@ pub fn add_header(table: &mut Table, headers: Vec<&str>) {
|
|||||||
pub fn print_key_value_table(pairs: Vec<(&str, String)>) {
|
pub fn print_key_value_table(pairs: Vec<(&str, String)>) {
|
||||||
let mut table = create_table();
|
let mut table = create_table();
|
||||||
add_header(&mut table, vec!["Key", "Value"]);
|
add_header(&mut table, vec!["Key", "Value"]);
|
||||||
|
let width = terminal_width();
|
||||||
|
let key_width = pairs
|
||||||
|
.iter()
|
||||||
|
.map(|(key, _)| display_width(key))
|
||||||
|
.max()
|
||||||
|
.unwrap_or(3)
|
||||||
|
.clamp(8, 18);
|
||||||
|
let value_width = width.saturating_sub(key_width + 9).max(20);
|
||||||
|
|
||||||
for (key, value) in pairs {
|
for (key, value) in pairs {
|
||||||
table.add_row(vec![Cell::new(key).fg(Color::Yellow), Cell::new(value)]);
|
table.add_row(vec![
|
||||||
|
Cell::new(wrap_text(key, key_width)).fg(Color::Yellow),
|
||||||
|
Cell::new(wrap_text(&value, value_width)),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{}", table);
|
println!("{}", table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print a schema in a readable multi-line format instead of a raw JSON dump.
|
||||||
|
pub fn print_schema(schema: &serde_json::Value) -> Result<()> {
|
||||||
|
if let Some(properties) = schema.as_object() {
|
||||||
|
if properties.values().all(|value| value.is_object()) {
|
||||||
|
let width = terminal_width();
|
||||||
|
let content_width = width.saturating_sub(4).max(24);
|
||||||
|
let mut names = properties.keys().collect::<Vec<_>>();
|
||||||
|
names.sort();
|
||||||
|
|
||||||
|
for (index, name) in names.into_iter().enumerate() {
|
||||||
|
if index > 0 {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", name.bold());
|
||||||
|
if let Some(definition) = properties.get(name).and_then(|value| value.as_object()) {
|
||||||
|
print_schema_field("Type", &schema_type_label(definition), content_width);
|
||||||
|
|
||||||
|
if let Some(default) = definition.get("default") {
|
||||||
|
print_schema_field("Default", &compact_json(default), content_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(description) = definition
|
||||||
|
.get("description")
|
||||||
|
.and_then(|value| value.as_str())
|
||||||
|
{
|
||||||
|
print_schema_field("Description", description, content_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
let constraints = schema_constraints(definition);
|
||||||
|
if !constraints.is_empty() {
|
||||||
|
print_schema_field("Constraints", &constraints.join(", "), content_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", serde_yaml_ng::to_string(schema)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Print a simple list
|
/// Print a simple list
|
||||||
pub fn print_list(items: Vec<String>) {
|
pub fn print_list(items: Vec<String>) {
|
||||||
for item in items {
|
for item in items {
|
||||||
@@ -137,6 +193,146 @@ pub fn truncate(s: &str, max_len: usize) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn terminal_width() -> usize {
|
||||||
|
terminal_size()
|
||||||
|
.map(|(Width(width), _)| width as usize)
|
||||||
|
.filter(|width| *width > 20)
|
||||||
|
.unwrap_or(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_width(value: &str) -> usize {
|
||||||
|
value.chars().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_text(value: &str, width: usize) -> String {
|
||||||
|
let width = width.max(1);
|
||||||
|
let mut wrapped = Vec::new();
|
||||||
|
|
||||||
|
for paragraph in value.split('\n') {
|
||||||
|
if paragraph.is_empty() {
|
||||||
|
wrapped.push(String::new());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut line = String::new();
|
||||||
|
for word in paragraph.split_whitespace() {
|
||||||
|
if line.is_empty() {
|
||||||
|
append_wrapped_word(&mut wrapped, &mut line, word, width);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if display_width(&line) + 1 + display_width(word) <= width {
|
||||||
|
line.push(' ');
|
||||||
|
line.push_str(word);
|
||||||
|
} else {
|
||||||
|
wrapped.push(line);
|
||||||
|
line = String::new();
|
||||||
|
append_wrapped_word(&mut wrapped, &mut line, word, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !line.is_empty() {
|
||||||
|
wrapped.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_wrapped_word(
|
||||||
|
lines: &mut Vec<String>,
|
||||||
|
current_line: &mut String,
|
||||||
|
word: &str,
|
||||||
|
width: usize,
|
||||||
|
) {
|
||||||
|
if display_width(word) <= width {
|
||||||
|
current_line.push_str(word);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chunk = String::new();
|
||||||
|
for ch in word.chars() {
|
||||||
|
chunk.push(ch);
|
||||||
|
if display_width(&chunk) >= width {
|
||||||
|
if current_line.is_empty() {
|
||||||
|
lines.push(std::mem::take(&mut chunk));
|
||||||
|
} else {
|
||||||
|
lines.push(std::mem::take(current_line));
|
||||||
|
lines.push(std::mem::take(&mut chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !chunk.is_empty() {
|
||||||
|
current_line.push_str(&chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_type_label(definition: &serde_json::Map<String, serde_json::Value>) -> String {
|
||||||
|
match definition.get("type") {
|
||||||
|
Some(serde_json::Value::String(kind)) => kind.clone(),
|
||||||
|
Some(serde_json::Value::Array(kinds)) => kinds
|
||||||
|
.iter()
|
||||||
|
.filter_map(|value| value.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" | "),
|
||||||
|
_ => "any".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_constraints(definition: &serde_json::Map<String, serde_json::Value>) -> Vec<String> {
|
||||||
|
let mut constraints = Vec::new();
|
||||||
|
|
||||||
|
if let Some(values) = definition.get("enum").and_then(|value| value.as_array()) {
|
||||||
|
constraints.push(format!(
|
||||||
|
"enum: {}",
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.map(compact_json)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in [
|
||||||
|
"minimum",
|
||||||
|
"maximum",
|
||||||
|
"minLength",
|
||||||
|
"maxLength",
|
||||||
|
"pattern",
|
||||||
|
"format",
|
||||||
|
] {
|
||||||
|
if let Some(value) = definition.get(key) {
|
||||||
|
constraints.push(format!("{key}: {}", compact_json(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compact_json(value: &serde_json::Value) -> String {
|
||||||
|
serde_json::to_string(value).unwrap_or_else(|_| value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_schema_field(label: &str, value: &str, width: usize) {
|
||||||
|
let indent = " ";
|
||||||
|
let label_prefix = format!("{indent}{label}: ");
|
||||||
|
let continuation = " ".repeat(label_prefix.chars().count());
|
||||||
|
let wrapped = wrap_text(
|
||||||
|
value,
|
||||||
|
width.saturating_sub(label_prefix.chars().count()).max(12),
|
||||||
|
);
|
||||||
|
let mut lines = wrapped.lines();
|
||||||
|
|
||||||
|
if let Some(first_line) = lines.next() {
|
||||||
|
println!("{label_prefix}{first_line}");
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
println!("{continuation}{line}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Format a timestamp in a human-readable way
|
/// Format a timestamp in a human-readable way
|
||||||
pub fn format_timestamp(timestamp: &str) -> String {
|
pub fn format_timestamp(timestamp: &str) -> String {
|
||||||
// Try to parse and format nicely, otherwise return as-is
|
// Try to parse and format nicely, otherwise return as-is
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -109,7 +109,7 @@ cargo test --package attune-cli --tests -- --test-threads=1
|
|||||||
- ✅ Execute with multiple parameters
|
- ✅ Execute with multiple parameters
|
||||||
- ✅ Execute with JSON parameters
|
- ✅ Execute with JSON parameters
|
||||||
- ✅ Execute without parameters
|
- ✅ Execute without parameters
|
||||||
- ✅ Execute with --wait flag
|
- ✅ Execute with --watch flag
|
||||||
- ✅ Execute with --async flag
|
- ✅ Execute with --async flag
|
||||||
- ✅ List actions by pack
|
- ✅ List actions by pack
|
||||||
- ✅ Invalid parameter formats
|
- ✅ Invalid parameter formats
|
||||||
@@ -287,4 +287,4 @@ For more information:
|
|||||||
- [CLI Usage Guide](../README.md)
|
- [CLI Usage Guide](../README.md)
|
||||||
- [CLI Profile Management](../../../docs/cli-profiles.md)
|
- [CLI Profile Management](../../../docs/cli-profiles.md)
|
||||||
- [API Documentation](../../../docs/api-*.md)
|
- [API Documentation](../../../docs/api-*.md)
|
||||||
- [Main Project README](../../../README.md)
|
- [Main Project README](../../../README.md)
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ async fn test_action_execute_wait_for_completion() {
|
|||||||
.arg("core.echo")
|
.arg("core.echo")
|
||||||
.arg("--param")
|
.arg("--param")
|
||||||
.arg("message=test")
|
.arg("message=test")
|
||||||
.arg("--wait");
|
.arg("--watch");
|
||||||
|
|
||||||
cmd.assert()
|
cmd.assert()
|
||||||
.success()
|
.success()
|
||||||
@@ -476,7 +476,7 @@ async fn test_action_execute_async_flag() {
|
|||||||
.arg("action")
|
.arg("action")
|
||||||
.arg("execute")
|
.arg("execute")
|
||||||
.arg("core.long_running");
|
.arg("core.long_running");
|
||||||
// Note: default behavior is async (no --wait), so no --async flag needed
|
// Note: default behavior is async (no --watch), so no --async flag needed
|
||||||
|
|
||||||
cmd.assert()
|
cmd.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|||||||
@@ -493,7 +493,16 @@ impl PackInstaller {
|
|||||||
})?;
|
})?;
|
||||||
let normalized_host = host.to_ascii_lowercase();
|
let normalized_host = host.to_ascii_lowercase();
|
||||||
|
|
||||||
if normalized_host == "localhost" {
|
// Whether the host is explicitly trusted via the allowlist. Explicitly allowlisted hosts
|
||||||
|
// bypass private-IP checks so that local/private registries (e.g. a self-hosted Gitea)
|
||||||
|
// can be used in development or air-gapped environments.
|
||||||
|
let host_is_allowlisted = self
|
||||||
|
.allowed_remote_hosts
|
||||||
|
.as_ref()
|
||||||
|
.map(|set| set.contains(&normalized_host))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if normalized_host == "localhost" && !host_is_allowlisted {
|
||||||
return Err(Error::validation(format!(
|
return Err(Error::validation(format!(
|
||||||
"Remote URL host is not allowed: {}",
|
"Remote URL host is not allowed: {}",
|
||||||
host
|
host
|
||||||
@@ -509,12 +518,14 @@ impl PackInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ip) = parsed.host().and_then(|host| match host {
|
if !host_is_allowlisted {
|
||||||
url::Host::Ipv4(ip) => Some(IpAddr::V4(ip)),
|
if let Some(ip) = parsed.host().and_then(|host| match host {
|
||||||
url::Host::Ipv6(ip) => Some(IpAddr::V6(ip)),
|
url::Host::Ipv4(ip) => Some(IpAddr::V4(ip)),
|
||||||
url::Host::Domain(_) => None,
|
url::Host::Ipv6(ip) => Some(IpAddr::V6(ip)),
|
||||||
}) {
|
url::Host::Domain(_) => None,
|
||||||
ensure_public_ip(ip)?;
|
}) {
|
||||||
|
ensure_public_ip(ip)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let port = parsed.port_or_known_default().ok_or_else(|| {
|
let port = parsed.port_or_known_default().ok_or_else(|| {
|
||||||
@@ -528,7 +539,9 @@ impl PackInstaller {
|
|||||||
let mut saw_address = false;
|
let mut saw_address = false;
|
||||||
for addr in resolved {
|
for addr in resolved {
|
||||||
saw_address = true;
|
saw_address = true;
|
||||||
ensure_public_ip(addr.ip())?;
|
if !host_is_allowlisted {
|
||||||
|
ensure_public_ip(addr.ip())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !saw_address {
|
if !saw_address {
|
||||||
@@ -557,7 +570,13 @@ impl PackInstaller {
|
|||||||
fn validate_remote_host(&self, host: &str) -> Result<()> {
|
fn validate_remote_host(&self, host: &str) -> Result<()> {
|
||||||
let normalized_host = host.to_ascii_lowercase();
|
let normalized_host = host.to_ascii_lowercase();
|
||||||
|
|
||||||
if normalized_host == "localhost" {
|
let host_is_allowlisted = self
|
||||||
|
.allowed_remote_hosts
|
||||||
|
.as_ref()
|
||||||
|
.map(|set| set.contains(&normalized_host))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if normalized_host == "localhost" && !host_is_allowlisted {
|
||||||
return Err(Error::validation(format!(
|
return Err(Error::validation(format!(
|
||||||
"Remote host is not allowed: {}",
|
"Remote host is not allowed: {}",
|
||||||
host
|
host
|
||||||
@@ -995,6 +1014,36 @@ mod tests {
|
|||||||
assert!(hosts.contains("cdn.example.com"));
|
assert!(hosts.contains("cdn.example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_validate_remote_url_allows_allowlisted_localhost() {
|
||||||
|
let temp_dir = std::env::temp_dir().join("attune-test");
|
||||||
|
let config = PackRegistryConfig {
|
||||||
|
allowed_source_hosts: vec!["localhost".to_string()],
|
||||||
|
allow_http: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let installer = PackInstaller::new(&temp_dir, Some(config)).await.unwrap();
|
||||||
|
|
||||||
|
installer
|
||||||
|
.validate_remote_url("http://localhost:3000/example/repo.git")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_remote_host_allows_allowlisted_localhost() {
|
||||||
|
let installer = PackInstaller {
|
||||||
|
temp_dir: std::env::temp_dir().join("attune-test"),
|
||||||
|
registry_client: None,
|
||||||
|
verify_checksums: false,
|
||||||
|
allow_http: false,
|
||||||
|
allowed_remote_hosts: Some(HashSet::from(["localhost".to_string()])),
|
||||||
|
progress_callback: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
installer.validate_remote_host("localhost").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_extract_git_host_from_scp_style_source() {
|
fn test_extract_git_host_from_scp_style_source() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -170,16 +170,11 @@ impl WorkerService {
|
|||||||
// Initialize worker registration
|
// Initialize worker registration
|
||||||
let registration = Arc::new(RwLock::new(WorkerRegistration::new(pool.clone(), &config)));
|
let registration = Arc::new(RwLock::new(WorkerRegistration::new(pool.clone(), &config)));
|
||||||
|
|
||||||
// Initialize artifact manager (legacy, for stdout/stderr log storage)
|
// Initialize artifact manager for execution stdout/stderr/result storage.
|
||||||
// nosemgrep: rust.actix.path-traversal.tainted-path.tainted-path -- Worker artifact/config directories come from trusted process configuration, not request data.
|
// This must use the shared artifacts_dir so the API log streaming endpoints
|
||||||
let artifact_base_dir = std::path::PathBuf::from(
|
// and artifact download routes can see the same files the worker writes.
|
||||||
config
|
// nosemgrep: rust.actix.path-traversal.tainted-path.tainted-path -- Artifact storage root is a trusted deployment configuration value.
|
||||||
.worker
|
let artifact_base_dir = std::path::PathBuf::from(&config.artifacts_dir);
|
||||||
.as_ref()
|
|
||||||
.and_then(|w| w.name.clone())
|
|
||||||
.map(|name| format!("/tmp/attune/artifacts/{}", name))
|
|
||||||
.unwrap_or_else(|| "/tmp/attune/artifacts".to_string()),
|
|
||||||
);
|
|
||||||
let artifact_manager = ArtifactManager::new(artifact_base_dir);
|
let artifact_manager = ArtifactManager::new(artifact_base_dir);
|
||||||
artifact_manager.initialize().await?;
|
artifact_manager.initialize().await?;
|
||||||
|
|
||||||
|
|||||||
37
deny.toml
37
deny.toml
@@ -24,7 +24,6 @@ allow = [
|
|||||||
"Unicode-3.0",
|
"Unicode-3.0",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
"OpenSSL",
|
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"MIT-0",
|
"MIT-0",
|
||||||
"CDLA-Permissive-2.0",
|
"CDLA-Permissive-2.0",
|
||||||
@@ -36,12 +35,36 @@ wildcards = "allow"
|
|||||||
highlight = "all"
|
highlight = "all"
|
||||||
deny = []
|
deny = []
|
||||||
skip = [
|
skip = [
|
||||||
"winnow@0.6.26",
|
"base64",
|
||||||
"winnow@0.7.15",
|
"core-foundation",
|
||||||
"windows_x86_64_msvc@0.42.2",
|
"cpufeatures",
|
||||||
"windows_x86_64_msvc@0.48.5",
|
"darling",
|
||||||
"windows_x86_64_msvc@0.52.6",
|
"darling_core",
|
||||||
"windows_x86_64_msvc@0.53.1",
|
"darling_macro",
|
||||||
|
"foldhash",
|
||||||
|
"getrandom",
|
||||||
|
"hashbrown",
|
||||||
|
"nom",
|
||||||
|
"r-efi",
|
||||||
|
"rand",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
"reqwest",
|
||||||
|
"thiserror",
|
||||||
|
"thiserror-impl",
|
||||||
|
"wasm-streams",
|
||||||
|
"webpki-roots",
|
||||||
|
"windows-sys",
|
||||||
|
"windows-targets",
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
"winnow"
|
||||||
]
|
]
|
||||||
skip-tree = []
|
skip-tree = []
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,53 @@ services:
|
|||||||
args:
|
args:
|
||||||
SERVICE: executor
|
SERVICE: executor
|
||||||
BUILDKIT_INLINE_CACHE: 1
|
BUILDKIT_INLINE_CACHE: 1
|
||||||
container_name: attune-executor
|
container_name: attune-executor-1
|
||||||
|
environment:
|
||||||
|
RUST_LOG: info
|
||||||
|
ATTUNE_CONFIG: /opt/attune/config/config.yaml
|
||||||
|
ATTUNE__SECURITY__JWT_SECRET: ${JWT_SECRET:-docker-dev-secret-change-in-production}
|
||||||
|
ATTUNE__SECURITY__ENCRYPTION_KEY: ${ENCRYPTION_KEY:-docker-dev-encryption-key-please-change-in-production-32plus}
|
||||||
|
ATTUNE__DATABASE__URL: postgresql://attune:attune@postgres:5432/attune
|
||||||
|
ATTUNE__MESSAGE_QUEUE__URL: amqp://attune:attune@rabbitmq:5672
|
||||||
|
ATTUNE__REDIS__URL: redis://redis:6379
|
||||||
|
ATTUNE__WORKER__WORKER_TYPE: container
|
||||||
|
volumes:
|
||||||
|
- ${ATTUNE_DOCKER_CONFIG_PATH:-./config.docker.yaml}:/opt/attune/config/config.yaml:ro
|
||||||
|
- packs_data:/opt/attune/packs:ro
|
||||||
|
- ./packs.dev:/opt/attune/packs.dev:rw
|
||||||
|
- artifacts_data:/opt/attune/artifacts:ro
|
||||||
|
- executor_logs:/opt/attune/logs
|
||||||
|
depends_on:
|
||||||
|
init-packs:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
init-user:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
migrations:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
rabbitmq:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "kill -0 1 || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- attune-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
executor-2:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/Dockerfile.optimized
|
||||||
|
args:
|
||||||
|
SERVICE: executor
|
||||||
|
BUILDKIT_INLINE_CACHE: 1
|
||||||
|
container_name: attune-executor-2
|
||||||
environment:
|
environment:
|
||||||
RUST_LOG: info
|
RUST_LOG: info
|
||||||
ATTUNE_CONFIG: /opt/attune/config/config.yaml
|
ATTUNE_CONFIG: /opt/attune/config/config.yaml
|
||||||
|
|||||||
@@ -133,11 +133,11 @@ attune action execute core.echo --param message="Hello" --param count=3
|
|||||||
# With JSON parameters
|
# With JSON parameters
|
||||||
attune action execute core.echo --params-json '{"message": "Hello", "count": 5}'
|
attune action execute core.echo --params-json '{"message": "Hello", "count": 5}'
|
||||||
|
|
||||||
# Wait for completion
|
# Watch until completion
|
||||||
attune action execute core.long_task --wait
|
attune action execute core.long_task --watch
|
||||||
|
|
||||||
# Wait with timeout
|
# Watch with timeout
|
||||||
attune action execute core.long_task --wait --timeout 600
|
attune action execute core.long_task --watch --timeout 600
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rule Management
|
### Rule Management
|
||||||
@@ -548,4 +548,4 @@ Potential future features:
|
|||||||
- [Main README](../README.md)
|
- [Main README](../README.md)
|
||||||
- [API Documentation](api-overview.md)
|
- [API Documentation](api-overview.md)
|
||||||
- [Pack Development](packs.md)
|
- [Pack Development](packs.md)
|
||||||
- [Configuration Guide](configuration.md)
|
- [Configuration Guide](configuration.md)
|
||||||
|
|||||||
Reference in New Issue
Block a user