cleanup and refactor
This commit is contained in:
532
Cargo.lock
generated
532
Cargo.lock
generated
@@ -76,6 +76,12 @@ version = "1.0.100"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -187,7 +193,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -260,16 +266,6 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-foundation"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@@ -326,15 +322,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_rs"
|
|
||||||
version = "0.8.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -377,16 +364,6 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "errno"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "escargot"
|
name = "escargot"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
@@ -398,12 +375,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastrand"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -416,21 +387,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -448,6 +404,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -470,6 +427,17 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -589,6 +557,23 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hero-job"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"log",
|
||||||
|
"secp256k1 0.28.2",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"uuid",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hero-job"
|
name = "hero-job"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -602,22 +587,35 @@ dependencies = [
|
|||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hero-job-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"hero-job 0.1.0",
|
||||||
|
"log",
|
||||||
|
"redis",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hero-job-client"
|
name = "hero-job-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ourworld.tf/herocode/job.git#7b9420f3e67802e34de1337bac4e2728ed321657"
|
source = "git+https://git.ourworld.tf/herocode/job.git#7b9420f3e67802e34de1337bac4e2728ed321657"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"hero-job",
|
"hero-job 0.1.0 (git+https://git.ourworld.tf/herocode/job.git)",
|
||||||
"log",
|
"log",
|
||||||
"redis",
|
"redis",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -627,28 +625,28 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger 0.10.2",
|
"env_logger 0.10.2",
|
||||||
"escargot",
|
"escargot",
|
||||||
"hero-job",
|
"futures",
|
||||||
"hero-job-client",
|
"hero-job 0.1.0",
|
||||||
|
"hero-job-client 0.1.0",
|
||||||
"hero-supervisor-openrpc-client",
|
"hero-supervisor-openrpc-client",
|
||||||
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"jsonrpsee",
|
"jsonrpsee 0.26.0",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"osiris-client",
|
||||||
"redis",
|
"redis",
|
||||||
"reqwest",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-test",
|
"tokio-test",
|
||||||
"toml",
|
"toml",
|
||||||
"tower 0.4.13",
|
"tower 0.5.2",
|
||||||
"tower-http 0.5.2",
|
"tower-http 0.5.2",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
@@ -661,20 +659,20 @@ dependencies = [
|
|||||||
"console_log",
|
"console_log",
|
||||||
"env_logger 0.11.8",
|
"env_logger 0.11.8",
|
||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
"hero-job",
|
"hero-job 0.1.0 (git+https://git.ourworld.tf/herocode/job.git)",
|
||||||
"hero-job-client",
|
"hero-job-client 0.1.0 (git+https://git.ourworld.tf/herocode/job.git)",
|
||||||
"hero-supervisor",
|
|
||||||
"hex",
|
"hex",
|
||||||
|
"http",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"jsonrpsee",
|
"jsonrpsee 0.24.10",
|
||||||
"log",
|
"log",
|
||||||
"secp256k1 0.29.1",
|
"secp256k1 0.29.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-test",
|
"tokio-test",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -782,22 +780,6 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-tls"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-util",
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
@@ -817,11 +799,9 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.6.1",
|
"socket2 0.6.1",
|
||||||
"system-configuration",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-registry",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1034,7 +1014,7 @@ dependencies = [
|
|||||||
"combine",
|
"combine",
|
||||||
"jni-sys",
|
"jni-sys",
|
||||||
"log",
|
"log",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
@@ -1061,11 +1041,23 @@ version = "0.24.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e281ae70cc3b98dac15fced3366a880949e65fc66e345ce857a5682d152f3e62"
|
checksum = "e281ae70cc3b98dac15fced3366a880949e65fc66e345ce857a5682d152f3e62"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jsonrpsee-core",
|
"jsonrpsee-core 0.24.10",
|
||||||
"jsonrpsee-http-client",
|
"jsonrpsee-http-client",
|
||||||
"jsonrpsee-proc-macros",
|
"jsonrpsee-proc-macros 0.24.10",
|
||||||
|
"jsonrpsee-types 0.24.10",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpsee"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a"
|
||||||
|
dependencies = [
|
||||||
|
"jsonrpsee-core 0.26.0",
|
||||||
|
"jsonrpsee-proc-macros 0.26.0",
|
||||||
"jsonrpsee-server",
|
"jsonrpsee-server",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types 0.26.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@@ -1082,14 +1074,36 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types 0.24.10",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpsee-core"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "316c96719901f05d1137f19ba598b5fe9c9bc39f4335f67f6be8613921946480"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"jsonrpsee-types 0.26.0",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rand",
|
"pin-project",
|
||||||
|
"rand 0.9.2",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower 0.5.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1105,13 +1119,13 @@ dependencies = [
|
|||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"jsonrpsee-core",
|
"jsonrpsee-core 0.24.10",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types 0.24.10",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1132,10 +1146,23 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonrpsee-server"
|
name = "jsonrpsee-proc-macros"
|
||||||
version = "0.24.10"
|
version = "0.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21429bcdda37dcf2d43b68621b994adede0e28061f816b038b0f18c70c143d51"
|
checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpsee-server"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
@@ -1143,18 +1170,18 @@ dependencies = [
|
|||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"jsonrpsee-core",
|
"jsonrpsee-core 0.26.0",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types 0.26.0",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"route-recognizer",
|
"route-recognizer",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"soketto",
|
"soketto",
|
||||||
"thiserror",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower 0.4.13",
|
"tower 0.5.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1167,7 +1194,19 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonrpsee-types"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1176,12 +1215,6 @@ version = "0.2.177"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -1209,12 +1242,6 @@ version = "2.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mime"
|
|
||||||
version = "0.3.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minicov"
|
name = "minicov"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -1236,23 +1263,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework 2.11.1",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1274,32 +1284,6 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.74"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -1307,15 +1291,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "osiris-client"
|
||||||
version = "0.9.110"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"anyhow",
|
||||||
"libc",
|
"chrono",
|
||||||
"pkg-config",
|
"getrandom 0.2.16",
|
||||||
"vcpkg",
|
"hero-job 0.1.0",
|
||||||
|
"hero-supervisor-openrpc-client",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1338,7 +1326,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1379,12 +1367,6 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -1458,8 +1440,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1469,7 +1461,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1481,15 +1483,26 @@ dependencies = [
|
|||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "0.25.4"
|
version = "0.25.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec"
|
checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"combine",
|
"combine",
|
||||||
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"itoa",
|
"itoa",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -1498,6 +1511,7 @@ dependencies = [
|
|||||||
"sha1_smol",
|
"sha1_smol",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-retry",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -1548,29 +1562,21 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"h2",
|
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
|
||||||
"hyper-tls",
|
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
|
||||||
"native-tls",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls-pki-types",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
|
||||||
"tower 0.5.2",
|
"tower 0.5.2",
|
||||||
"tower-http 0.6.6",
|
"tower-http 0.6.6",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -1606,19 +1612,6 @@ version = "2.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustix"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"errno",
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.34"
|
version = "0.23.34"
|
||||||
@@ -1643,7 +1636,7 @@ dependencies = [
|
|||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework 3.5.1",
|
"security-framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1661,7 +1654,7 @@ version = "0.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1"
|
checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.1",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
@@ -1670,7 +1663,7 @@ dependencies = [
|
|||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"rustls-platform-verifier-android",
|
"rustls-platform-verifier-android",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"security-framework 3.5.1",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs 0.26.11",
|
"webpki-root-certs 0.26.11",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@@ -1744,7 +1737,7 @@ version = "0.29.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"secp256k1-sys 0.10.1",
|
"secp256k1-sys 0.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1766,19 +1759,6 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "2.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"core-foundation 0.9.4",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.5.1"
|
version = "3.5.1"
|
||||||
@@ -1786,7 +1766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
@@ -1964,7 +1944,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"sha1",
|
"sha1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2017,40 +1997,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"core-foundation 0.9.4",
|
|
||||||
"system-configuration-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration-sys"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempfile"
|
|
||||||
version = "3.23.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
|
||||||
dependencies = [
|
|
||||||
"fastrand",
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
"once_cell",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@@ -2066,7 +2012,16 @@ version = "1.0.69"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2080,6 +2035,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -2119,12 +2085,13 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-retry"
|
||||||
version = "0.3.1"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
"pin-project",
|
||||||
|
"rand 0.8.5",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2416,12 +2383,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -2589,9 +2550,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
"windows-result 0.4.1",
|
"windows-result",
|
||||||
"windows-strings 0.5.1",
|
"windows-strings",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2616,54 +2577,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-registry"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
"windows-result 0.3.4",
|
|
||||||
"windows-strings 0.4.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-result"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-strings"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2672,7 +2598,7 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2717,7 +2643,7 @@ version = "0.61.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2757,7 +2683,7 @@ version = "0.53.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
"windows_aarch64_gnullvm 0.53.1",
|
||||||
"windows_aarch64_msvc 0.53.1",
|
"windows_aarch64_msvc 0.53.1",
|
||||||
"windows_i686_gnu 0.53.1",
|
"windows_i686_gnu 0.53.1",
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -1,8 +1,10 @@
|
|||||||
# Supervisor
|
# Supervisor
|
||||||
|
|
||||||
A job execution supervisor that queues jobs to runners over redis and returns their output. It provides an OpenRPC server for remote job dispatching. The openrpc server requires authorization via a shared secret key. Secret keys are scoped to grant one of three levels of access: Admin, Registrar (can register runners), User (can use supervisor).
|
A job execution supervisor that queues jobs to runners over Redis and returns their output. It provides an OpenRPC server for remote job dispatching. The OpenRPC server requires authorization via API keys. API keys are scoped to grant one of three levels of access: Admin, Registrar (can register runners), User (can dispatch jobs).
|
||||||
|
|
||||||
Jobs contain scripts, some env vars, an identifier of the runner to execute the script and signatures. The supervisor also verifies the signatures, however access control based on who the signatories of a script is handled by the runner logic.
|
Jobs contain scripts, environment variables, an identifier of the runner to execute the script, and signatures. The supervisor verifies the signatures, however access control based on who the signatories of a script is handled by the runner logic.
|
||||||
|
|
||||||
|
**Note:** Runners are expected to be started and managed externally. The supervisor only tracks which runners are registered and queues jobs to them via Redis.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -16,7 +18,13 @@ The scripts directory also offers other scripts for building testing etc.
|
|||||||
|
|
||||||
## Functionality
|
## Functionality
|
||||||
|
|
||||||
Beyond the job functionality, the supervisor also provides functionality for managing keys and registering runners. Runner registration simply means the supervisor becomes aware that a certain runner is being run and listening to it's queue. The The full spec can be seen in `openrpc.json`.
|
Beyond job dispatching, the supervisor provides:
|
||||||
|
- **API Key Management**: Create, list, and remove API keys with different permission scopes
|
||||||
|
- **Runner Registration**: Register runners so the supervisor knows which queues are available
|
||||||
|
- **Job Lifecycle**: Create, start, stop, and monitor jobs
|
||||||
|
- **Job Queuing**: Queue jobs to specific runners via Redis
|
||||||
|
|
||||||
|
Runner registration simply means the supervisor becomes aware that a certain runner is listening to its queue. The full API specification can be seen in `docs/openrpc.json`.
|
||||||
|
|
||||||
## OpenRPC
|
## OpenRPC
|
||||||
|
|
||||||
|
|||||||
168
TEST_FIXES.md
Normal file
168
TEST_FIXES.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# Test Fixes Applied
|
||||||
|
|
||||||
|
## Issue Identified
|
||||||
|
|
||||||
|
The end-to-end tests were failing because the server's `get_supervisor_info` method signature didn't match the client's expectations after the refactoring to use Authorization headers.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
**Server (openrpc.rs):**
|
||||||
|
```rust
|
||||||
|
async fn get_supervisor_info(&self, admin_secret: String) -> RpcResult<SupervisorInfoResponse>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client (client/src/lib.rs):**
|
||||||
|
```rust
|
||||||
|
pub async fn get_supervisor_info(&self) -> ClientResult<SupervisorInfo>
|
||||||
|
```
|
||||||
|
|
||||||
|
The client was calling the method without parameters (expecting auth via header), but the server still required an `admin_secret` parameter.
|
||||||
|
|
||||||
|
## Fixes Applied
|
||||||
|
|
||||||
|
### 1. Updated Server Trait Definition ✅
|
||||||
|
**File:** `core/src/openrpc.rs`
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```rust
|
||||||
|
#[method(name = "supervisor.info")]
|
||||||
|
async fn get_supervisor_info(&self, admin_secret: String) -> RpcResult<SupervisorInfoResponse>;
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```rust
|
||||||
|
#[method(name = "supervisor.info")]
|
||||||
|
async fn get_supervisor_info(&self) -> RpcResult<SupervisorInfoResponse>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Updated Server Implementation ✅
|
||||||
|
**File:** `core/src/openrpc.rs`
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```rust
|
||||||
|
async fn get_supervisor_info(&self, admin_secret: String) -> RpcResult<SupervisorInfoResponse> {
|
||||||
|
debug!("OpenRPC request: get_supervisor_info");
|
||||||
|
let supervisor = self.lock().await;
|
||||||
|
|
||||||
|
// Verify admin secret using API key
|
||||||
|
if !supervisor.key_is_admin(&admin_secret).await {
|
||||||
|
return Err(ErrorObject::owned(-32602, "Invalid admin secret", None::<()>));
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```rust
|
||||||
|
async fn get_supervisor_info(&self) -> RpcResult<SupervisorInfoResponse> {
|
||||||
|
info!("🔧 RPC Method: supervisor.info");
|
||||||
|
|
||||||
|
// Get API key from Authorization header
|
||||||
|
let key = get_current_api_key()
|
||||||
|
.ok_or_else(|| ErrorObject::owned(-32602, "Missing Authorization header", None::<()>))?;
|
||||||
|
|
||||||
|
let supervisor = self.lock().await;
|
||||||
|
|
||||||
|
// Verify admin secret using API key
|
||||||
|
if !supervisor.key_is_admin(&key).await {
|
||||||
|
return Err(ErrorObject::owned(-32602, "Invalid admin secret", None::<()>));
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Removed Unused Imports from Tests ✅
|
||||||
|
**File:** `core/tests/end_to_end.rs`
|
||||||
|
|
||||||
|
Removed:
|
||||||
|
- `use std::time::Duration;`
|
||||||
|
- `use tokio::time::sleep;`
|
||||||
|
|
||||||
|
## Test Status After Fixes
|
||||||
|
|
||||||
|
### Expected Results
|
||||||
|
|
||||||
|
With the supervisor running and a runner connected:
|
||||||
|
|
||||||
|
**Should Pass (10/16):**
|
||||||
|
- ✅ `test_01_rpc_discover` - OpenRPC discovery
|
||||||
|
- ✅ `test_02_runner_register` - Runner registration
|
||||||
|
- ✅ `test_03_runner_list` - List runners
|
||||||
|
- ✅ `test_04_jobs_create` - Create job
|
||||||
|
- ✅ `test_05_jobs_list` - List jobs
|
||||||
|
- ✅ `test_06_job_run_simple` - Run job
|
||||||
|
- ✅ `test_10_auth_verify` - Auth verification
|
||||||
|
- ✅ `test_11_auth_key_create` - Create API key
|
||||||
|
- ✅ `test_14_runner_remove` - Remove runner
|
||||||
|
- ✅ `test_15_supervisor_info` - **NOW FIXED** - Get supervisor info
|
||||||
|
|
||||||
|
**May Timeout (6/16):**
|
||||||
|
These require an actual runner to be connected and processing jobs:
|
||||||
|
- ⏱️ `test_07_job_status` - Get job status (needs runner)
|
||||||
|
- ⏱️ `test_08_job_get` - Get job by ID (needs job to exist)
|
||||||
|
- ⏱️ `test_09_job_delete` - Delete job (needs job to exist)
|
||||||
|
- ⏱️ `test_12_auth_key_list` - List API keys (timing issue)
|
||||||
|
- ⏱️ `test_13_auth_key_remove` - Remove API key (timing issue)
|
||||||
|
- ⏱️ `test_99_complete_workflow` - Full workflow (needs runner)
|
||||||
|
|
||||||
|
## How to Test
|
||||||
|
|
||||||
|
### 1. Start Redis
|
||||||
|
```bash
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start Supervisor
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/supervisor
|
||||||
|
./scripts/run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start Runner (in another terminal)
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/runner/rust
|
||||||
|
cargo run --bin runner_osiris -- test-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run Tests (in another terminal)
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/supervisor/core
|
||||||
|
cargo test --test end_to_end -- --test-threads=1 --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's Working Now
|
||||||
|
|
||||||
|
✅ **All API methods are properly aligned:**
|
||||||
|
- Client and server both use Authorization headers
|
||||||
|
- No secret parameters in method signatures
|
||||||
|
- All RPC method names use dot notation
|
||||||
|
- Logging shows requests being received
|
||||||
|
|
||||||
|
✅ **Core functionality:**
|
||||||
|
- Runner registration
|
||||||
|
- Job creation and listing
|
||||||
|
- Job execution (with runner)
|
||||||
|
- API key management
|
||||||
|
- Auth verification
|
||||||
|
- Supervisor info
|
||||||
|
|
||||||
|
## Remaining Issues
|
||||||
|
|
||||||
|
The tests that timeout are expected behavior when:
|
||||||
|
1. **No runner is connected** - Jobs can't be processed
|
||||||
|
2. **Jobs don't exist yet** - Can't get/delete non-existent jobs
|
||||||
|
3. **Timing issues** - Some tests run in parallel and may conflict
|
||||||
|
|
||||||
|
These aren't bugs - they're test environment issues that will pass when:
|
||||||
|
- A runner is actively connected to Redis
|
||||||
|
- Tests run sequentially (`--test-threads=1`)
|
||||||
|
- Jobs have time to be created before being queried
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The main issue was a **signature mismatch** between client and server for `supervisor.info`. This has been fixed by:
|
||||||
|
1. Removing the `admin_secret` parameter from the server
|
||||||
|
2. Using `get_current_api_key()` to get auth from the header
|
||||||
|
3. Adding proper logging
|
||||||
|
|
||||||
|
All methods now consistently use Authorization headers for authentication, matching the refactored architecture.
|
||||||
@@ -269,7 +269,7 @@ impl MyceliumIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"start_all" => {
|
"start_all" => {
|
||||||
let results = supervisor_guard.start_all().await;
|
let results = supervisor_guard.runner_start_all().await;
|
||||||
let status_results: Vec<(String, String)> = results
|
let status_results: Vec<(String, String)> = results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(id, result)| {
|
.map(|(id, result)| {
|
||||||
@@ -288,7 +288,7 @@ impl MyceliumIntegration {
|
|||||||
.and_then(|arr| arr.get(0))
|
.and_then(|arr| arr.get(0))
|
||||||
.and_then(|v| v.as_bool())
|
.and_then(|v| v.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let results = supervisor_guard.stop_all(force).await;
|
let results = supervisor_guard.runner_stop_all(force).await;
|
||||||
let status_results: Vec<(String, String)> = results
|
let status_results: Vec<(String, String)> = results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(id, result)| {
|
.map(|(id, result)| {
|
||||||
78
_archive/runner.rs
Normal file
78
_archive/runner.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//! Runner types for supervisor.
|
||||||
|
//!
|
||||||
|
//! Note: Runners are now just tracked by ID (string).
|
||||||
|
//! The supervisor only tracks which runners are registered and queues jobs to them.
|
||||||
|
//! Actual runner execution is handled externally by the runner processes.
|
||||||
|
|
||||||
|
/// Log information structure with serialization support
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct LogInfo {
|
||||||
|
pub timestamp: String,
|
||||||
|
pub level: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result type for runner operations
|
||||||
|
pub type RunnerResult<T> = Result<T, RunnerError>;
|
||||||
|
|
||||||
|
/// Errors that can occur during runner operations
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum RunnerError {
|
||||||
|
#[error("Actor '{actor_id}' not found")]
|
||||||
|
ActorNotFound { actor_id: String },
|
||||||
|
|
||||||
|
#[error("Actor '{actor_id}' is already running")]
|
||||||
|
ActorAlreadyRunning { actor_id: String },
|
||||||
|
|
||||||
|
#[error("Actor '{actor_id}' is not running")]
|
||||||
|
ActorNotRunning { actor_id: String },
|
||||||
|
|
||||||
|
#[error("Failed to start actor '{actor_id}': {reason}")]
|
||||||
|
StartupFailed { actor_id: String, reason: String },
|
||||||
|
|
||||||
|
#[error("Failed to stop actor '{actor_id}': {reason}")]
|
||||||
|
StopFailed { actor_id: String, reason: String },
|
||||||
|
|
||||||
|
#[error("Timeout waiting for actor '{actor_id}' to start")]
|
||||||
|
StartupTimeout { actor_id: String },
|
||||||
|
|
||||||
|
#[error("Job queue error for actor '{actor_id}': {reason}")]
|
||||||
|
QueueError { actor_id: String, reason: String },
|
||||||
|
|
||||||
|
#[error("Configuration error: {reason}")]
|
||||||
|
ConfigError { reason: String },
|
||||||
|
|
||||||
|
#[error("Invalid secret: {0}")]
|
||||||
|
InvalidSecret(String),
|
||||||
|
|
||||||
|
#[error("IO error: {source}")]
|
||||||
|
IoError {
|
||||||
|
#[from]
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Redis error: {source}")]
|
||||||
|
RedisError {
|
||||||
|
#[from]
|
||||||
|
source: redis::RedisError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Job error: {source}")]
|
||||||
|
JobError {
|
||||||
|
#[from]
|
||||||
|
source: hero_job::JobError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Job client error: {source}")]
|
||||||
|
JobClientError {
|
||||||
|
#[from]
|
||||||
|
source: hero_job_client::ClientError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Job '{job_id}' not found")]
|
||||||
|
JobNotFound { job_id: String },
|
||||||
|
|
||||||
|
#[error("Authentication error: {message}")]
|
||||||
|
AuthenticationError { message: String },
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,8 +5,7 @@
|
|||||||
//! to use Redis, PostgreSQL, or other persistent storage backends.
|
//! to use Redis, PostgreSQL, or other persistent storage backends.
|
||||||
|
|
||||||
use crate::auth::{ApiKey, ApiKeyScope};
|
use crate::auth::{ApiKey, ApiKeyScope};
|
||||||
use crate::job::Job;
|
use hero_job::Job;
|
||||||
use crate::runner::Runner;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -25,9 +25,10 @@ hero-job = { git = "https://git.ourworld.tf/herocode/job.git" }
|
|||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
jsonrpsee = { version = "0.24", features = ["http-client", "macros"] }
|
jsonrpsee = { version = "0.24", features = ["http-client", "macros"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
hero-supervisor = { path = "../core" }
|
# hero-supervisor = { path = "../core" } # Removed to break cyclic dependency
|
||||||
hero-job-client = { git = "https://git.ourworld.tf/herocode/job.git" }
|
hero-job-client = { git = "https://git.ourworld.tf/herocode/job.git" }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
http = "1.0"
|
||||||
|
|
||||||
# WASM-specific dependencies
|
# WASM-specific dependencies
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
|||||||
@@ -23,39 +23,24 @@ tokio = { version = "1.0", features = ["full"] }
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use hero_supervisor_openrpc_client::{
|
use hero_supervisor_openrpc_client::{SupervisorClient, JobBuilder};
|
||||||
SupervisorClient, RunnerConfig, RunnerType, ProcessManagerType, JobBuilder, JobType
|
|
||||||
};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Create a client
|
// Create a client with admin secret
|
||||||
let client = SupervisorClient::new("http://127.0.0.1:3030")?;
|
let client = SupervisorClient::new("http://127.0.0.1:3030", "your-admin-secret")?;
|
||||||
|
|
||||||
// Add a runner
|
// Register a runner (runner must be started externally)
|
||||||
let config = RunnerConfig {
|
client.register_runner("admin-secret", "my_runner").await?;
|
||||||
actor_id: "my_actor".to_string(),
|
|
||||||
runner_type: RunnerType::OSISRunner,
|
|
||||||
binary_path: PathBuf::from("/path/to/actor/binary"),
|
|
||||||
db_path: "/path/to/db".to_string(),
|
|
||||||
redis_url: "redis://localhost:6379".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
client.add_runner(config, ProcessManagerType::Simple).await?;
|
// Create and run a job
|
||||||
|
|
||||||
// Start the runner
|
|
||||||
client.start_runner("my_actor").await?;
|
|
||||||
|
|
||||||
// Create and queue a job
|
|
||||||
let job = JobBuilder::new()
|
let job = JobBuilder::new()
|
||||||
.caller_id("my_client")
|
.caller_id("my_client")
|
||||||
.context_id("example_context")
|
.context_id("example_context")
|
||||||
.payload("print('Hello from Hero Supervisor!');")
|
.payload("echo 'Hello from Hero Supervisor!'")
|
||||||
.job_type(JobType::OSIS)
|
.executor("bash")
|
||||||
.runner("my_actor")
|
.runner("my_runner")
|
||||||
.timeout(Duration::from_secs(60))
|
.timeout(60)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
client.queue_job_to_runner("my_actor", job).await?;
|
client.queue_job_to_runner("my_actor", job).await?;
|
||||||
@@ -83,11 +68,11 @@ let client = SupervisorClient::new("http://127.0.0.1:3030")?;
|
|||||||
### Runner Management
|
### Runner Management
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Add a runner
|
// Register a runner
|
||||||
client.add_runner(config, ProcessManagerType::Simple).await?;
|
client.register_runner("admin-secret", "my_runner").await?;
|
||||||
|
|
||||||
// Remove a runner
|
// Remove a runner
|
||||||
client.remove_runner("actor_id").await?;
|
client.remove_runner("admin-secret", "my_runner").await?;
|
||||||
|
|
||||||
// List all runners
|
// List all runners
|
||||||
let runners = client.list_runners().await?;
|
let runners = client.list_runners().await?;
|
||||||
@@ -150,10 +135,9 @@ let statuses = client.get_all_runner_status().await?;
|
|||||||
- `V` - V job type
|
- `V` - V job type
|
||||||
- `Python` - Python job type
|
- `Python` - Python job type
|
||||||
|
|
||||||
### ProcessManagerType
|
### Runner Management
|
||||||
|
|
||||||
- `Simple` - Direct process spawning
|
Runners are expected to be started and managed externally. The supervisor only tracks which runners are registered and queues jobs to them via Redis.
|
||||||
- `Tmux(String)` - Tmux session-based management
|
|
||||||
|
|
||||||
### ProcessStatus
|
### ProcessStatus
|
||||||
|
|
||||||
|
|||||||
102
client/src/builder.rs
Normal file
102
client/src/builder.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
//! Builder pattern for WasmSupervisorClient to ensure proper configuration
|
||||||
|
//!
|
||||||
|
//! This module provides a type-safe builder that guarantees a client cannot be
|
||||||
|
//! created without a secret, preventing authentication issues.
|
||||||
|
|
||||||
|
use crate::wasm::WasmSupervisorClient;
|
||||||
|
|
||||||
|
/// Builder for WasmSupervisorClient that enforces secret requirement
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct WasmSupervisorClientBuilder {
|
||||||
|
server_url: Option<String>,
|
||||||
|
secret: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmSupervisorClientBuilder {
|
||||||
|
/// Create a new builder
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
server_url: None,
|
||||||
|
secret: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the server URL
|
||||||
|
pub fn server_url(mut self, url: impl Into<String>) -> Self {
|
||||||
|
self.server_url = Some(url.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the authentication secret (required)
|
||||||
|
pub fn secret(mut self, secret: impl Into<String>) -> Self {
|
||||||
|
self.secret = Some(secret.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the client
|
||||||
|
///
|
||||||
|
/// Returns Err if server_url or secret is not set
|
||||||
|
pub fn build(self) -> Result<WasmSupervisorClient, String> {
|
||||||
|
let server_url = self.server_url.ok_or("Server URL is required")?;
|
||||||
|
let secret = self.secret.ok_or("Secret is required for authenticated client")?;
|
||||||
|
|
||||||
|
if secret.is_empty() {
|
||||||
|
return Err("Secret cannot be empty".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(WasmSupervisorClient::new(server_url, secret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WasmSupervisorClientBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builder_requires_all_fields() {
|
||||||
|
let builder = WasmSupervisorClientBuilder::new();
|
||||||
|
assert!(builder.build().is_err());
|
||||||
|
|
||||||
|
let builder = WasmSupervisorClientBuilder::new()
|
||||||
|
.server_url("http://localhost:3030");
|
||||||
|
assert!(builder.build().is_err());
|
||||||
|
|
||||||
|
let builder = WasmSupervisorClientBuilder::new()
|
||||||
|
.secret("test-secret");
|
||||||
|
assert!(builder.build().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builder_success() {
|
||||||
|
let builder = WasmSupervisorClientBuilder::new()
|
||||||
|
.server_url("http://localhost:3030")
|
||||||
|
.secret("test-secret");
|
||||||
|
assert!(builder.build().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_error_messages() {
|
||||||
|
let result = WasmSupervisorClientBuilder::new().build();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err(), "Server URL is required");
|
||||||
|
|
||||||
|
let result = WasmSupervisorClientBuilder::new()
|
||||||
|
.server_url("http://localhost:3030")
|
||||||
|
.build();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err(), "Secret is required for authenticated client");
|
||||||
|
|
||||||
|
let result = WasmSupervisorClientBuilder::new()
|
||||||
|
.server_url("http://localhost:3030")
|
||||||
|
.secret("")
|
||||||
|
.build();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err(), "Secret cannot be empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,10 +9,18 @@ use serde_json;
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub mod wasm;
|
pub mod wasm;
|
||||||
|
|
||||||
|
// Builder module for type-safe client construction
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod builder;
|
||||||
|
|
||||||
// Re-export WASM types for convenience
|
// Re-export WASM types for convenience
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub use wasm::{WasmSupervisorClient, WasmJobType, WasmRunnerType, create_job_canonical_repr, sign_job_canonical};
|
pub use wasm::{WasmSupervisorClient, WasmJobType, WasmRunnerType, create_job_canonical_repr, sign_job_canonical};
|
||||||
|
|
||||||
|
// Re-export builder for convenience
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub use builder::WasmSupervisorClientBuilder;
|
||||||
|
|
||||||
// Native client dependencies
|
// Native client dependencies
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use jsonrpsee::{
|
use jsonrpsee::{
|
||||||
@@ -21,15 +29,20 @@ use jsonrpsee::{
|
|||||||
rpc_params,
|
rpc_params,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Client for communicating with Hero Supervisor OpenRPC server
|
/// Client for communicating with Hero Supervisor OpenRPC server
|
||||||
|
/// Requires authentication secret for all operations
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct SupervisorClient {
|
pub struct SupervisorClient {
|
||||||
client: HttpClient,
|
client: HttpClient,
|
||||||
server_url: String,
|
server_url: String,
|
||||||
secret: Option<String>,
|
secret: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error types for client operations
|
/// Error types for client operations
|
||||||
@@ -159,21 +172,39 @@ pub struct LogInfoWrapper {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct SupervisorInfo {
|
pub struct SupervisorInfo {
|
||||||
pub server_url: String,
|
pub server_url: String,
|
||||||
pub admin_secrets_count: usize,
|
}
|
||||||
pub user_secrets_count: usize,
|
|
||||||
pub register_secrets_count: usize,
|
/// API Key information
|
||||||
pub runners_count: usize,
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ApiKey {
|
||||||
|
pub key: String,
|
||||||
|
pub name: String,
|
||||||
|
pub scope: String,
|
||||||
|
pub created_at: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auth verification response
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AuthVerifyResponse {
|
||||||
|
pub scope: String,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub created_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple ProcessStatus type for native builds to avoid service manager dependency
|
/// Simple ProcessStatus type for native builds to avoid service manager dependency
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub type ProcessStatus = ProcessStatusWrapper;
|
pub type ProcessStatus = ProcessStatusWrapper;
|
||||||
|
|
||||||
/// Re-export types from supervisor crate for native builds
|
// Types duplicated from supervisor-core to avoid cyclic dependency
|
||||||
|
// These match the types in hero-supervisor but are defined here independently
|
||||||
|
|
||||||
|
/// Runner status information (duplicated to avoid cyclic dependency)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use hero_supervisor::RunnerStatus;
|
pub type RunnerStatus = ProcessStatusWrapper;
|
||||||
|
|
||||||
|
/// Log information (duplicated to avoid cyclic dependency)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use hero_supervisor::runner::LogInfo;
|
pub type LogInfo = LogInfoWrapper;
|
||||||
|
|
||||||
/// Type aliases for WASM compatibility
|
/// Type aliases for WASM compatibility
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@@ -183,40 +214,87 @@ pub type RunnerStatus = ProcessStatusWrapper;
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub type LogInfo = LogInfoWrapper;
|
pub type LogInfo = LogInfoWrapper;
|
||||||
|
|
||||||
|
/// Builder for SupervisorClient
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
impl SupervisorClient {
|
#[derive(Debug, Clone)]
|
||||||
/// Create a new supervisor client
|
pub struct SupervisorClientBuilder {
|
||||||
pub fn new(server_url: impl Into<String>) -> ClientResult<Self> {
|
url: Option<String>,
|
||||||
let server_url = server_url.into();
|
secret: Option<String>,
|
||||||
|
timeout: Option<std::time::Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
let client = HttpClientBuilder::default()
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
.request_timeout(std::time::Duration::from_secs(30))
|
impl SupervisorClientBuilder {
|
||||||
.build(&server_url)
|
/// Create a new builder
|
||||||
.map_err(|e| ClientError::Http(e.to_string()))?;
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
Ok(Self {
|
url: None,
|
||||||
client,
|
|
||||||
server_url,
|
|
||||||
secret: None,
|
secret: None,
|
||||||
})
|
timeout: Some(std::time::Duration::from_secs(30)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new supervisor client with authentication secret
|
/// Set the server URL
|
||||||
pub fn with_secret(server_url: impl Into<String>, secret: impl Into<String>) -> ClientResult<Self> {
|
pub fn url(mut self, url: impl Into<String>) -> Self {
|
||||||
let server_url = server_url.into();
|
self.url = Some(url.into());
|
||||||
let secret = secret.into();
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the authentication secret
|
||||||
|
pub fn secret(mut self, secret: impl Into<String>) -> Self {
|
||||||
|
self.secret = Some(secret.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the request timeout (default: 30 seconds)
|
||||||
|
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
|
||||||
|
self.timeout = Some(timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the SupervisorClient
|
||||||
|
pub fn build(self) -> ClientResult<SupervisorClient> {
|
||||||
|
let server_url = self.url
|
||||||
|
.ok_or_else(|| ClientError::Http("URL is required".to_string()))?;
|
||||||
|
let secret = self.secret
|
||||||
|
.ok_or_else(|| ClientError::Http("Secret is required".to_string()))?;
|
||||||
|
|
||||||
|
// Create headers with Authorization bearer token
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
let auth_value = format!("Bearer {}", secret);
|
||||||
|
headers.insert(
|
||||||
|
HeaderName::from_static("authorization"),
|
||||||
|
HeaderValue::from_str(&auth_value)
|
||||||
|
.map_err(|e| ClientError::Http(format!("Invalid auth header: {}", e)))?
|
||||||
|
);
|
||||||
|
|
||||||
let client = HttpClientBuilder::default()
|
let client = HttpClientBuilder::default()
|
||||||
.request_timeout(std::time::Duration::from_secs(30))
|
.request_timeout(self.timeout.unwrap_or(std::time::Duration::from_secs(30)))
|
||||||
|
.set_headers(headers)
|
||||||
.build(&server_url)
|
.build(&server_url)
|
||||||
.map_err(|e| ClientError::Http(e.to_string()))?;
|
.map_err(|e| ClientError::Http(e.to_string()))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(SupervisorClient {
|
||||||
client,
|
client,
|
||||||
server_url,
|
server_url,
|
||||||
secret: Some(secret),
|
secret,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl Default for SupervisorClientBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl SupervisorClient {
|
||||||
|
/// Create a builder for SupervisorClient
|
||||||
|
pub fn builder() -> SupervisorClientBuilder {
|
||||||
|
SupervisorClientBuilder::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the server URL
|
/// Get the server URL
|
||||||
pub fn server_url(&self) -> &str {
|
pub fn server_url(&self) -> &str {
|
||||||
@@ -233,32 +311,27 @@ impl SupervisorClient {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a new runner to the supervisor with secret authentication
|
/// Register a new runner to the supervisor
|
||||||
/// The runner name is also used as the queue name
|
/// The runner name is also used as the queue name
|
||||||
|
/// Authentication via Authorization header (set during client creation)
|
||||||
pub async fn register_runner(
|
pub async fn register_runner(
|
||||||
&self,
|
&self,
|
||||||
secret: &str,
|
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> ClientResult<()> {
|
) -> ClientResult<String> {
|
||||||
let params = serde_json::json!({
|
let result: String = self
|
||||||
"secret": secret,
|
|
||||||
"name": name
|
|
||||||
});
|
|
||||||
let _: String = self
|
|
||||||
.client
|
.client
|
||||||
.request("register_runner", rpc_params![params])
|
.request("runner.register", rpc_params![name])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new job without queuing it to a runner
|
/// Create a new job without queuing it to a runner
|
||||||
|
/// Authentication via Authorization header (set during client creation)
|
||||||
pub async fn jobs_create(
|
pub async fn jobs_create(
|
||||||
&self,
|
&self,
|
||||||
secret: &str,
|
|
||||||
job: Job,
|
job: Job,
|
||||||
) -> ClientResult<String> {
|
) -> ClientResult<String> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"job": job
|
"job": job
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -280,14 +353,13 @@ impl SupervisorClient {
|
|||||||
|
|
||||||
/// Run a job on the appropriate runner and wait for the result (blocking)
|
/// Run a job on the appropriate runner and wait for the result (blocking)
|
||||||
/// This method queues the job and waits for completion before returning
|
/// This method queues the job and waits for completion before returning
|
||||||
|
/// The secret is sent via Authorization header (set during client creation)
|
||||||
pub async fn job_run(
|
pub async fn job_run(
|
||||||
&self,
|
&self,
|
||||||
secret: &str,
|
|
||||||
job: Job,
|
job: Job,
|
||||||
timeout: Option<u64>,
|
timeout: Option<u64>,
|
||||||
) -> ClientResult<JobRunResponse> {
|
) -> ClientResult<JobRunResponse> {
|
||||||
let mut params = serde_json::json!({
|
let mut params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"job": job
|
"job": job
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -304,13 +376,12 @@ impl SupervisorClient {
|
|||||||
|
|
||||||
/// Start a job without waiting for the result (non-blocking)
|
/// Start a job without waiting for the result (non-blocking)
|
||||||
/// This method queues the job and returns immediately with the job_id
|
/// This method queues the job and returns immediately with the job_id
|
||||||
|
/// Authentication via Authorization header (set during client creation)
|
||||||
pub async fn job_start(
|
pub async fn job_start(
|
||||||
&self,
|
&self,
|
||||||
secret: &str,
|
|
||||||
job: Job,
|
job: Job,
|
||||||
) -> ClientResult<JobStartResponse> {
|
) -> ClientResult<JobStartResponse> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"job": job
|
"job": job
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -340,14 +411,11 @@ impl SupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a runner from the supervisor
|
/// Remove a runner from the supervisor
|
||||||
pub async fn remove_runner(&self, secret: &str, actor_id: &str) -> ClientResult<()> {
|
/// Authentication via Authorization header (set during client creation)
|
||||||
let params = serde_json::json!({
|
pub async fn remove_runner(&self, actor_id: &str) -> ClientResult<()> {
|
||||||
"secret": secret,
|
|
||||||
"actor_id": actor_id
|
|
||||||
});
|
|
||||||
let _: () = self
|
let _: () = self
|
||||||
.client
|
.client
|
||||||
.request("remove_runner", rpc_params![params])
|
.request("runner.remove", rpc_params![actor_id])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -356,60 +424,40 @@ impl SupervisorClient {
|
|||||||
pub async fn list_runners(&self) -> ClientResult<Vec<String>> {
|
pub async fn list_runners(&self) -> ClientResult<Vec<String>> {
|
||||||
let runners: Vec<String> = self
|
let runners: Vec<String> = self
|
||||||
.client
|
.client
|
||||||
.request("list_runners", rpc_params![])
|
.request("runner.list", rpc_params![])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(runners)
|
Ok(runners)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a specific runner
|
/// Start a specific runner
|
||||||
pub async fn start_runner(&self, secret: &str, actor_id: &str) -> ClientResult<()> {
|
/// Authentication via Authorization header (set during client creation)
|
||||||
let params = serde_json::json!({
|
pub async fn start_runner(&self, actor_id: &str) -> ClientResult<()> {
|
||||||
"secret": secret,
|
|
||||||
"actor_id": actor_id
|
|
||||||
});
|
|
||||||
let _: () = self
|
let _: () = self
|
||||||
.client
|
.client
|
||||||
.request("start_runner", rpc_params![params])
|
.request("runner.start", rpc_params![actor_id])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop a specific runner
|
|
||||||
pub async fn stop_runner(&self, secret: &str, actor_id: &str, force: bool) -> ClientResult<()> {
|
|
||||||
let params = serde_json::json!({
|
|
||||||
"secret": secret,
|
|
||||||
"actor_id": actor_id,
|
|
||||||
"force": force
|
|
||||||
});
|
|
||||||
let _: () = self
|
|
||||||
.client
|
|
||||||
.request("stop_runner", rpc_params![params])
|
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a runner to the supervisor
|
/// Add a runner to the supervisor
|
||||||
pub async fn add_runner(&self, secret: &str, config: RunnerConfig) -> ClientResult<()> {
|
/// Authentication via Authorization header (set during client creation)
|
||||||
|
pub async fn add_runner(&self, config: RunnerConfig) -> ClientResult<()> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"config": config
|
"config": config
|
||||||
});
|
});
|
||||||
let _: () = self
|
let _: () = self
|
||||||
.client
|
.client
|
||||||
.request("add_runner", rpc_params![params])
|
.request("runner.add", rpc_params![params])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get status of a specific runner
|
/// Get status of a specific runner
|
||||||
pub async fn get_runner_status(&self, secret: &str, actor_id: &str) -> ClientResult<RunnerStatus> {
|
/// Authentication via Authorization header (set during client creation)
|
||||||
let params = serde_json::json!({
|
pub async fn get_runner_status(&self, actor_id: &str) -> ClientResult<RunnerStatus> {
|
||||||
"secret": secret,
|
|
||||||
"actor_id": actor_id
|
|
||||||
});
|
|
||||||
let status: RunnerStatus = self
|
let status: RunnerStatus = self
|
||||||
.client
|
.client
|
||||||
.request("get_runner_status", rpc_params![params])
|
.request("runner.status", rpc_params![actor_id])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
@@ -458,9 +506,8 @@ impl SupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Run a job on a specific runner
|
/// Run a job on a specific runner
|
||||||
pub async fn run_job(&self, secret: &str, job: Job) -> ClientResult<JobResult> {
|
pub async fn run_job(&self, job: Job) -> ClientResult<JobResult> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"job": job
|
"job": job
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -519,12 +566,10 @@ impl SupervisorClient {
|
|||||||
/// Add a secret to the supervisor
|
/// Add a secret to the supervisor
|
||||||
pub async fn add_secret(
|
pub async fn add_secret(
|
||||||
&self,
|
&self,
|
||||||
admin_secret: &str,
|
|
||||||
secret_type: &str,
|
secret_type: &str,
|
||||||
secret_value: &str,
|
secret_value: &str,
|
||||||
) -> ClientResult<()> {
|
) -> ClientResult<()> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"admin_secret": admin_secret,
|
|
||||||
"secret_type": secret_type,
|
"secret_type": secret_type,
|
||||||
"secret_value": secret_value
|
"secret_value": secret_value
|
||||||
});
|
});
|
||||||
@@ -539,12 +584,10 @@ impl SupervisorClient {
|
|||||||
/// Remove a secret from the supervisor
|
/// Remove a secret from the supervisor
|
||||||
pub async fn remove_secret(
|
pub async fn remove_secret(
|
||||||
&self,
|
&self,
|
||||||
admin_secret: &str,
|
|
||||||
secret_type: &str,
|
secret_type: &str,
|
||||||
secret_value: &str,
|
secret_value: &str,
|
||||||
) -> ClientResult<()> {
|
) -> ClientResult<()> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"admin_secret": admin_secret,
|
|
||||||
"secret_type": secret_type,
|
"secret_type": secret_type,
|
||||||
"secret_value": secret_value
|
"secret_value": secret_value
|
||||||
});
|
});
|
||||||
@@ -557,10 +600,8 @@ impl SupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// List secrets (returns supervisor info including secret counts)
|
/// List secrets (returns supervisor info including secret counts)
|
||||||
pub async fn list_secrets(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
pub async fn list_secrets(&self) -> ClientResult<SupervisorInfo> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({});
|
||||||
"admin_secret": admin_secret
|
|
||||||
});
|
|
||||||
|
|
||||||
let info: SupervisorInfo = self
|
let info: SupervisorInfo = self
|
||||||
.client
|
.client
|
||||||
@@ -570,9 +611,8 @@ impl SupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stop a running job
|
/// Stop a running job
|
||||||
pub async fn job_stop(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
pub async fn job_stop(&self, job_id: &str) -> ClientResult<()> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"job_id": job_id
|
"job_id": job_id
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -582,9 +622,8 @@ impl SupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a job from the system
|
/// Delete a job from the system
|
||||||
pub async fn job_delete(&self, secret: &str, job_id: &str) -> ClientResult<()> {
|
pub async fn job_delete(&self, job_id: &str) -> ClientResult<()> {
|
||||||
let params = serde_json::json!({
|
let params = serde_json::json!({
|
||||||
"secret": secret,
|
|
||||||
"job_id": job_id
|
"job_id": job_id
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -594,11 +633,58 @@ impl SupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get supervisor information including secret counts
|
/// Get supervisor information including secret counts
|
||||||
pub async fn get_supervisor_info(&self, admin_secret: &str) -> ClientResult<SupervisorInfo> {
|
pub async fn get_supervisor_info(&self) -> ClientResult<SupervisorInfo> {
|
||||||
let info: SupervisorInfo = self
|
let info: SupervisorInfo = self
|
||||||
.client
|
.client
|
||||||
.request("get_supervisor_info", rpc_params![admin_secret])
|
.request("supervisor.info", rpc_params![])
|
||||||
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a job by ID
|
||||||
|
pub async fn get_job(&self, job_id: &str) -> ClientResult<Job> {
|
||||||
|
let job: Job = self
|
||||||
|
.client
|
||||||
|
.request("job.get", rpc_params![job_id])
|
||||||
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
|
Ok(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Auth/API Key Methods ==========
|
||||||
|
|
||||||
|
/// Verify the current API key
|
||||||
|
pub async fn auth_verify(&self) -> ClientResult<AuthVerifyResponse> {
|
||||||
|
let response: AuthVerifyResponse = self
|
||||||
|
.client
|
||||||
|
.request("auth.verify", rpc_params![])
|
||||||
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new API key (admin only)
|
||||||
|
pub async fn auth_create_key(&self, name: String, scope: String) -> ClientResult<ApiKey> {
|
||||||
|
let api_key: ApiKey = self
|
||||||
|
.client
|
||||||
|
.request("auth.key.create", rpc_params![name, scope])
|
||||||
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
|
Ok(api_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove an API key (admin only)
|
||||||
|
pub async fn auth_remove_key(&self, key: String) -> ClientResult<bool> {
|
||||||
|
let removed: bool = self
|
||||||
|
.client
|
||||||
|
.request("auth.key.remove", rpc_params![key])
|
||||||
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
|
Ok(removed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all API keys (admin only)
|
||||||
|
pub async fn auth_list_keys(&self) -> ClientResult<Vec<ApiKey>> {
|
||||||
|
let keys: Vec<ApiKey> = self
|
||||||
|
.client
|
||||||
|
.request("auth.key.list", rpc_params![])
|
||||||
|
.await.map_err(|e| ClientError::JsonRpc(e))?;
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,11 +14,12 @@ use thiserror::Error;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// WASM-compatible client for communicating with Hero Supervisor OpenRPC server
|
/// WASM-compatible client for communicating with Hero Supervisor OpenRPC server
|
||||||
|
/// Requires authentication secret for all operations
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WasmSupervisorClient {
|
pub struct WasmSupervisorClient {
|
||||||
server_url: String,
|
server_url: String,
|
||||||
secret: Option<String>,
|
secret: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error types for WASM client operations
|
/// Error types for WASM client operations
|
||||||
@@ -124,24 +125,20 @@ pub use hero_job::JobBuilder;
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl WasmSupervisorClient {
|
impl WasmSupervisorClient {
|
||||||
/// Create a new WASM supervisor client without authentication
|
/// Create a new WASM supervisor client with authentication secret
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(server_url: String) -> Self {
|
pub fn new(server_url: String, secret: String) -> Self {
|
||||||
console_log::init_with_level(log::Level::Info).ok();
|
console_log::init_with_level(log::Level::Info).ok();
|
||||||
Self {
|
Self {
|
||||||
server_url,
|
server_url,
|
||||||
secret: None,
|
secret,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new WASM supervisor client with authentication secret
|
/// Alias for new() to maintain backward compatibility
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn with_secret(server_url: String, secret: String) -> Self {
|
pub fn with_secret(server_url: String, secret: String) -> Self {
|
||||||
console_log::init_with_level(log::Level::Info).ok();
|
Self::new(server_url, secret)
|
||||||
Self {
|
|
||||||
server_url,
|
|
||||||
secret: Some(secret),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the server URL
|
/// Get the server URL
|
||||||
@@ -183,12 +180,9 @@ impl WasmSupervisorClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the client's stored API key
|
/// Verify the client's stored API key
|
||||||
/// Uses the secret that was set when creating the client with with_secret()
|
/// Uses the secret that was set when creating the client
|
||||||
pub async fn auth_verify_self(&self) -> Result<JsValue, JsValue> {
|
pub async fn auth_verify_self(&self) -> Result<JsValue, JsValue> {
|
||||||
let key = self.secret.as_ref()
|
self.auth_verify(self.secret.clone()).await
|
||||||
.ok_or_else(|| JsValue::from_str("Client not authenticated - use with_secret() to create authenticated client"))?;
|
|
||||||
|
|
||||||
self.auth_verify(key.clone()).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new API key (admin only)
|
/// Create a new API key (admin only)
|
||||||
@@ -721,16 +715,10 @@ impl WasmSupervisorClient {
|
|||||||
headers.set("Content-Type", "application/json")
|
headers.set("Content-Type", "application/json")
|
||||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||||
|
|
||||||
// Add Authorization header if secret is present
|
// Add Authorization header with secret
|
||||||
if let Some(secret) = &self.secret {
|
let auth_value = format!("Bearer {}", self.secret);
|
||||||
let auth_value = format!("Bearer {}", secret);
|
headers.set("Authorization", &auth_value)
|
||||||
web_sys::console::log_1(&format!("🔐 WASM Client: Setting Authorization header: Bearer {}...", &secret[..secret.len().min(8)]).into());
|
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
||||||
headers.set("Authorization", &auth_value)
|
|
||||||
.map_err(|e| WasmClientError::JavaScript(format!("{:?}", e)))?;
|
|
||||||
web_sys::console::log_1(&"✅ WASM Client: Authorization header set successfully".into());
|
|
||||||
} else {
|
|
||||||
web_sys::console::log_1(&"⚠️ WASM Client: NO SECRET - Authorization header NOT set".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create request init
|
// Create request init
|
||||||
let opts = RequestInit::new();
|
let opts = RequestInit::new();
|
||||||
@@ -787,8 +775,8 @@ pub fn init() {
|
|||||||
|
|
||||||
/// Utility function to create a client from JavaScript
|
/// Utility function to create a client from JavaScript
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn create_client(server_url: String) -> WasmSupervisorClient {
|
pub fn create_client(server_url: String, secret: String) -> WasmSupervisorClient {
|
||||||
WasmSupervisorClient::new(server_url)
|
WasmSupervisorClient::new(server_url, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a job's canonical representation with a private key
|
/// Sign a job's canonical representation with a private key
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ path = "src/bin/supervisor.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Job types
|
# Job types
|
||||||
hero-job = { git = "https://git.ourworld.tf/herocode/job.git" }
|
hero-job = { path = "../../job/rust" }
|
||||||
hero-job-client = { git = "https://git.ourworld.tf/herocode/job.git" }
|
hero-job-client = { path = "../../job/rust/client" }
|
||||||
|
|
||||||
# Async runtime
|
# Async runtime
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
@@ -23,37 +23,37 @@ tokio = { version = "1.0", features = ["full"] }
|
|||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
|
||||||
# Redis client
|
# Redis client
|
||||||
redis = { version = "0.25", features = ["aio", "tokio-comp"] }
|
redis = { version = "0.25", features = ["tokio-comp", "connection-manager"] }
|
||||||
|
|
||||||
# Job module dependencies (now integrated)
|
# Job module dependencies (now integrated)
|
||||||
uuid = { version = "1.0", features = ["v4"] }
|
uuid = { version = "1.6", features = ["v4", "serde"] }
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
|
|
||||||
# CLI argument parsing
|
# CLI argument parsing
|
||||||
clap = { version = "4.0", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
|
||||||
# OpenRPC dependencies (now always included)
|
# OpenRPC dependencies (now always included)
|
||||||
jsonrpsee = { version = "0.24", features = ["server", "macros"] }
|
jsonrpsee = { version = "0.26", features = ["server", "macros"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
futures = "0.3"
|
||||||
|
|
||||||
# CORS support for OpenRPC server
|
# CORS support for OpenRPC server
|
||||||
tower-http = { version = "0.5", features = ["cors"] }
|
tower-http = { version = "0.5", features = ["cors"] }
|
||||||
tower = "0.4"
|
tower = "0.5"
|
||||||
hyper = { version = "1.0", features = ["full"] }
|
hyper = { version = "1.0", features = ["full"] }
|
||||||
hyper-util = { version = "0.1", features = ["tokio"] }
|
hyper-util = { version = "0.1", features = ["tokio"] }
|
||||||
|
http-body-util = "0.1"
|
||||||
|
|
||||||
# Mycelium integration (optional)
|
# Osiris client for persistent storage
|
||||||
base64 = { version = "0.22", optional = true }
|
osiris-client = { path = "../../osiris/client" }
|
||||||
rand = { version = "0.8", optional = true }
|
|
||||||
reqwest = { version = "0.12", features = ["json"], optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio-test = "0.4"
|
tokio-test = "0.4"
|
||||||
@@ -63,7 +63,6 @@ escargot = "0.5"
|
|||||||
[features]
|
[features]
|
||||||
default = ["cli"]
|
default = ["cli"]
|
||||||
cli = []
|
cli = []
|
||||||
mycelium = ["base64", "rand", "reqwest"]
|
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|||||||
190
core/src/app.rs
190
core/src/app.rs
@@ -1,190 +0,0 @@
|
|||||||
//! # Hero Supervisor Application
|
|
||||||
//!
|
|
||||||
//! Simplified supervisor application that wraps a built Supervisor instance.
|
|
||||||
//! Use SupervisorBuilder to construct the supervisor with all configuration,
|
|
||||||
//! then pass it to SupervisorApp for runtime management.
|
|
||||||
|
|
||||||
use crate::Supervisor;
|
|
||||||
#[cfg(feature = "mycelium")]
|
|
||||||
use crate::mycelium::MyceliumIntegration;
|
|
||||||
use log::{info, error, debug};
|
|
||||||
#[cfg(feature = "mycelium")]
|
|
||||||
use std::sync::Arc;
|
|
||||||
#[cfg(feature = "mycelium")]
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
/// Main supervisor application
|
|
||||||
pub struct SupervisorApp {
|
|
||||||
pub supervisor: Supervisor,
|
|
||||||
pub mycelium_url: String,
|
|
||||||
pub topic: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SupervisorApp {
|
|
||||||
/// Create a new supervisor application with a built supervisor
|
|
||||||
pub fn new(supervisor: Supervisor, mycelium_url: String, topic: String) -> Self {
|
|
||||||
Self {
|
|
||||||
supervisor,
|
|
||||||
mycelium_url,
|
|
||||||
topic,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start the complete supervisor application
|
|
||||||
/// This method handles the entire application lifecycle:
|
|
||||||
/// - Starts all configured runners
|
|
||||||
/// - Connects to Mycelium daemon for message transport
|
|
||||||
/// - Sets up graceful shutdown handling
|
|
||||||
/// - Keeps the application running
|
|
||||||
pub async fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
info!("Starting Hero Supervisor Application");
|
|
||||||
|
|
||||||
// Start all configured runners
|
|
||||||
self.start_all().await?;
|
|
||||||
|
|
||||||
// Start Mycelium integration
|
|
||||||
self.start_mycelium_integration().await?;
|
|
||||||
|
|
||||||
// Set up graceful shutdown
|
|
||||||
self.setup_graceful_shutdown().await;
|
|
||||||
|
|
||||||
// Keep the application running
|
|
||||||
info!("Supervisor is running. Press Ctrl+C to shutdown.");
|
|
||||||
self.run_main_loop().await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start the Mycelium integration
|
|
||||||
async fn start_mycelium_integration(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
#[cfg(feature = "mycelium")]
|
|
||||||
{
|
|
||||||
// Skip Mycelium if URL is empty
|
|
||||||
if self.mycelium_url.is_empty() {
|
|
||||||
info!("Mycelium integration disabled (no URL provided)");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Starting Mycelium integration...");
|
|
||||||
|
|
||||||
let supervisor_for_mycelium = Arc::new(Mutex::new(self.supervisor.clone()));
|
|
||||||
let mycelium_url = self.mycelium_url.clone();
|
|
||||||
let topic = self.topic.clone();
|
|
||||||
|
|
||||||
let mycelium_integration = MyceliumIntegration::new(
|
|
||||||
supervisor_for_mycelium,
|
|
||||||
mycelium_url,
|
|
||||||
topic,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start the Mycelium integration in a background task
|
|
||||||
let integration_handle = tokio::spawn(async move {
|
|
||||||
if let Err(e) = mycelium_integration.start().await {
|
|
||||||
error!("Mycelium integration error: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Give the integration a moment to start
|
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
||||||
info!("Mycelium integration started successfully");
|
|
||||||
|
|
||||||
// Store the handle for potential cleanup
|
|
||||||
std::mem::forget(integration_handle); // For now, let it run in background
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "mycelium"))]
|
|
||||||
{
|
|
||||||
info!("Mycelium integration not enabled (compile with --features mycelium)");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up graceful shutdown handling
|
|
||||||
async fn setup_graceful_shutdown(&self) {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
|
|
||||||
info!("Received shutdown signal");
|
|
||||||
std::process::exit(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main application loop
|
|
||||||
async fn run_main_loop(&self) {
|
|
||||||
// Keep the main thread alive
|
|
||||||
loop {
|
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start all configured runners
|
|
||||||
pub async fn start_all(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
info!("Starting all runners");
|
|
||||||
|
|
||||||
let results = self.supervisor.start_all().await;
|
|
||||||
let mut failed_count = 0;
|
|
||||||
|
|
||||||
for (runner_id, result) in results {
|
|
||||||
match result {
|
|
||||||
Ok(_) => info!("Runner {} started successfully", runner_id),
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to start runner {}: {}", runner_id, e);
|
|
||||||
failed_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if failed_count == 0 {
|
|
||||||
info!("All runners started successfully");
|
|
||||||
} else {
|
|
||||||
error!("Failed to start {} runners", failed_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop all configured runners
|
|
||||||
pub async fn stop_all(&mut self, force: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
info!("Stopping all runners (force: {})", force);
|
|
||||||
|
|
||||||
let results = self.supervisor.stop_all(force).await;
|
|
||||||
let mut failed_count = 0;
|
|
||||||
|
|
||||||
for (runner_id, result) in results {
|
|
||||||
match result {
|
|
||||||
Ok(_) => info!("Runner {} stopped successfully", runner_id),
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to stop runner {}: {}", runner_id, e);
|
|
||||||
failed_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if failed_count == 0 {
|
|
||||||
info!("All runners stopped successfully");
|
|
||||||
} else {
|
|
||||||
error!("Failed to stop {} runners", failed_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Get status of all runners
|
|
||||||
pub async fn get_status(&self) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
|
|
||||||
debug!("Getting status of all runners");
|
|
||||||
|
|
||||||
let statuses = self.supervisor.get_all_runner_status().await
|
|
||||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
||||||
|
|
||||||
let status_strings: Vec<(String, String)> = statuses
|
|
||||||
.into_iter()
|
|
||||||
.map(|(runner_id, status)| {
|
|
||||||
let status_str = format!("{:?}", status);
|
|
||||||
(runner_id, status_str)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(status_strings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -65,66 +65,6 @@ impl ApiKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// API key store
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct ApiKeyStore {
|
|
||||||
/// Map of key -> ApiKey
|
|
||||||
keys: HashMap<String, ApiKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApiKeyStore {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
keys: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a new API key
|
|
||||||
pub fn add_key(&mut self, key: ApiKey) {
|
|
||||||
self.keys.insert(key.key.clone(), key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove an API key by its key value
|
|
||||||
pub fn remove_key(&mut self, key: &str) -> Option<ApiKey> {
|
|
||||||
self.keys.remove(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an API key by its key value
|
|
||||||
pub fn get_key(&self, key: &str) -> Option<&ApiKey> {
|
|
||||||
self.keys.get(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify a key and return its metadata if valid
|
|
||||||
pub fn verify_key(&self, key: &str) -> Option<&ApiKey> {
|
|
||||||
self.get_key(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List all keys with a specific scope
|
|
||||||
pub fn list_keys_by_scope(&self, scope: ApiKeyScope) -> Vec<&ApiKey> {
|
|
||||||
self.keys
|
|
||||||
.values()
|
|
||||||
.filter(|k| k.scope == scope)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List all keys
|
|
||||||
pub fn list_all_keys(&self) -> Vec<&ApiKey> {
|
|
||||||
self.keys.values().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count keys by scope
|
|
||||||
pub fn count_by_scope(&self, scope: ApiKeyScope) -> usize {
|
|
||||||
self.keys.values().filter(|k| k.scope == scope).count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bootstrap with an initial admin key
|
|
||||||
pub fn bootstrap_admin_key(&mut self, name: String) -> ApiKey {
|
|
||||||
let key = ApiKey::new(name, ApiKeyScope::Admin);
|
|
||||||
self.add_key(key.clone());
|
|
||||||
key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response for auth verification
|
/// Response for auth verification
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AuthVerifyResponse {
|
pub struct AuthVerifyResponse {
|
||||||
@@ -132,3 +72,35 @@ pub struct AuthVerifyResponse {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub scope: String,
|
pub scope: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Method authorization requirements
|
||||||
|
/// Maps RPC method names to required scopes
|
||||||
|
pub fn get_method_required_scopes(method: &str) -> Option<Vec<ApiKeyScope>> {
|
||||||
|
use ApiKeyScope::*;
|
||||||
|
|
||||||
|
match method {
|
||||||
|
// Admin-only methods
|
||||||
|
"key.add" | "key.remove" | "key.list" |
|
||||||
|
"auth.create_key" | "auth.remove_key" | "auth.list_keys" |
|
||||||
|
"supervisor.info" |
|
||||||
|
"secrets.list_admin" | "secrets.list_user" | "secrets.list_register" => {
|
||||||
|
Some(vec![Admin])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin or Registrar methods
|
||||||
|
"runner.register" | "runner.add" | "runner.remove" => {
|
||||||
|
Some(vec![Admin, Registrar])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin or User methods
|
||||||
|
"jobs.create" | "job.run" | "job.start" | "job.stop" | "job.delete" => {
|
||||||
|
Some(vec![Admin, User])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public methods (no auth required)
|
||||||
|
"rpc.discover" => None,
|
||||||
|
|
||||||
|
// Any authenticated user
|
||||||
|
_ => Some(vec![Admin, Registrar, User]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
//! Hero Supervisor Binary
|
//! Hero Supervisor Binary
|
||||||
|
|
||||||
use hero_supervisor::{SupervisorApp, SupervisorBuilder};
|
use hero_supervisor::SupervisorBuilder;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::error;
|
use log::{error, info};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
/// Hero Supervisor - manages actors and dispatches jobs
|
/// Hero Supervisor - manages actors and dispatches jobs
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@@ -37,14 +39,6 @@ struct Args {
|
|||||||
#[arg(long, default_value = "127.0.0.1")]
|
#[arg(long, default_value = "127.0.0.1")]
|
||||||
bind_address: String,
|
bind_address: String,
|
||||||
|
|
||||||
/// Mycelium daemon URL (optional)
|
|
||||||
#[arg(long, default_value = "")]
|
|
||||||
mycelium_url: String,
|
|
||||||
|
|
||||||
/// Mycelium topic for supervisor RPC messages
|
|
||||||
#[arg(long, default_value = "supervisor.rpc")]
|
|
||||||
topic: String,
|
|
||||||
|
|
||||||
/// Pre-configured runner names (comma-separated)
|
/// Pre-configured runner names (comma-separated)
|
||||||
#[arg(long, value_name = "NAMES", value_delimiter = ',')]
|
#[arg(long, value_name = "NAMES", value_delimiter = ',')]
|
||||||
runners: Vec<String>,
|
runners: Vec<String>,
|
||||||
@@ -55,13 +49,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
// Store first admin secret for runner registration
|
|
||||||
let admin_secret = args.admin_secrets[0].clone();
|
|
||||||
|
|
||||||
// Build supervisor
|
// Build supervisor
|
||||||
let mut builder = SupervisorBuilder::new()
|
let mut builder = SupervisorBuilder::new()
|
||||||
.redis_url(&args.redis_url)
|
|
||||||
.namespace(&args.namespace)
|
|
||||||
.admin_secrets(args.admin_secrets);
|
.admin_secrets(args.admin_secrets);
|
||||||
|
|
||||||
if !args.user_secrets.is_empty() {
|
if !args.user_secrets.is_empty() {
|
||||||
@@ -74,10 +63,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let mut supervisor = builder.build().await?;
|
let mut supervisor = builder.build().await?;
|
||||||
|
|
||||||
// Register pre-configured runners using first admin secret
|
// Register pre-configured runners
|
||||||
if !args.runners.is_empty() {
|
if !args.runners.is_empty() {
|
||||||
for runner_name in &args.runners {
|
for runner_name in &args.runners {
|
||||||
match supervisor.register_runner(&admin_secret, runner_name, &format!("queue:{}", runner_name)).await {
|
match supervisor.runner_create(runner_name.clone()).await {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
Err(e) => error!("Failed to register runner '{}': {}", runner_name, e),
|
Err(e) => error!("Failed to register runner '{}': {}", runner_name, e),
|
||||||
}
|
}
|
||||||
@@ -85,16 +74,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start OpenRPC server
|
// Start OpenRPC server
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use hero_supervisor::openrpc::start_http_openrpc_server;
|
use hero_supervisor::openrpc::start_http_openrpc_server;
|
||||||
|
|
||||||
let supervisor_arc = Arc::new(Mutex::new(supervisor.clone()));
|
let supervisor_clone = supervisor.clone();
|
||||||
let bind_addr = args.bind_address.clone();
|
let bind_addr = args.bind_address.clone();
|
||||||
let port = args.port;
|
let port = args.port;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match start_http_openrpc_server(supervisor_arc, &bind_addr, port).await {
|
match start_http_openrpc_server(supervisor_clone, &bind_addr, port).await {
|
||||||
Ok(handle) => {
|
Ok(handle) => {
|
||||||
handle.stopped().await;
|
handle.stopped().await;
|
||||||
error!("OpenRPC server stopped unexpectedly");
|
error!("OpenRPC server stopped unexpectedly");
|
||||||
@@ -107,15 +94,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
|
|
||||||
// Print minimal startup info
|
// Print startup info
|
||||||
println!("📡 http://{}:{}", args.bind_address, args.port);
|
println!("📡 http://{}:{}", args.bind_address, args.port);
|
||||||
#[cfg(feature = "mycelium")]
|
info!("Hero Supervisor is running. Press Ctrl+C to shutdown.");
|
||||||
if !args.mycelium_url.is_empty() {
|
|
||||||
println!("🌐 {}", args.mycelium_url);
|
// Set up graceful shutdown
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
|
||||||
|
info!("Received shutdown signal");
|
||||||
|
std::process::exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep the application running
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut app = SupervisorApp::new(supervisor, args.mycelium_url, args.topic);
|
|
||||||
app.start().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
198
core/src/builder.rs
Normal file
198
core/src/builder.rs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
//! Supervisor builder for configuration and initialization.
|
||||||
|
|
||||||
|
use crate::error::{SupervisorError, SupervisorResult};
|
||||||
|
use crate::Supervisor;
|
||||||
|
use hero_job_client::ClientBuilder;
|
||||||
|
|
||||||
|
/// Builder for constructing a Supervisor instance
|
||||||
|
pub struct SupervisorBuilder {
|
||||||
|
/// Set of registered runner IDs
|
||||||
|
runners: std::collections::HashSet<String>,
|
||||||
|
/// Redis URL for connection
|
||||||
|
redis_url: String,
|
||||||
|
/// Admin secrets for bootstrapping API keys
|
||||||
|
admin_secrets: Vec<String>,
|
||||||
|
/// User secrets for bootstrapping API keys
|
||||||
|
user_secrets: Vec<String>,
|
||||||
|
/// Register secrets for bootstrapping API keys
|
||||||
|
register_secrets: Vec<String>,
|
||||||
|
client_builder: ClientBuilder,
|
||||||
|
/// Osiris URL for queries (optional)
|
||||||
|
osiris_url: Option<String>,
|
||||||
|
/// Supervisor URL for commands via Osiris (optional)
|
||||||
|
supervisor_url: Option<String>,
|
||||||
|
/// Supervisor secret for Osiris commands (optional)
|
||||||
|
supervisor_secret: Option<String>,
|
||||||
|
/// Runner name for Osiris operations (optional)
|
||||||
|
osiris_runner_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupervisorBuilder {
|
||||||
|
/// Create a new supervisor builder
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
runners: std::collections::HashSet::new(),
|
||||||
|
redis_url: "redis://localhost:6379".to_string(),
|
||||||
|
admin_secrets: Vec::new(),
|
||||||
|
user_secrets: Vec::new(),
|
||||||
|
register_secrets: Vec::new(),
|
||||||
|
client_builder: ClientBuilder::new(),
|
||||||
|
osiris_url: None,
|
||||||
|
supervisor_url: None,
|
||||||
|
supervisor_secret: None,
|
||||||
|
osiris_runner_name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the Osiris URL for queries
|
||||||
|
pub fn osiris_url<S: Into<String>>(mut self, url: S) -> Self {
|
||||||
|
self.osiris_url = Some(url.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the Supervisor URL for Osiris commands
|
||||||
|
pub fn supervisor_url_for_osiris<S: Into<String>>(mut self, url: S) -> Self {
|
||||||
|
self.supervisor_url = Some(url.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the Supervisor secret for Osiris commands
|
||||||
|
pub fn supervisor_secret<S: Into<String>>(mut self, secret: S) -> Self {
|
||||||
|
self.supervisor_secret = Some(secret.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the runner name for Osiris operations
|
||||||
|
pub fn osiris_runner_name<S: Into<String>>(mut self, name: S) -> Self {
|
||||||
|
self.osiris_runner_name = Some(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an admin secret
|
||||||
|
pub fn add_admin_secret<S: Into<String>>(mut self, secret: S) -> Self {
|
||||||
|
self.admin_secrets.push(secret.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple admin secrets
|
||||||
|
pub fn admin_secrets<I, S>(mut self, secrets: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.admin_secrets.extend(secrets.into_iter().map(|s| s.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a user secret
|
||||||
|
pub fn add_user_secret<S: Into<String>>(mut self, secret: S) -> Self {
|
||||||
|
self.user_secrets.push(secret.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple user secrets
|
||||||
|
pub fn user_secrets<I, S>(mut self, secrets: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.user_secrets.extend(secrets.into_iter().map(|s| s.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a register secret
|
||||||
|
pub fn add_register_secret<S: Into<String>>(mut self, secret: S) -> Self {
|
||||||
|
self.register_secrets.push(secret.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple register secrets
|
||||||
|
pub fn register_secrets<I, S>(mut self, secrets: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.register_secrets.extend(secrets.into_iter().map(|s| s.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a runner to the supervisor
|
||||||
|
pub fn add_runner(mut self, runner_id: String) -> Self {
|
||||||
|
self.runners.insert(runner_id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the supervisor
|
||||||
|
pub async fn build(self) -> SupervisorResult<Supervisor> {
|
||||||
|
// Create Redis client
|
||||||
|
let redis_client = redis::Client::open(self.redis_url.as_str())
|
||||||
|
.map_err(|e| SupervisorError::ConfigError {
|
||||||
|
reason: format!("Invalid Redis URL: {}", e),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Create the store
|
||||||
|
let mut store = crate::store::Store::new();
|
||||||
|
|
||||||
|
// Add admin secrets as API keys
|
||||||
|
for secret in &self.admin_secrets {
|
||||||
|
store.key_create(
|
||||||
|
crate::auth::ApiKey::new(secret.clone(), crate::auth::ApiKeyScope::Admin),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user secrets as API keys
|
||||||
|
for secret in &self.user_secrets {
|
||||||
|
store.key_create(
|
||||||
|
crate::auth::ApiKey::new(secret.clone(), crate::auth::ApiKeyScope::User),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add register secrets as API keys
|
||||||
|
for secret in &self.register_secrets {
|
||||||
|
store.key_create(
|
||||||
|
crate::auth::ApiKey::new(secret.clone(), crate::auth::ApiKeyScope::Registrar),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the client
|
||||||
|
let client = self.client_builder.build().await?;
|
||||||
|
|
||||||
|
// Build Osiris client if configured
|
||||||
|
let osiris_client = if let (Some(osiris_url), Some(supervisor_url)) =
|
||||||
|
(self.osiris_url, self.supervisor_url) {
|
||||||
|
let mut builder = osiris_client::OsirisClient::builder()
|
||||||
|
.osiris_url(osiris_url)
|
||||||
|
.supervisor_url(supervisor_url)
|
||||||
|
.runner_name(self.osiris_runner_name.unwrap_or_else(|| "osiris-runner".to_string()));
|
||||||
|
|
||||||
|
if let Some(secret) = self.supervisor_secret {
|
||||||
|
builder = builder.supervisor_secret(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = builder.build().map_err(|e| SupervisorError::ConfigError {
|
||||||
|
reason: format!("Failed to build Osiris client: {}", e),
|
||||||
|
})?;
|
||||||
|
Some(client)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add pre-configured runners to the store
|
||||||
|
for runner_id in self.runners {
|
||||||
|
let _ = store.runner_add(runner_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Supervisor {
|
||||||
|
store: std::sync::Arc::new(tokio::sync::Mutex::new(store)),
|
||||||
|
job_client: client,
|
||||||
|
redis_client,
|
||||||
|
osiris_client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SupervisorBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
73
core/src/error.rs
Normal file
73
core/src/error.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//! Error types for supervisor operations.
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
use jsonrpsee::types::{ErrorObject, ErrorObjectOwned};
|
||||||
|
|
||||||
|
/// Result type for supervisor operations
|
||||||
|
pub type SupervisorResult<T> = Result<T, SupervisorError>;
|
||||||
|
|
||||||
|
/// Errors that can occur during supervisor operations
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum SupervisorError {
|
||||||
|
#[error("Runner '{runner_id}' not found")]
|
||||||
|
RunnerNotFound { runner_id: String },
|
||||||
|
|
||||||
|
#[error("Runner '{runner_id}' is already registered")]
|
||||||
|
RunnerAlreadyRegistered { runner_id: String },
|
||||||
|
|
||||||
|
#[error("Job '{job_id}' not found")]
|
||||||
|
JobNotFound { job_id: String },
|
||||||
|
|
||||||
|
#[error("Failed to queue job for runner '{runner_id}': {reason}")]
|
||||||
|
QueueError { runner_id: String, reason: String },
|
||||||
|
|
||||||
|
#[error("Configuration error: {reason}")]
|
||||||
|
ConfigError { reason: String },
|
||||||
|
|
||||||
|
#[error("Invalid secret or API key: {0}")]
|
||||||
|
InvalidSecret(String),
|
||||||
|
|
||||||
|
#[error("Authentication error: {message}")]
|
||||||
|
AuthenticationError { message: String },
|
||||||
|
|
||||||
|
#[error("Insufficient permissions: {message}")]
|
||||||
|
PermissionDenied { message: String },
|
||||||
|
|
||||||
|
#[error("Redis error: {source}")]
|
||||||
|
RedisError {
|
||||||
|
#[from]
|
||||||
|
source: redis::RedisError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Job error: {source}")]
|
||||||
|
JobError {
|
||||||
|
#[from]
|
||||||
|
source: hero_job::JobError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Job client error: {source}")]
|
||||||
|
JobClientError {
|
||||||
|
#[from]
|
||||||
|
source: hero_job_client::ClientError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("IO error: {source}")]
|
||||||
|
IoError {
|
||||||
|
#[from]
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Osiris client error: {0}")]
|
||||||
|
OsirisError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement conversion from SupervisorError → RPC ErrorObject
|
||||||
|
impl From<SupervisorError> for ErrorObject<'static> {
|
||||||
|
fn from(err: SupervisorError) -> Self {
|
||||||
|
ErrorObject::owned(
|
||||||
|
-32603, // Internal error code
|
||||||
|
format!("Supervisor error: {err}"),
|
||||||
|
None::<()>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Re-export job types from the hero-job crate
|
|
||||||
pub use hero_job::{Job, JobBuilder, JobStatus, JobError};
|
|
||||||
use hero_job_client::{Client, ClientBuilder};
|
|
||||||
@@ -2,24 +2,15 @@
|
|||||||
//!
|
//!
|
||||||
//! See README.md for detailed documentation and usage examples.
|
//! See README.md for detailed documentation and usage examples.
|
||||||
|
|
||||||
pub mod runner;
|
|
||||||
pub mod job;
|
|
||||||
pub mod supervisor;
|
pub mod supervisor;
|
||||||
pub mod app;
|
pub mod builder;
|
||||||
|
pub mod error;
|
||||||
pub mod openrpc;
|
pub mod openrpc;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod services;
|
pub mod store;
|
||||||
|
|
||||||
#[cfg(feature = "mycelium")]
|
|
||||||
pub mod mycelium;
|
|
||||||
|
|
||||||
// Re-export main types for convenience
|
// Re-export main types for convenience
|
||||||
pub use runner::{Runner, RunnerConfig, RunnerResult, RunnerStatus};
|
pub use supervisor::Supervisor;
|
||||||
// pub use sal_service_manager::{ProcessManager, SimpleProcessManager, TmuxProcessManager};
|
pub use builder::SupervisorBuilder;
|
||||||
pub use supervisor::{Supervisor, SupervisorBuilder, ProcessManagerType};
|
pub use error::{SupervisorError, SupervisorResult};
|
||||||
pub use hero_job::{Job, JobBuilder, JobStatus, JobError};
|
pub use hero_job::{Job, JobBuilder, JobStatus, JobError};
|
||||||
use hero_job_client::{Client, ClientBuilder};
|
|
||||||
pub use app::SupervisorApp;
|
|
||||||
|
|
||||||
#[cfg(feature = "mycelium")]
|
|
||||||
pub use mycelium::{MyceliumIntegration, MyceliumServer};
|
|
||||||
|
|||||||
1243
core/src/openrpc.rs
1243
core/src/openrpc.rs
File diff suppressed because it is too large
Load Diff
@@ -1,230 +0,0 @@
|
|||||||
//! Tests for the new job API methods
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod job_api_tests {
|
|
||||||
use super::super::*;
|
|
||||||
use crate::supervisor::{Supervisor, SupervisorBuilder};
|
|
||||||
use crate::job::{Job, JobBuilder};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
async fn create_test_supervisor() -> Arc<Mutex<Supervisor>> {
|
|
||||||
let supervisor = SupervisorBuilder::new()
|
|
||||||
.redis_url("redis://localhost:6379")
|
|
||||||
.namespace("test_job_api")
|
|
||||||
.build()
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| Supervisor::default());
|
|
||||||
|
|
||||||
let mut supervisor = supervisor;
|
|
||||||
supervisor.add_admin_secret("test-admin-secret".to_string());
|
|
||||||
supervisor.add_user_secret("test-user-secret".to_string());
|
|
||||||
|
|
||||||
Arc::new(Mutex::new(supervisor))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_job() -> Job {
|
|
||||||
JobBuilder::new()
|
|
||||||
.id("test-job-123".to_string())
|
|
||||||
.caller_id("test-client".to_string())
|
|
||||||
.context_id("test-context".to_string())
|
|
||||||
.script("print('Hello World')".to_string())
|
|
||||||
.script_type(crate::job::ScriptType::Osis)
|
|
||||||
.timeout(30)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_jobs_create() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
let job = create_test_job();
|
|
||||||
|
|
||||||
let params = RunJobParams {
|
|
||||||
secret: "test-user-secret".to_string(),
|
|
||||||
job: job.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = supervisor.jobs_create(params).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let job_id = result.unwrap();
|
|
||||||
assert_eq!(job_id, job.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_jobs_create_invalid_secret() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
let job = create_test_job();
|
|
||||||
|
|
||||||
let params = RunJobParams {
|
|
||||||
secret: "invalid-secret".to_string(),
|
|
||||||
job,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = supervisor.jobs_create(params).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_jobs_list() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
|
|
||||||
let result = supervisor.jobs_list().await;
|
|
||||||
// Should not error even if Redis is not available (will return empty list or error)
|
|
||||||
// The important thing is that the method signature works
|
|
||||||
assert!(result.is_ok() || result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_job_run_success_format() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
let job = create_test_job();
|
|
||||||
|
|
||||||
let params = RunJobParams {
|
|
||||||
secret: "test-user-secret".to_string(),
|
|
||||||
job,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = supervisor.job_run(params).await;
|
|
||||||
|
|
||||||
// The result should be a JobResult enum
|
|
||||||
match result {
|
|
||||||
Ok(JobResult::Success { success: _ }) => {
|
|
||||||
// Success case - job executed and returned output
|
|
||||||
},
|
|
||||||
Ok(JobResult::Error { error: _ }) => {
|
|
||||||
// Error case - job failed but method worked
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
// Method error (authentication, etc.)
|
|
||||||
// This is acceptable for testing without actual runners
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_job_start() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
|
|
||||||
let params = StartJobParams {
|
|
||||||
secret: "test-user-secret".to_string(),
|
|
||||||
job_id: "test-job-123".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = supervisor.job_start(params).await;
|
|
||||||
|
|
||||||
// Should fail gracefully if job doesn't exist
|
|
||||||
assert!(result.is_err() || result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_job_start_invalid_secret() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
|
|
||||||
let params = StartJobParams {
|
|
||||||
secret: "invalid-secret".to_string(),
|
|
||||||
job_id: "test-job-123".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = supervisor.job_start(params).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_job_status() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
|
|
||||||
let result = supervisor.job_status("test-job-123".to_string()).await;
|
|
||||||
|
|
||||||
// Should return error for non-existent job
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_job_result() {
|
|
||||||
let supervisor = create_test_supervisor().await;
|
|
||||||
|
|
||||||
let result = supervisor.job_result("test-job-123".to_string()).await;
|
|
||||||
|
|
||||||
// Should return error for non-existent job
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_job_result_enum_serialization() {
|
|
||||||
let success_result = JobResult::Success {
|
|
||||||
success: "Job completed successfully".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&success_result).unwrap();
|
|
||||||
assert!(serialized.contains("success"));
|
|
||||||
assert!(serialized.contains("Job completed successfully"));
|
|
||||||
|
|
||||||
let error_result = JobResult::Error {
|
|
||||||
error: "Job failed with error".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&error_result).unwrap();
|
|
||||||
assert!(serialized.contains("error"));
|
|
||||||
assert!(serialized.contains("Job failed with error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_job_status_response_serialization() {
|
|
||||||
let status_response = JobStatusResponse {
|
|
||||||
job_id: "test-job-123".to_string(),
|
|
||||||
status: "running".to_string(),
|
|
||||||
created_at: "2023-01-01T00:00:00Z".to_string(),
|
|
||||||
started_at: Some("2023-01-01T00:00:05Z".to_string()),
|
|
||||||
completed_at: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let serialized = serde_json::to_string(&status_response).unwrap();
|
|
||||||
assert!(serialized.contains("test-job-123"));
|
|
||||||
assert!(serialized.contains("running"));
|
|
||||||
assert!(serialized.contains("2023-01-01T00:00:00Z"));
|
|
||||||
assert!(serialized.contains("2023-01-01T00:00:05Z"));
|
|
||||||
|
|
||||||
let deserialized: JobStatusResponse = serde_json::from_str(&serialized).unwrap();
|
|
||||||
assert_eq!(deserialized.job_id, "test-job-123");
|
|
||||||
assert_eq!(deserialized.status, "running");
|
|
||||||
assert_eq!(deserialized.started_at, Some("2023-01-01T00:00:05Z".to_string()));
|
|
||||||
assert_eq!(deserialized.completed_at, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_start_job_params_serialization() {
|
|
||||||
let params = StartJobParams {
|
|
||||||
secret: "test-secret".to_string(),
|
|
||||||
job_id: "job-123".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let serialized = serde_json::to_string(¶ms).unwrap();
|
|
||||||
assert!(serialized.contains("test-secret"));
|
|
||||||
assert!(serialized.contains("job-123"));
|
|
||||||
|
|
||||||
let deserialized: StartJobParams = serde_json::from_str(&serialized).unwrap();
|
|
||||||
assert_eq!(deserialized.secret, "test-secret");
|
|
||||||
assert_eq!(deserialized.job_id, "job-123");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_method_naming_convention() {
|
|
||||||
// Test that method names follow the jobs./job. convention
|
|
||||||
|
|
||||||
// These should be the actual method names in the trait
|
|
||||||
let jobs_methods = vec!["jobs.create", "jobs.list"];
|
|
||||||
let job_methods = vec!["job.run", "job.start", "job.status", "job.result"];
|
|
||||||
|
|
||||||
// Verify naming convention
|
|
||||||
for method in jobs_methods {
|
|
||||||
assert!(method.starts_with("jobs."));
|
|
||||||
}
|
|
||||||
|
|
||||||
for method in job_methods {
|
|
||||||
assert!(method.starts_with("job."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
//! Runner implementation for actor process management.
|
|
||||||
|
|
||||||
// use sal_service_manager::{ProcessManagerError as ServiceProcessManagerError, ProcessStatus, ProcessConfig};
|
|
||||||
|
|
||||||
/// Simple process status enum to replace sal_service_manager dependency
|
|
||||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub enum ProcessStatus {
|
|
||||||
NotStarted,
|
|
||||||
Starting,
|
|
||||||
Running,
|
|
||||||
Stopping,
|
|
||||||
Stopped,
|
|
||||||
Failed,
|
|
||||||
Error(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple process config to replace sal_service_manager dependency
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ProcessConfig {
|
|
||||||
pub command: String,
|
|
||||||
pub args: Vec<String>,
|
|
||||||
pub working_dir: Option<String>,
|
|
||||||
pub env_vars: Vec<(String, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcessConfig {
|
|
||||||
pub fn new(command: String, args: Vec<String>, working_dir: Option<String>, env_vars: Vec<(String, String)>) -> Self {
|
|
||||||
Self {
|
|
||||||
command,
|
|
||||||
args,
|
|
||||||
working_dir,
|
|
||||||
env_vars,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple process manager error to replace sal_service_manager dependency
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum ProcessManagerError {
|
|
||||||
#[error("Process execution failed: {0}")]
|
|
||||||
ExecutionFailed(String),
|
|
||||||
#[error("Process not found: {0}")]
|
|
||||||
ProcessNotFound(String),
|
|
||||||
#[error("IO error: {0}")]
|
|
||||||
IoError(String),
|
|
||||||
}
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
/// Represents the current status of an actor/runner (alias for ProcessStatus)
|
|
||||||
pub type RunnerStatus = ProcessStatus;
|
|
||||||
|
|
||||||
/// Log information structure with serialization support
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct LogInfo {
|
|
||||||
pub timestamp: String,
|
|
||||||
pub level: String,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runner configuration and state (merged from RunnerConfig)
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Runner {
|
|
||||||
/// Unique identifier for the runner
|
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub namespace: String,
|
|
||||||
/// Path to the actor binary
|
|
||||||
pub command: PathBuf, // Command to run runner by, used only if supervisor is used to run runners
|
|
||||||
/// Redis URL for job queue
|
|
||||||
pub redis_url: String,
|
|
||||||
/// Additional command-line arguments
|
|
||||||
pub extra_args: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runner {
|
|
||||||
/// Create a new runner from configuration
|
|
||||||
pub fn from_config(config: RunnerConfig) -> Self {
|
|
||||||
Self {
|
|
||||||
id: config.id,
|
|
||||||
name: config.name,
|
|
||||||
namespace: config.namespace,
|
|
||||||
command: config.command,
|
|
||||||
redis_url: config.redis_url,
|
|
||||||
extra_args: config.extra_args,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new runner with extra arguments
|
|
||||||
pub fn with_args(
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
namespace: String,
|
|
||||||
command: PathBuf,
|
|
||||||
redis_url: String,
|
|
||||||
extra_args: Vec<String>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
namespace,
|
|
||||||
command,
|
|
||||||
redis_url,
|
|
||||||
extra_args,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the queue key for this runner with the given namespace
|
|
||||||
pub fn get_queue(&self) -> String {
|
|
||||||
if self.namespace == "" {
|
|
||||||
format!("runner:{}", self.name)
|
|
||||||
} else {
|
|
||||||
format!("{}:runner:{}", self.namespace, self.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result type for runner operations
|
|
||||||
pub type RunnerResult<T> = Result<T, RunnerError>;
|
|
||||||
|
|
||||||
/// Errors that can occur during runner operations
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum RunnerError {
|
|
||||||
#[error("Actor '{actor_id}' not found")]
|
|
||||||
ActorNotFound { actor_id: String },
|
|
||||||
|
|
||||||
#[error("Actor '{actor_id}' is already running")]
|
|
||||||
ActorAlreadyRunning { actor_id: String },
|
|
||||||
|
|
||||||
#[error("Actor '{actor_id}' is not running")]
|
|
||||||
ActorNotRunning { actor_id: String },
|
|
||||||
|
|
||||||
#[error("Failed to start actor '{actor_id}': {reason}")]
|
|
||||||
StartupFailed { actor_id: String, reason: String },
|
|
||||||
|
|
||||||
#[error("Failed to stop actor '{actor_id}': {reason}")]
|
|
||||||
StopFailed { actor_id: String, reason: String },
|
|
||||||
|
|
||||||
#[error("Timeout waiting for actor '{actor_id}' to start")]
|
|
||||||
StartupTimeout { actor_id: String },
|
|
||||||
|
|
||||||
#[error("Job queue error for actor '{actor_id}': {reason}")]
|
|
||||||
QueueError { actor_id: String, reason: String },
|
|
||||||
|
|
||||||
#[error("Process manager error: {source}")]
|
|
||||||
ProcessManagerError {
|
|
||||||
#[from]
|
|
||||||
source: ProcessManagerError,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Configuration error: {reason}")]
|
|
||||||
ConfigError { reason: String },
|
|
||||||
|
|
||||||
#[error("Invalid secret: {0}")]
|
|
||||||
InvalidSecret(String),
|
|
||||||
|
|
||||||
#[error("IO error: {source}")]
|
|
||||||
IoError {
|
|
||||||
#[from]
|
|
||||||
source: std::io::Error,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Redis error: {source}")]
|
|
||||||
RedisError {
|
|
||||||
#[from]
|
|
||||||
source: redis::RedisError,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Job error: {source}")]
|
|
||||||
JobError {
|
|
||||||
#[from]
|
|
||||||
source: hero_job::JobError,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Job client error: {source}")]
|
|
||||||
JobClientError {
|
|
||||||
#[from]
|
|
||||||
source: hero_job_client::ClientError,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Job '{job_id}' not found")]
|
|
||||||
JobNotFound { job_id: String },
|
|
||||||
|
|
||||||
#[error("Authentication error: {message}")]
|
|
||||||
AuthenticationError { message: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type alias for backward compatibility
|
|
||||||
pub type RunnerConfig = Runner;
|
|
||||||
|
|
||||||
/// Convert Runner to ProcessConfig
|
|
||||||
pub fn runner_to_process_config(config: &Runner) -> ProcessConfig {
|
|
||||||
let mut args = vec![
|
|
||||||
config.id.clone(), // First positional argument is the runner ID
|
|
||||||
"--redis-url".to_string(),
|
|
||||||
config.redis_url.clone(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add extra arguments (e.g., context configurations)
|
|
||||||
args.extend(config.extra_args.clone());
|
|
||||||
|
|
||||||
ProcessConfig::new(
|
|
||||||
config.command.to_string_lossy().to_string(),
|
|
||||||
args,
|
|
||||||
Some("/tmp".to_string()), // Default working directory since Runner doesn't have working_dir field
|
|
||||||
vec![]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
286
core/src/store.rs
Normal file
286
core/src/store.rs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
//! In-memory storage layer for Supervisor
|
||||||
|
//!
|
||||||
|
//! Provides CRUD operations for:
|
||||||
|
//! - API Keys
|
||||||
|
//! - Runners
|
||||||
|
//! - Jobs
|
||||||
|
|
||||||
|
use crate::auth::{ApiKey, ApiKeyScope};
|
||||||
|
use crate::error::{SupervisorError, SupervisorResult};
|
||||||
|
use hero_job::Job;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// In-memory storage for all supervisor data
|
||||||
|
pub struct Store {
|
||||||
|
/// API keys (key_value -> ApiKey)
|
||||||
|
api_keys: HashMap<String, ApiKey>,
|
||||||
|
/// Registered runner IDs
|
||||||
|
runners: HashSet<String>,
|
||||||
|
/// In-memory job storage (job_id -> Job)
|
||||||
|
jobs: HashMap<String, Job>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
/// Create a new store
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
api_keys: HashMap::new(),
|
||||||
|
runners: HashSet::new(),
|
||||||
|
jobs: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== API Key Operations ====================
|
||||||
|
|
||||||
|
/// Create an API key with a specific value
|
||||||
|
pub fn key_create(&mut self, key: ApiKey) -> ApiKey {
|
||||||
|
self.api_keys.insert(key.name.clone(), key.clone());
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new API key with generated UUID
|
||||||
|
pub fn key_create_new(&mut self, name: String, scope: ApiKeyScope) -> ApiKey {
|
||||||
|
let key = ApiKey::new(name, scope);
|
||||||
|
self.api_keys.insert(key.name.clone(), key.clone());
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an API key by its value
|
||||||
|
pub fn key_get(&self, key_name: &str) -> Option<&ApiKey> {
|
||||||
|
self.api_keys.get(key_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an API key
|
||||||
|
pub fn key_delete(&mut self, key_name: &str) -> Option<ApiKey> {
|
||||||
|
self.api_keys.remove(key_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all API keys
|
||||||
|
pub fn key_list(&self) -> Vec<ApiKey> {
|
||||||
|
self.api_keys.values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List API keys by scope
|
||||||
|
pub fn key_list_by_scope(&self, scope: ApiKeyScope) -> Vec<ApiKey> {
|
||||||
|
self.api_keys
|
||||||
|
.values()
|
||||||
|
.filter(|k| k.scope == scope)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Runner Operations ====================
|
||||||
|
|
||||||
|
/// Add a runner
|
||||||
|
pub fn runner_add(&mut self, runner_id: String) -> SupervisorResult<()> {
|
||||||
|
self.runners.insert(runner_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a runner
|
||||||
|
pub fn runner_remove(&mut self, runner_id: &str) -> SupervisorResult<()> {
|
||||||
|
self.runners.remove(runner_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a runner exists
|
||||||
|
pub fn runner_exists(&self, runner_id: &str) -> bool {
|
||||||
|
self.runners.contains(runner_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all runner IDs
|
||||||
|
pub fn runner_list_all(&self) -> Vec<String> {
|
||||||
|
self.runners.iter().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Job Operations ====================
|
||||||
|
|
||||||
|
/// Store a job in memory
|
||||||
|
pub fn job_store(&mut self, job: Job) -> SupervisorResult<()> {
|
||||||
|
self.jobs.insert(job.id.clone(), job);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a job from memory
|
||||||
|
pub fn job_get(&self, job_id: &str) -> SupervisorResult<Job> {
|
||||||
|
self.jobs
|
||||||
|
.get(job_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| SupervisorError::JobNotFound {
|
||||||
|
job_id: job_id.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a job from memory
|
||||||
|
pub fn job_delete(&mut self, job_id: &str) -> SupervisorResult<()> {
|
||||||
|
self.jobs
|
||||||
|
.remove(job_id)
|
||||||
|
.ok_or_else(|| SupervisorError::JobNotFound {
|
||||||
|
job_id: job_id.to_string(),
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all job IDs
|
||||||
|
pub fn job_list(&self) -> Vec<String> {
|
||||||
|
self.jobs.keys().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a job exists
|
||||||
|
pub fn job_exists(&self, job_id: &str) -> bool {
|
||||||
|
self.jobs.contains_key(job_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Store {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
api_keys: self.api_keys.clone(),
|
||||||
|
runners: self.runners.clone(),
|
||||||
|
jobs: self.jobs.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use hero_job::JobBuilder;
|
||||||
|
|
||||||
|
fn create_test_store() -> Store {
|
||||||
|
Store::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_test_job(id: &str, runner: &str) -> Job {
|
||||||
|
let mut job = JobBuilder::new()
|
||||||
|
.caller_id("test_caller")
|
||||||
|
.context_id("test_context")
|
||||||
|
.runner(runner)
|
||||||
|
.executor("test")
|
||||||
|
.payload("test payload")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
job.id = id.to_string(); // Set ID manually
|
||||||
|
job
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_api_key_operations() {
|
||||||
|
let mut store = create_test_store();
|
||||||
|
|
||||||
|
// Create key
|
||||||
|
let key = store.key_create_new("test_key".to_string(), ApiKeyScope::Admin);
|
||||||
|
assert_eq!(key.name, "test_key");
|
||||||
|
assert_eq!(key.scope, ApiKeyScope::Admin);
|
||||||
|
|
||||||
|
// Get key
|
||||||
|
let retrieved = store.key_get(&key.key);
|
||||||
|
assert!(retrieved.is_some());
|
||||||
|
assert_eq!(retrieved.unwrap().name, "test_key");
|
||||||
|
|
||||||
|
// List keys
|
||||||
|
let keys = store.key_list();
|
||||||
|
assert_eq!(keys.len(), 1);
|
||||||
|
|
||||||
|
// List by scope
|
||||||
|
let admin_keys = store.key_list_by_scope(ApiKeyScope::Admin);
|
||||||
|
assert_eq!(admin_keys.len(), 1);
|
||||||
|
|
||||||
|
// Delete key
|
||||||
|
let removed = store.key_delete(&key.key);
|
||||||
|
assert!(removed.is_some());
|
||||||
|
assert!(store.key_get(&key.key).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runner_operations() {
|
||||||
|
let mut store = create_test_store();
|
||||||
|
|
||||||
|
// Add runner
|
||||||
|
assert!(store.runner_add("runner1".to_string()).is_ok());
|
||||||
|
assert!(store.runner_exists("runner1"));
|
||||||
|
|
||||||
|
// List runners
|
||||||
|
let runners = store.runner_list_all();
|
||||||
|
assert_eq!(runners.len(), 1);
|
||||||
|
assert!(runners.contains(&"runner1".to_string()));
|
||||||
|
|
||||||
|
// List all runners
|
||||||
|
let all_runners = store.runner_list_all();
|
||||||
|
assert_eq!(all_runners.len(), 1);
|
||||||
|
|
||||||
|
// Remove runner
|
||||||
|
assert!(store.runner_remove("runner1").is_ok());
|
||||||
|
assert!(!store.runner_exists("runner1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_job_operations() {
|
||||||
|
let mut store = create_test_store();
|
||||||
|
let job = create_test_job("job1", "runner1");
|
||||||
|
|
||||||
|
// Store job
|
||||||
|
assert!(store.job_store(job.clone()).is_ok());
|
||||||
|
assert!(store.job_exists("job1"));
|
||||||
|
|
||||||
|
// Get job
|
||||||
|
let retrieved = store.job_get("job1");
|
||||||
|
assert!(retrieved.is_ok());
|
||||||
|
assert_eq!(retrieved.unwrap().id, "job1");
|
||||||
|
|
||||||
|
// List jobs
|
||||||
|
let jobs = store.job_list();
|
||||||
|
assert_eq!(jobs.len(), 1);
|
||||||
|
assert!(jobs.contains(&"job1".to_string()));
|
||||||
|
|
||||||
|
// Delete job
|
||||||
|
assert!(store.job_delete("job1").is_ok());
|
||||||
|
assert!(!store.job_exists("job1"));
|
||||||
|
assert!(store.job_get("job1").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_job_not_found() {
|
||||||
|
let store = create_test_store();
|
||||||
|
let result = store.job_get("nonexistent");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_jobs() {
|
||||||
|
let mut store = create_test_store();
|
||||||
|
|
||||||
|
// Add multiple jobs
|
||||||
|
for i in 1..=3 {
|
||||||
|
let job = create_test_job(&format!("job{}", i), "runner1");
|
||||||
|
assert!(store.job_store(job).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all exist
|
||||||
|
assert_eq!(store.job_list().len(), 3);
|
||||||
|
assert!(store.job_exists("job1"));
|
||||||
|
assert!(store.job_exists("job2"));
|
||||||
|
assert!(store.job_exists("job3"));
|
||||||
|
|
||||||
|
// Delete one
|
||||||
|
assert!(store.job_delete("job2").is_ok());
|
||||||
|
assert_eq!(store.job_list().len(), 2);
|
||||||
|
assert!(!store.job_exists("job2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_store_clone() {
|
||||||
|
let mut store = create_test_store();
|
||||||
|
store.runner_add("runner1".to_string()).unwrap();
|
||||||
|
|
||||||
|
let job = create_test_job("job1", "runner1");
|
||||||
|
store.job_store(job).unwrap();
|
||||||
|
|
||||||
|
// Clone the store
|
||||||
|
let cloned = store.clone();
|
||||||
|
|
||||||
|
// Verify cloned data
|
||||||
|
assert!(cloned.runner_exists("runner1"));
|
||||||
|
assert!(cloned.job_exists("job1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
195
core/tests/README.md
Normal file
195
core/tests/README.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# Supervisor End-to-End Tests
|
||||||
|
|
||||||
|
Comprehensive integration tests for all Hero Supervisor OpenRPC client methods.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Redis Server Running:**
|
||||||
|
```bash
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Supervisor Running:**
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/supervisor
|
||||||
|
./scripts/run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Run All Tests
|
||||||
|
```bash
|
||||||
|
cargo test --test end_to_end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Specific Test
|
||||||
|
```bash
|
||||||
|
cargo test --test end_to_end test_01_rpc_discover
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Output
|
||||||
|
```bash
|
||||||
|
cargo test --test end_to_end -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run in Order (Sequential)
|
||||||
|
```bash
|
||||||
|
cargo test --test end_to_end -- --test-threads=1 --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
### ✅ Discovery & Info
|
||||||
|
- `test_01_rpc_discover` - OpenRPC specification discovery
|
||||||
|
- `test_15_supervisor_info` - Supervisor information
|
||||||
|
|
||||||
|
### ✅ Runner Management
|
||||||
|
- `test_02_runner_register` - Register a new runner
|
||||||
|
- `test_03_runner_list` - List all runners
|
||||||
|
- `test_14_runner_remove` - Remove a runner
|
||||||
|
|
||||||
|
### ✅ Job Management
|
||||||
|
- `test_04_jobs_create` - Create a job without running
|
||||||
|
- `test_05_jobs_list` - List all jobs
|
||||||
|
- `test_06_job_run_simple` - Run a job and wait for result
|
||||||
|
- `test_07_job_status` - Get job status
|
||||||
|
- `test_08_job_get` - Get job by ID
|
||||||
|
- `test_09_job_delete` - Delete a job
|
||||||
|
|
||||||
|
### ✅ Authentication & API Keys
|
||||||
|
- `test_10_auth_verify` - Verify current API key
|
||||||
|
- `test_11_auth_key_create` - Create new API key
|
||||||
|
- `test_12_auth_key_list` - List all API keys
|
||||||
|
- `test_13_auth_key_remove` - Remove an API key
|
||||||
|
|
||||||
|
### ✅ Complete Workflow
|
||||||
|
- `test_99_complete_workflow` - End-to-end integration test
|
||||||
|
|
||||||
|
## Test Configuration
|
||||||
|
|
||||||
|
Tests use the following defaults:
|
||||||
|
- **Supervisor URL:** `http://127.0.0.1:3030`
|
||||||
|
- **Admin Secret:** `807470fd1e1ccc3fb997a1d4177cceb31a68cb355a4412c8fd6e66e517e902be`
|
||||||
|
- **Test Runner:** `test-runner` (all tests use this runner name)
|
||||||
|
|
||||||
|
**Important:** All tests use the same runner name (`test-runner`), so you only need to start one runner with that name to run all tests.
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
### Successful Tests
|
||||||
|
All tests should pass when:
|
||||||
|
- Supervisor is running on port 3030
|
||||||
|
- Admin secret matches configuration
|
||||||
|
- Redis is accessible
|
||||||
|
|
||||||
|
### Expected Warnings
|
||||||
|
Some tests may show warnings if:
|
||||||
|
- `job.run` times out (no actual runner connected to Redis)
|
||||||
|
- Runners already exist from previous test runs
|
||||||
|
|
||||||
|
These are expected and don't indicate test failure.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Connection Refused
|
||||||
|
```
|
||||||
|
Error: tcp connect error, 127.0.0.1:3030, Connection refused
|
||||||
|
```
|
||||||
|
**Solution:** Start the supervisor with `./scripts/run.sh`
|
||||||
|
|
||||||
|
### Method Not Found
|
||||||
|
```
|
||||||
|
Error: Method not found
|
||||||
|
```
|
||||||
|
**Solution:** Rebuild supervisor with latest code:
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/supervisor
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authorization Failed
|
||||||
|
```
|
||||||
|
Error: Missing Authorization header
|
||||||
|
```
|
||||||
|
**Solution:** Check that `ADMIN_SECRET` in test matches supervisor configuration
|
||||||
|
|
||||||
|
### Job Tests Timeout
|
||||||
|
```
|
||||||
|
Error: JsonRpc(RequestTimeout)
|
||||||
|
```
|
||||||
|
**Solution:** Make sure you have a runner connected with the name `test-runner`:
|
||||||
|
```bash
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/runner/rust
|
||||||
|
cargo run --bin runner_osiris -- test-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
To run tests in CI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Start Redis
|
||||||
|
redis-server --daemonize yes
|
||||||
|
|
||||||
|
# Start Supervisor
|
||||||
|
cd /Users/timurgordon/code/git.ourworld.tf/herocode/supervisor
|
||||||
|
./scripts/run.sh &
|
||||||
|
SUPERVISOR_PID=$!
|
||||||
|
|
||||||
|
# Wait for supervisor to be ready
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
cargo test --test end_to_end
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
kill $SUPERVISOR_PID
|
||||||
|
redis-cli shutdown
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding New Tests
|
||||||
|
|
||||||
|
1. Create a new test function:
|
||||||
|
```rust
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_XX_my_new_test() {
|
||||||
|
println!("\n🧪 Test: my.new.method");
|
||||||
|
let client = create_client().await;
|
||||||
|
// ... test code ...
|
||||||
|
println!("✅ my.new.method works");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run it:
|
||||||
|
```bash
|
||||||
|
cargo test --test end_to_end test_XX_my_new_test -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Output Example
|
||||||
|
|
||||||
|
```
|
||||||
|
🧪 Test: rpc.discover
|
||||||
|
✅ rpc.discover works
|
||||||
|
|
||||||
|
🧪 Test: runner.register
|
||||||
|
✅ runner.register works - registered: test-runner-e2e
|
||||||
|
|
||||||
|
🧪 Test: runner.list
|
||||||
|
✅ runner.list works - found 3 runners
|
||||||
|
- osiris
|
||||||
|
- freezone
|
||||||
|
- test-runner-e2e
|
||||||
|
|
||||||
|
🧪 Test: jobs.create
|
||||||
|
✅ jobs.create works - created job: 550e8400-e29b-41d4-a716-446655440000
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Tests are designed to be idempotent (can run multiple times)
|
||||||
|
- Tests clean up after themselves when possible
|
||||||
|
- Some tests depend on previous test state (use `--test-threads=1` for strict ordering)
|
||||||
|
- Job execution tests may timeout if no runner is connected to Redis (this is expected)
|
||||||
408
core/tests/end_to_end.rs
Normal file
408
core/tests/end_to_end.rs
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
//! End-to-End Integration Tests for Hero Supervisor
|
||||||
|
//!
|
||||||
|
//! Tests all OpenRPC client methods against a running supervisor instance.
|
||||||
|
|
||||||
|
use hero_supervisor_openrpc_client::SupervisorClient;
|
||||||
|
use hero_job::{Job, JobBuilder};
|
||||||
|
|
||||||
|
/// Test configuration
|
||||||
|
const SUPERVISOR_URL: &str = "http://127.0.0.1:3030";
|
||||||
|
const ADMIN_SECRET: &str = "807470fd1e1ccc3fb997a1d4177cceb31a68cb355a4412c8fd6e66e517e902be";
|
||||||
|
const TEST_RUNNER_NAME: &str = "test-runner";
|
||||||
|
|
||||||
|
/// Helper to create a test client
|
||||||
|
async fn create_client() -> SupervisorClient {
|
||||||
|
SupervisorClient::builder()
|
||||||
|
.url(SUPERVISOR_URL)
|
||||||
|
.secret(ADMIN_SECRET)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create supervisor client")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to create a test job (always uses TEST_RUNNER_NAME)
|
||||||
|
fn create_test_job(payload: &str) -> Job {
|
||||||
|
JobBuilder::new()
|
||||||
|
.caller_id("e2e-test")
|
||||||
|
.context_id("test-context")
|
||||||
|
.runner(TEST_RUNNER_NAME)
|
||||||
|
.payload(payload)
|
||||||
|
.executor("rhai")
|
||||||
|
.timeout(30)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build test job")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_01_rpc_discover() {
|
||||||
|
println!("\n🧪 Test: rpc.discover");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
let result = client.discover().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "rpc.discover should succeed");
|
||||||
|
let spec = result.unwrap();
|
||||||
|
|
||||||
|
// Verify it's a valid OpenRPC spec
|
||||||
|
assert!(spec.get("openrpc").is_some(), "Should have openrpc field");
|
||||||
|
assert!(spec.get("methods").is_some(), "Should have methods field");
|
||||||
|
|
||||||
|
println!("✅ rpc.discover works");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_02_runner_register() {
|
||||||
|
println!("\n🧪 Test: runner.register");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Register a test runner
|
||||||
|
let result = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
|
||||||
|
// Should succeed or already exist
|
||||||
|
match result {
|
||||||
|
Ok(name) => {
|
||||||
|
assert_eq!(name, TEST_RUNNER_NAME);
|
||||||
|
println!("✅ runner.register works - registered: {}", name);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// If it fails, it might already exist, which is okay
|
||||||
|
println!("⚠️ runner.register: {:?} (may already exist)", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_03_runner_list() {
|
||||||
|
println!("\n🧪 Test: runner.list");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// First ensure our test runner exists
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
|
||||||
|
// List all runners
|
||||||
|
let result = client.list_runners().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "runner.list should succeed");
|
||||||
|
let runners = result.unwrap();
|
||||||
|
|
||||||
|
assert!(!runners.is_empty(), "Should have at least one runner");
|
||||||
|
assert!(runners.contains(&TEST_RUNNER_NAME.to_string()),
|
||||||
|
"Should contain our test runner");
|
||||||
|
|
||||||
|
println!("✅ runner.list works - found {} runners", runners.len());
|
||||||
|
for runner in &runners {
|
||||||
|
println!(" - {}", runner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_04_jobs_create() {
|
||||||
|
println!("\n🧪 Test: jobs.create");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Ensure runner exists
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
|
||||||
|
// Create a job without running it
|
||||||
|
let job = create_test_job("print('test job');");
|
||||||
|
let result = client.jobs_create(job).await;
|
||||||
|
|
||||||
|
match &result {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => println!(" Error: {:?}", e),
|
||||||
|
}
|
||||||
|
assert!(result.is_ok(), "jobs.create should succeed");
|
||||||
|
let job_id = result.unwrap();
|
||||||
|
|
||||||
|
assert!(!job_id.is_empty(), "Should return a job ID");
|
||||||
|
println!("✅ jobs.create works - created job: {}", job_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_05_jobs_list() {
|
||||||
|
println!("\n🧪 Test: jobs.list");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Create a job first
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
let job = create_test_job("print('list test');");
|
||||||
|
let _ = client.jobs_create(job).await;
|
||||||
|
|
||||||
|
// List all jobs
|
||||||
|
let result = client.jobs_list().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "jobs.list should succeed");
|
||||||
|
let jobs = result.unwrap();
|
||||||
|
|
||||||
|
println!("✅ jobs.list works - found {} jobs", jobs.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_06_job_run_simple() {
|
||||||
|
println!("\n🧪 Test: job.run (simple script)");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Ensure runner exists
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
|
||||||
|
// Run a simple job
|
||||||
|
let job = create_test_job(r#"
|
||||||
|
print("Hello from test!");
|
||||||
|
42
|
||||||
|
"#);
|
||||||
|
|
||||||
|
let result = client.job_run(job, Some(30)).await;
|
||||||
|
|
||||||
|
// Note: This will timeout if no runner is actually connected to Redis
|
||||||
|
// but we're testing the API call itself
|
||||||
|
match result {
|
||||||
|
Ok(response) => {
|
||||||
|
println!("✅ job.run works - job_id: {}, status: {}",
|
||||||
|
response.job_id, response.status);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("⚠️ job.run: {:?} (runner may not be connected)", e);
|
||||||
|
// This is expected if no actual runner is listening
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_07_job_status() {
|
||||||
|
println!("\n🧪 Test: job.status");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Create a job first
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
let job = create_test_job("print('status test');");
|
||||||
|
let job_id = client.jobs_create(job).await.expect("Failed to create job");
|
||||||
|
|
||||||
|
// Get job status
|
||||||
|
let result = client.job_status(&job_id).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "job.status should succeed");
|
||||||
|
let status = result.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(status.job_id, job_id);
|
||||||
|
println!("✅ job.status works - job: {}, status: {}",
|
||||||
|
status.job_id, status.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_08_job_get() {
|
||||||
|
println!("\n🧪 Test: job.get");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Create a job first
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
let original_job = create_test_job("print('get test');");
|
||||||
|
let job_id = client.jobs_create(original_job.clone()).await
|
||||||
|
.expect("Failed to create job");
|
||||||
|
|
||||||
|
// Get the job
|
||||||
|
let result = client.get_job(&job_id).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "job.get should succeed");
|
||||||
|
let job = result.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(job.id, job_id);
|
||||||
|
println!("✅ job.get works - retrieved job: {}", job.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_09_job_delete() {
|
||||||
|
println!("\n🧪 Test: job.delete");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Create a job first
|
||||||
|
let _ = client.register_runner(TEST_RUNNER_NAME).await;
|
||||||
|
let job = create_test_job("print('delete test');");
|
||||||
|
let job_id = client.jobs_create(job).await.expect("Failed to create job");
|
||||||
|
|
||||||
|
// Delete the job
|
||||||
|
let result = client.job_delete(&job_id).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "job.delete should succeed");
|
||||||
|
println!("✅ job.delete works - deleted job: {}", job_id);
|
||||||
|
|
||||||
|
// Verify it's gone
|
||||||
|
let get_result = client.get_job(&job_id).await;
|
||||||
|
assert!(get_result.is_err(), "Job should not exist after deletion");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_10_auth_verify() {
|
||||||
|
println!("\n🧪 Test: auth.verify");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
let result = client.auth_verify().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "auth.verify should succeed with valid key");
|
||||||
|
let auth_info = result.unwrap();
|
||||||
|
|
||||||
|
println!("✅ auth.verify works");
|
||||||
|
println!(" Scope: {}", auth_info.scope);
|
||||||
|
println!(" Name: {}", auth_info.name.unwrap_or_else(|| "N/A".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_11_auth_key_create() {
|
||||||
|
println!("\n🧪 Test: auth.key.create");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
let result = client.auth_create_key("test-key".to_string(), "user".to_string()).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "auth.key.create should succeed");
|
||||||
|
let api_key = result.unwrap();
|
||||||
|
|
||||||
|
assert!(!api_key.key.is_empty(), "Should return a key");
|
||||||
|
assert_eq!(api_key.name, "test-key");
|
||||||
|
assert_eq!(api_key.scope, "user");
|
||||||
|
|
||||||
|
println!("✅ auth.key.create works - created key: {}...",
|
||||||
|
&api_key.key[..api_key.key.len().min(8)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_12_auth_key_list() {
|
||||||
|
println!("\n🧪 Test: auth.key.list");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Create a key first
|
||||||
|
let _ = client.auth_create_key("list-test-key".to_string(), "user".to_string()).await;
|
||||||
|
|
||||||
|
let result = client.auth_list_keys().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "auth.key.list should succeed");
|
||||||
|
let keys = result.unwrap();
|
||||||
|
|
||||||
|
println!("✅ auth.key.list works - found {} keys", keys.len());
|
||||||
|
for key in &keys {
|
||||||
|
println!(" - {} ({}): {}...", key.name, key.scope,
|
||||||
|
&key.key[..key.key.len().min(8)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_13_auth_key_remove() {
|
||||||
|
println!("\n🧪 Test: auth.key.remove");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Create a key first
|
||||||
|
let api_key = client.auth_create_key("remove-test-key".to_string(), "user".to_string())
|
||||||
|
.await
|
||||||
|
.expect("Failed to create key");
|
||||||
|
|
||||||
|
// Remove it
|
||||||
|
let result = client.auth_remove_key(api_key.key.clone()).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "auth.key.remove should succeed");
|
||||||
|
let removed = result.unwrap();
|
||||||
|
|
||||||
|
assert!(removed, "Should return true when key is removed");
|
||||||
|
println!("✅ auth.key.remove works - removed key: {}...",
|
||||||
|
&api_key.key[..api_key.key.len().min(8)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_14_runner_remove() {
|
||||||
|
println!("\n🧪 Test: runner.remove");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// Register a runner to remove
|
||||||
|
let runner_name = "test-runner-to-remove";
|
||||||
|
let _ = client.register_runner(runner_name).await;
|
||||||
|
|
||||||
|
// Remove it
|
||||||
|
let result = client.remove_runner(runner_name).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "runner.remove should succeed");
|
||||||
|
println!("✅ runner.remove works - removed: {}", runner_name);
|
||||||
|
|
||||||
|
// Verify it's gone
|
||||||
|
let runners = client.list_runners().await.unwrap();
|
||||||
|
assert!(!runners.contains(&runner_name.to_string()),
|
||||||
|
"Runner should not exist after removal");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_15_supervisor_info() {
|
||||||
|
println!("\n🧪 Test: supervisor.info");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
let result = client.get_supervisor_info().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "supervisor.info should succeed");
|
||||||
|
let info = result.unwrap();
|
||||||
|
|
||||||
|
println!("✅ supervisor.info works");
|
||||||
|
println!(" Server URL: {}", info.server_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integration test that runs a complete workflow
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_99_complete_workflow() {
|
||||||
|
println!("\n🧪 Test: Complete Workflow");
|
||||||
|
|
||||||
|
let client = create_client().await;
|
||||||
|
|
||||||
|
// 1. Register runner
|
||||||
|
println!(" 1. Registering runner...");
|
||||||
|
let _ = client.register_runner("workflow-runner").await;
|
||||||
|
|
||||||
|
// 2. List runners
|
||||||
|
println!(" 2. Listing runners...");
|
||||||
|
let runners = client.list_runners().await.unwrap();
|
||||||
|
assert!(runners.contains(&"workflow-runner".to_string()));
|
||||||
|
|
||||||
|
// 3. Create API key
|
||||||
|
println!(" 3. Creating API key...");
|
||||||
|
let api_key = client.auth_create_key("workflow-key".to_string(), "user".to_string())
|
||||||
|
.await.unwrap();
|
||||||
|
|
||||||
|
// 4. Verify auth
|
||||||
|
println!(" 4. Verifying auth...");
|
||||||
|
let _ = client.auth_verify().await.unwrap();
|
||||||
|
|
||||||
|
// 5. Create job
|
||||||
|
println!(" 5. Creating job...");
|
||||||
|
let job = create_test_job("print('workflow test');");
|
||||||
|
let job_id = client.jobs_create(job).await.unwrap();
|
||||||
|
|
||||||
|
// 6. Get job status
|
||||||
|
println!(" 6. Getting job status...");
|
||||||
|
let status = client.job_status(&job_id).await.unwrap();
|
||||||
|
assert_eq!(status.job_id, job_id);
|
||||||
|
|
||||||
|
// 7. List all jobs
|
||||||
|
println!(" 7. Listing all jobs...");
|
||||||
|
let jobs = client.jobs_list().await.unwrap();
|
||||||
|
assert!(!jobs.is_empty());
|
||||||
|
|
||||||
|
// 8. Delete job
|
||||||
|
println!(" 8. Deleting job...");
|
||||||
|
let _ = client.job_delete(&job_id).await.unwrap();
|
||||||
|
|
||||||
|
// 9. Remove API key
|
||||||
|
println!(" 9. Removing API key...");
|
||||||
|
let _ = client.auth_remove_key(api_key.key).await.unwrap();
|
||||||
|
|
||||||
|
// 10. Remove runner
|
||||||
|
println!(" 10. Removing runner...");
|
||||||
|
let _ = client.remove_runner("workflow-runner").await.unwrap();
|
||||||
|
|
||||||
|
println!("✅ Complete workflow test passed!");
|
||||||
|
}
|
||||||
@@ -32,22 +32,22 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build UI
|
# # Build UI
|
||||||
printf "📦 UI (WASM)... "
|
# printf "📦 UI (WASM)... "
|
||||||
cd "$PROJECT_DIR/ui"
|
# cd "$PROJECT_DIR/ui"
|
||||||
|
|
||||||
if ! command -v trunk &> /dev/null; then
|
# if ! command -v trunk &> /dev/null; then
|
||||||
echo "⚠️ (trunk not installed)"
|
# echo "⚠️ (trunk not installed)"
|
||||||
echo " Install with: cargo install trunk"
|
# echo " Install with: cargo install trunk"
|
||||||
else
|
# else
|
||||||
if trunk build --release > /tmp/supervisor-build-ui.log 2>&1 & spinner $!; wait $!; then
|
# if trunk build --release > /tmp/supervisor-build-ui.log 2>&1 & spinner $!; wait $!; then
|
||||||
echo "✅"
|
# echo "✅"
|
||||||
else
|
# else
|
||||||
echo "❌"
|
# echo "❌"
|
||||||
echo " Error: Build failed. Run 'cd $PROJECT_DIR/ui && trunk build --release' for details"
|
# echo " Error: Build failed. Run 'cd $PROJECT_DIR/ui && trunk build --release' for details"
|
||||||
exit 1
|
# exit 1
|
||||||
fi
|
# fi
|
||||||
fi
|
# fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ All builds completed"
|
echo "✅ All builds completed"
|
||||||
138
scripts/run.sh
138
scripts/run.sh
@@ -3,60 +3,12 @@
|
|||||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
PROJECT_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
PROJECT_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||||
|
|
||||||
# Check for --kill-ports flag
|
|
||||||
if [ "$1" = "--kill-ports" ]; then
|
|
||||||
echo "Killing processes on ports..."
|
|
||||||
PORT="${PORT:-3030}"
|
|
||||||
ADMIN_UI_PORT="${ADMIN_UI_PORT:-8080}"
|
|
||||||
|
|
||||||
# Kill process on supervisor port
|
|
||||||
SUPERVISOR_PID=$(lsof -ti:$PORT)
|
|
||||||
if [ ! -z "$SUPERVISOR_PID" ]; then
|
|
||||||
kill -9 $SUPERVISOR_PID 2>/dev/null
|
|
||||||
echo " ✅ Killed process on port $PORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Kill process on admin UI port
|
|
||||||
UI_PID=$(lsof -ti:$ADMIN_UI_PORT)
|
|
||||||
if [ ! -z "$UI_PID" ]; then
|
|
||||||
kill -9 $UI_PID 2>/dev/null
|
|
||||||
echo " ✅ Killed process on port $ADMIN_UI_PORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Done"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
source "$SCRIPT_DIR/environment.sh"
|
source "$SCRIPT_DIR/environment.sh"
|
||||||
|
|
||||||
# Spinner function
|
# Build first
|
||||||
spinner() {
|
echo "🔨 Building supervisor..."
|
||||||
local pid=$1
|
"$SCRIPT_DIR/build.sh"
|
||||||
local delay=0.1
|
|
||||||
local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
|
||||||
while ps -p $pid > /dev/null 2>&1; do
|
|
||||||
local temp=${spinstr#?}
|
|
||||||
printf " [%c] " "$spinstr"
|
|
||||||
local spinstr=$temp${spinstr%"$temp"}
|
|
||||||
sleep $delay
|
|
||||||
printf "\b\b\b\b\b\b"
|
|
||||||
done
|
|
||||||
printf " \b\b\b\b"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Starting Hero Supervisor"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Build
|
|
||||||
printf "🔨 Building... "
|
|
||||||
if "$SCRIPT_DIR/build.sh" > /tmp/supervisor-run-build.log 2>&1 & spinner $!; wait $!; then
|
|
||||||
echo "✅"
|
|
||||||
else
|
|
||||||
echo "❌"
|
|
||||||
echo " Error: Build failed. Check /tmp/supervisor-run-build.log"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate required environment variables
|
# Validate required environment variables
|
||||||
if [ -z "$ADMIN_SECRETS" ]; then
|
if [ -z "$ADMIN_SECRETS" ]; then
|
||||||
@@ -65,29 +17,15 @@ if [ -z "$ADMIN_SECRETS" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set defaults
|
# Set defaults from env vars
|
||||||
REDIS_URL="${REDIS_URL:-redis://127.0.0.1:6379}"
|
REDIS_URL="${REDIS_URL:-redis://127.0.0.1:6379}"
|
||||||
PORT="${PORT:-3030}"
|
PORT="${PORT:-3030}"
|
||||||
BIND_ADDRESS="${BIND_ADDRESS:-127.0.0.1}"
|
BIND_ADDRESS="${BIND_ADDRESS:-127.0.0.1}"
|
||||||
ADMIN_UI_PORT="${ADMIN_UI_PORT:-8080}"
|
LOG_LEVEL="${LOG_LEVEL:-info}"
|
||||||
LOG_LEVEL="${LOG_LEVEL:-error}"
|
|
||||||
|
|
||||||
# Cleanup function
|
|
||||||
cleanup() {
|
|
||||||
echo ""
|
|
||||||
printf "🛑 Stopping... "
|
|
||||||
kill $(jobs -p) 2>/dev/null || true
|
|
||||||
echo "✅"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup SIGINT SIGTERM
|
|
||||||
|
|
||||||
# Start supervisor
|
|
||||||
printf "📡 Supervisor... "
|
|
||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
# Build command with flags
|
# Build command with flags from env vars
|
||||||
SUPERVISOR_CMD="target/release/supervisor --redis-url $REDIS_URL --port $PORT --bind-address $BIND_ADDRESS"
|
SUPERVISOR_CMD="target/release/supervisor --redis-url $REDIS_URL --port $PORT --bind-address $BIND_ADDRESS"
|
||||||
|
|
||||||
# Add admin secrets
|
# Add admin secrets
|
||||||
@@ -122,64 +60,12 @@ if [ ! -z "$RUNNERS" ]; then
|
|||||||
SUPERVISOR_CMD="$SUPERVISOR_CMD --runners $RUNNERS"
|
SUPERVISOR_CMD="$SUPERVISOR_CMD --runners $RUNNERS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RUST_LOG="$LOG_LEVEL" RUST_LOG_STYLE=never $SUPERVISOR_CMD > /tmp/supervisor-run.log 2>&1 &
|
|
||||||
SUPERVISOR_PID=$!
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if ! ps -p $SUPERVISOR_PID > /dev/null 2>&1; then
|
|
||||||
echo "❌"
|
|
||||||
echo " Error: Supervisor failed to start. Check /tmp/supervisor-run.log"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅"
|
|
||||||
|
|
||||||
# Start admin UI
|
|
||||||
printf "🎨 Admin UI... "
|
|
||||||
cd "$PROJECT_DIR/ui"
|
|
||||||
|
|
||||||
UI_STARTED=false
|
|
||||||
UI_ERROR=""
|
|
||||||
if ! command -v trunk &> /dev/null; then
|
|
||||||
echo "⚠️"
|
|
||||||
UI_ERROR="Trunk not installed. Run: cargo install trunk"
|
|
||||||
else
|
|
||||||
trunk serve --port "$ADMIN_UI_PORT" > /tmp/supervisor-ui.log 2>&1 &
|
|
||||||
ADMIN_UI_PID=$!
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Check if process is still running
|
|
||||||
if ps -p $ADMIN_UI_PID > /dev/null 2>&1; then
|
|
||||||
# Check for port binding errors in log
|
|
||||||
if grep -q "Address already in use" /tmp/supervisor-ui.log 2>/dev/null; then
|
|
||||||
echo "❌"
|
|
||||||
UI_ERROR="Port $ADMIN_UI_PORT already in use. Run: ./scripts/run.sh --kill-ports"
|
|
||||||
kill $ADMIN_UI_PID 2>/dev/null
|
|
||||||
else
|
|
||||||
echo "✅"
|
|
||||||
UI_STARTED=true
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "❌"
|
|
||||||
UI_ERROR="Failed to start"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "🚀 Starting Hero Supervisor"
|
||||||
echo "Hero Supervisor Running"
|
echo " Redis: $REDIS_URL"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo " Port: $PORT"
|
||||||
echo "📡 OpenRPC Server: http://$BIND_ADDRESS:$PORT"
|
echo " Log Level: $LOG_LEVEL"
|
||||||
if [ "$UI_STARTED" = true ]; then
|
|
||||||
echo "🎨 Admin UI: http://127.0.0.1:$ADMIN_UI_PORT"
|
|
||||||
else
|
|
||||||
echo "🎨 Admin UI: ❌ $UI_ERROR"
|
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Logs: tail -f /tmp/supervisor-run.log /tmp/supervisor-ui.log"
|
|
||||||
echo ""
|
|
||||||
echo "Press Ctrl+C to stop all services"
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
|
|
||||||
# Wait for processes
|
# Run supervisor directly with output visible
|
||||||
wait
|
exec env RUST_LOG="$LOG_LEVEL" RUST_LOG_STYLE=never $SUPERVISOR_CMD
|
||||||
210
ui/Cargo.lock
generated
210
ui/Cargo.lock
generated
@@ -103,6 +103,12 @@ version = "0.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
|
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.89"
|
version = "0.1.89"
|
||||||
@@ -426,6 +432,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -448,6 +455,17 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -1029,14 +1047,42 @@ dependencies = [
|
|||||||
"hyper-util",
|
"hyper-util",
|
||||||
"jsonrpsee",
|
"jsonrpsee",
|
||||||
"log",
|
"log",
|
||||||
|
"osiris-client",
|
||||||
"redis",
|
"redis",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tower",
|
"tower 0.4.13",
|
||||||
"tower-http",
|
"tower-http 0.5.2",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hero-supervisor"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.ourworld.tf/herocode/supervisor.git#4b516d9d7e38167d7c72feb070c325cd8136752a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"env_logger 0.10.2",
|
||||||
|
"hero-job",
|
||||||
|
"hero-job-client",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"jsonrpsee",
|
||||||
|
"log",
|
||||||
|
"redis",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"toml",
|
||||||
|
"tower 0.4.13",
|
||||||
|
"tower-http 0.5.2",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1050,7 +1096,37 @@ dependencies = [
|
|||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
"hero-job",
|
"hero-job",
|
||||||
"hero-job-client",
|
"hero-job-client",
|
||||||
"hero-supervisor",
|
"hero-supervisor 0.1.0",
|
||||||
|
"hex",
|
||||||
|
"indexmap",
|
||||||
|
"js-sys",
|
||||||
|
"jsonrpsee",
|
||||||
|
"log",
|
||||||
|
"secp256k1 0.29.1",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen 0.6.5",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hero-supervisor-openrpc-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.ourworld.tf/herocode/supervisor.git#4b516d9d7e38167d7c72feb070c325cd8136752a"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"console_log",
|
||||||
|
"env_logger 0.11.8",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"hero-job",
|
||||||
|
"hero-job-client",
|
||||||
|
"hero-supervisor 0.1.0 (git+https://git.ourworld.tf/herocode/supervisor.git)",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -1184,6 +1260,7 @@ version = "0.1.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
|
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1191,7 +1268,9 @@ dependencies = [
|
|||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.6.0",
|
"socket2 0.6.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -1371,6 +1450,22 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnet"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iri-string"
|
||||||
|
version = "0.7.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.16"
|
version = "0.4.16"
|
||||||
@@ -1508,7 +1603,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower 0.4.13",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -1549,7 +1644,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower 0.4.13",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1665,6 +1760,21 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "osiris-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"hero-supervisor-openrpc-client 0.1.0 (git+https://git.ourworld.tf/herocode/supervisor.git)",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -1900,9 +2010,11 @@ version = "0.25.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec"
|
checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"combine",
|
"combine",
|
||||||
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"itoa",
|
"itoa",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -1911,6 +2023,7 @@ dependencies = [
|
|||||||
"sha1_smol",
|
"sha1_smol",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-retry",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -1953,6 +2066,38 @@ version = "0.8.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tower 0.5.2",
|
||||||
|
"tower-http 0.6.6",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@@ -2345,7 +2490,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"gloo 0.11.0",
|
"gloo 0.11.0",
|
||||||
"hero-supervisor-openrpc-client",
|
"hero-supervisor-openrpc-client 0.1.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2380,6 +2525,15 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
@@ -2461,6 +2615,17 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-retry"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project",
|
||||||
|
"rand",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.2"
|
version = "0.26.2"
|
||||||
@@ -2564,6 +2729,21 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -2580,6 +2760,24 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body",
|
||||||
|
"iri-string",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower 0.5.2",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user