mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
fix: Cleanup by sending a command and kill when dropped (#1945)
* fix: Cleanup by sending a command and kill when dropped * chore: Fix the loadModule run command * fix: cleans up failed health * refactor long-running * chore: Fixes?" * refactor * run iso ci on pr * fix debuild * fix tests * switch to libc kill * kill process by parent * fix graceful shutdown * recurse submodules * fix compat build * feat: Add back in the timeout * chore: add the missing types for the unnstable * inherited logs Co-authored-by: J M <Blu-J@users.noreply.github.com> * fix deleted code Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: J M <Blu-J@users.noreply.github.com>
This commit is contained in:
10
.github/workflows/debian.yaml
vendored
10
.github/workflows/debian.yaml
vendored
@@ -16,6 +16,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: Start9Labs/embassy-os-deb
|
repository: Start9Labs/embassy-os-deb
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
path: embassyos-0.3.x
|
||||||
|
- run: |
|
||||||
|
cp -r debian embassyos-0.3.x/
|
||||||
|
VERSION=0.3.x ./control.sh
|
||||||
|
cp embassyos-0.3.x/backend/embassyd.service embassyos-0.3.x/debian/embassyos.embassyd.service
|
||||||
|
cp embassyos-0.3.x/backend/embassy-init.service embassyos-0.3.x/debian/embassyos.embassy-init.service
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODEJS_VERSION }}
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|||||||
5
.github/workflows/pureos-iso.yaml
vendored
5
.github/workflows/pureos-iso.yaml
vendored
@@ -7,7 +7,10 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
- next
|
||||||
- feature/iso-ci
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
dpkg:
|
dpkg:
|
||||||
|
|||||||
83
backend/Cargo.lock
generated
83
backend/Cargo.lock
generated
@@ -1281,6 +1281,10 @@ dependencies = [
|
|||||||
"async-stream",
|
"async-stream",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"futures",
|
"futures",
|
||||||
|
"helpers",
|
||||||
|
"imbl 2.0.0",
|
||||||
|
"nix 0.25.0",
|
||||||
|
"procfs",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -1289,6 +1293,7 @@ dependencies = [
|
|||||||
"tracing-error 0.2.0",
|
"tracing-error 0.2.0",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
"tracing-subscriber 0.3.16",
|
"tracing-subscriber 0.3.16",
|
||||||
|
"yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1382,6 +1387,27 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||||
|
dependencies = [
|
||||||
|
"errno-dragonfly",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno-dragonfly"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
@@ -1747,9 +1773,11 @@ dependencies = [
|
|||||||
"models",
|
"models",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2030,6 +2058,12 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
@@ -2336,6 +2370,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -2458,7 +2498,6 @@ dependencies = [
|
|||||||
"bollard",
|
"bollard",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"embassy_container_init",
|
|
||||||
"emver",
|
"emver",
|
||||||
"mbrman",
|
"mbrman",
|
||||||
"openssl",
|
"openssl",
|
||||||
@@ -3110,6 +3149,21 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "procfs"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dfb6451c91904606a1abe93e83a8ec851f45827fa84273f256ade45dc095818"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
|
"flate2",
|
||||||
|
"hex",
|
||||||
|
"lazy_static",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proptest"
|
name = "proptest"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -3452,7 +3506,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"yajrc",
|
"yajrc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3519,6 +3573,20 @@ dependencies = [
|
|||||||
"semver 1.0.14",
|
"semver 1.0.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.35.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.20.7"
|
version = "0.20.7"
|
||||||
@@ -5593,6 +5661,17 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yajrc"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/dr-bonez/yajrc.git?branch=develop#72a22f7ac2197d7a5cdce4be601cf20e5280eec5"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.5.7"
|
version = "1.5.7"
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use embassy::context::{DiagnosticContext, RpcContext};
|
use embassy::context::{DiagnosticContext, RpcContext};
|
||||||
|
|
||||||
use embassy::hostname::get_current_ip;
|
|
||||||
use embassy::net::embassy_service_http_server::EmbassyServiceHTTPServer;
|
use embassy::net::embassy_service_http_server::EmbassyServiceHTTPServer;
|
||||||
#[cfg(feature = "avahi")]
|
#[cfg(feature = "avahi")]
|
||||||
use embassy::net::mdns::MdnsController;
|
use embassy::net::mdns::MdnsController;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::net::{Ipv4Addr, SocketAddr};
|
use std::net::Ipv4Addr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bollard::image::ListImagesOptions;
|
use bollard::image::ListImagesOptions;
|
||||||
use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier};
|
use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
|
pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
|
||||||
pub const COMMUNITY_MARKETPLACE: &str = "https://community-registry.start9.com";
|
pub const COMMUNITY_MARKETPLACE: &str = "https://community-registry.start9.com";
|
||||||
pub const BUFFER_SIZE: usize = 1024;
|
pub const BUFFER_SIZE: usize = 1024;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,18 @@
|
|||||||
use std::convert::TryInto;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
use super::{pause, resume, start, stop, ManagerSharedState, PersistantContainer, Status};
|
use super::{pause, resume, start, stop, ManagerSharedState, Status};
|
||||||
use crate::status::MainStatus;
|
use crate::status::MainStatus;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// Allocates a db handle. DO NOT CALL with a db handle already in scope
|
/// Allocates a db handle. DO NOT CALL with a db handle already in scope
|
||||||
async fn synchronize_once(
|
async fn synchronize_once(shared: &ManagerSharedState) -> Result<Status, Error> {
|
||||||
shared: &ManagerSharedState,
|
let mut db = shared.seed.ctx.db.handle();
|
||||||
persistant_container: Arc<PersistantContainer>,
|
|
||||||
) -> Result<Status, Error> {
|
|
||||||
let mut db = shared.ctx.db.handle();
|
|
||||||
let mut status = crate::db::DatabaseModel::new()
|
let mut status = crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(&shared.manifest.id)
|
.idx_model(&shared.seed.manifest.id)
|
||||||
.expect(&mut db)
|
.expect(&mut db)
|
||||||
.await?
|
.await?
|
||||||
.installed()
|
.installed()
|
||||||
@@ -27,7 +22,7 @@ async fn synchronize_once(
|
|||||||
.main()
|
.main()
|
||||||
.get_mut(&mut db)
|
.get_mut(&mut db)
|
||||||
.await?;
|
.await?;
|
||||||
let manager_status = shared.status.load(Ordering::SeqCst).try_into().unwrap();
|
let manager_status = *shared.status.1.borrow();
|
||||||
match manager_status {
|
match manager_status {
|
||||||
Status::Stopped => match &mut *status {
|
Status::Stopped => match &mut *status {
|
||||||
MainStatus::Stopped => (),
|
MainStatus::Stopped => (),
|
||||||
@@ -48,16 +43,16 @@ async fn synchronize_once(
|
|||||||
},
|
},
|
||||||
Status::Starting => match *status {
|
Status::Starting => match *status {
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
||||||
stop(shared, persistant_container).await?;
|
stop(shared).await?;
|
||||||
}
|
}
|
||||||
MainStatus::Starting { .. } | MainStatus::Running { .. } => (),
|
MainStatus::Starting { .. } | MainStatus::Running { .. } => (),
|
||||||
MainStatus::BackingUp { .. } => {
|
MainStatus::BackingUp { .. } => {
|
||||||
pause(shared, persistant_container).await?;
|
pause(shared).await?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Status::Running => match *status {
|
Status::Running => match *status {
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
||||||
stop(shared, persistant_container).await?;
|
stop(shared).await?;
|
||||||
}
|
}
|
||||||
MainStatus::Starting { .. } => {
|
MainStatus::Starting { .. } => {
|
||||||
*status = MainStatus::Running {
|
*status = MainStatus::Running {
|
||||||
@@ -67,12 +62,12 @@ async fn synchronize_once(
|
|||||||
}
|
}
|
||||||
MainStatus::Running { .. } => (),
|
MainStatus::Running { .. } => (),
|
||||||
MainStatus::BackingUp { .. } => {
|
MainStatus::BackingUp { .. } => {
|
||||||
pause(shared, persistant_container).await?;
|
pause(shared).await?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Status::Paused => match *status {
|
Status::Paused => match *status {
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
||||||
stop(shared, persistant_container).await?;
|
stop(shared).await?;
|
||||||
}
|
}
|
||||||
MainStatus::Starting { .. } | MainStatus::Running { .. } => {
|
MainStatus::Starting { .. } | MainStatus::Running { .. } => {
|
||||||
resume(shared).await?;
|
resume(shared).await?;
|
||||||
@@ -85,21 +80,20 @@ async fn synchronize_once(
|
|||||||
Ok(manager_status)
|
Ok(manager_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn synchronizer(
|
pub async fn synchronizer(shared: &ManagerSharedState) {
|
||||||
shared: &ManagerSharedState,
|
let mut status_recv = shared.status.0.subscribe();
|
||||||
persistant_container: Arc<PersistantContainer>,
|
|
||||||
) {
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = tokio::time::sleep(Duration::from_secs(5)) => (),
|
_ = tokio::time::sleep(Duration::from_secs(5)) => (),
|
||||||
_ = shared.synchronize_now.notified() => (),
|
_ = shared.synchronize_now.notified() => (),
|
||||||
|
_ = status_recv.changed() => (),
|
||||||
}
|
}
|
||||||
let status = match synchronize_once(shared, persistant_container.clone()).await {
|
let status = match synchronize_once(shared).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Synchronizer for {}@{} failed: {}",
|
"Synchronizer for {}@{} failed: {}",
|
||||||
shared.manifest.id,
|
shared.seed.manifest.id,
|
||||||
shared.manifest.version,
|
shared.seed.manifest.version,
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
tracing::debug!("{:?}", e);
|
tracing::debug!("{:?}", e);
|
||||||
@@ -107,7 +101,7 @@ pub async fn synchronizer(
|
|||||||
}
|
}
|
||||||
Ok(status) => status,
|
Ok(status) => status,
|
||||||
};
|
};
|
||||||
tracing::trace!("{} status synchronized", shared.manifest.id);
|
tracing::trace!("{} status synchronized", shared.seed.manifest.id);
|
||||||
shared.synchronized.notify_waiters();
|
shared.synchronized.notify_waiters();
|
||||||
match status {
|
match status {
|
||||||
Status::Shutdown => {
|
Status::Shutdown => {
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ impl ResolvesServerCert for EmbassyCertResolver {
|
|||||||
|
|
||||||
match hostname_raw {
|
match hostname_raw {
|
||||||
Some(hostname_str) => {
|
Some(hostname_str) => {
|
||||||
|
|
||||||
let full_fqdn = match ResourceFqdn::from_str(hostname_str) {
|
let full_fqdn = match ResourceFqdn::from_str(hostname_str) {
|
||||||
Ok(fqdn) => fqdn,
|
Ok(fqdn) => fqdn,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|||||||
@@ -7,22 +7,20 @@ use indexmap::IndexSet;
|
|||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
|
|
||||||
use self::interface::InterfaceId;
|
use self::interface::InterfaceId;
|
||||||
|
|
||||||
use crate::net::interface::LanPortConfig;
|
use crate::net::interface::LanPortConfig;
|
||||||
|
|
||||||
use crate::util::serde::Port;
|
use crate::util::serde::Port;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
pub mod cert_resolver;
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
|
pub mod embassy_service_http_server;
|
||||||
pub mod interface;
|
pub mod interface;
|
||||||
#[cfg(feature = "avahi")]
|
#[cfg(feature = "avahi")]
|
||||||
pub mod mdns;
|
pub mod mdns;
|
||||||
pub mod embassy_service_http_server;
|
|
||||||
pub mod net_controller;
|
pub mod net_controller;
|
||||||
pub mod net_utils;
|
pub mod net_utils;
|
||||||
pub mod proxy_controller;
|
pub mod proxy_controller;
|
||||||
pub mod ssl;
|
pub mod ssl;
|
||||||
pub mod cert_resolver;
|
|
||||||
pub mod static_server;
|
pub mod static_server;
|
||||||
pub mod tor;
|
pub mod tor;
|
||||||
pub mod vhost_controller;
|
pub mod vhost_controller;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::hostname::{get_current_ip, get_embassyd_tor_addr, get_hostname, HostNameReceipt};
|
use crate::hostname::{get_embassyd_tor_addr, get_hostname, HostNameReceipt};
|
||||||
use crate::net::dns::DnsController;
|
use crate::net::dns::DnsController;
|
||||||
use crate::net::interface::{Interface, TorConfig};
|
use crate::net::interface::{Interface, TorConfig};
|
||||||
#[cfg(feature = "avahi")]
|
#[cfg(feature = "avahi")]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use tokio::net::TcpStream;
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{error, info, instrument};
|
use tracing::{error, info, instrument};
|
||||||
|
|
||||||
use crate::net::net_utils::{host_addr_fqdn, ResourceFqdn};
|
use crate::net::net_utils::ResourceFqdn;
|
||||||
use crate::net::ssl::SslManager;
|
use crate::net::ssl::SslManager;
|
||||||
use crate::net::vhost_controller::VHOSTController;
|
use crate::net::vhost_controller::VHOSTController;
|
||||||
use crate::net::{HttpClient, HttpHandler, InterfaceMetadata, PackageNetInfo};
|
use crate::net::{HttpClient, HttpHandler, InterfaceMetadata, PackageNetInfo};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use digest::Digest;
|
|||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use http::response::Builder;
|
use http::response::Builder;
|
||||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
|
|
||||||
use rpc_toolkit::rpc_handler;
|
use rpc_toolkit::rpc_handler;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||||
|
|||||||
@@ -377,8 +377,7 @@ pub async fn tor_health_check(client: &Client, tor_controller: &TorController) {
|
|||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
let mut num_attempt = 1;
|
let mut num_attempt = 1;
|
||||||
tracing::error!(
|
tracing::error!("Unable to reach self over tor, we will retry now...");
|
||||||
"Unable to reach self over tor, we will retry now...");
|
|
||||||
tracing::error!("The first TOR error: {}", e);
|
tracing::error!("The first TOR error: {}", e);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ use std::net::SocketAddr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio_rustls::rustls::ServerConfig;
|
use tokio_rustls::rustls::ServerConfig;
|
||||||
use crate::net::cert_resolver::EmbassyCertResolver;
|
|
||||||
use crate::net::embassy_service_http_server::{EmbassyServiceHTTPServer};
|
|
||||||
|
|
||||||
|
use crate::net::cert_resolver::EmbassyCertResolver;
|
||||||
|
use crate::net::embassy_service_http_server::EmbassyServiceHTTPServer;
|
||||||
|
use crate::net::net_utils::ResourceFqdn;
|
||||||
use crate::net::HttpHandler;
|
use crate::net::HttpHandler;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::net::net_utils::ResourceFqdn;
|
|
||||||
|
|
||||||
pub struct VHOSTController {
|
pub struct VHOSTController {
|
||||||
pub service_servers: BTreeMap<u16, EmbassyServiceHTTPServer>,
|
pub service_servers: BTreeMap<u16, EmbassyServiceHTTPServer>,
|
||||||
@@ -67,7 +67,6 @@ impl VHOSTController {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let mut new_service_server =
|
let mut new_service_server =
|
||||||
EmbassyServiceHTTPServer::new(self.embassyd_addr.ip(), external_svc_port, ssl_cfg)
|
EmbassyServiceHTTPServer::new(self.embassyd_addr.ip(), external_svc_port, ssl_cfg)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -9,19 +9,15 @@ use async_stream::stream;
|
|||||||
use bollard::container::RemoveContainerOptions;
|
use bollard::container::RemoveContainerOptions;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use embassy_container_init::{InputJsonRpc, OutputJsonRpc};
|
|
||||||
use futures::future::Either as EitherFuture;
|
use futures::future::Either as EitherFuture;
|
||||||
use futures::{Stream, StreamExt, TryFutureExt, TryStreamExt};
|
use futures::TryStreamExt;
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::{NonDetachingJoinHandle, RpcClient};
|
||||||
use nix::sys::signal;
|
use nix::sys::signal;
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::{
|
use tokio::io::{AsyncBufRead, AsyncBufReadExt, BufReader};
|
||||||
io::{AsyncBufRead, AsyncBufReadExt, BufReader},
|
|
||||||
process::Child,
|
|
||||||
sync::mpsc::UnboundedReceiver,
|
|
||||||
};
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::ProcedureName;
|
use super::ProcedureName;
|
||||||
@@ -70,6 +66,57 @@ pub struct DockerContainer {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub system: bool,
|
pub system: bool,
|
||||||
}
|
}
|
||||||
|
impl DockerContainer {
|
||||||
|
/// We created a new exec runner, where we are going to be passing the commands for it to run.
|
||||||
|
/// Idea is that we are going to send it command and get the inputs be filtered back from the manager.
|
||||||
|
/// Then we could in theory run commands without the cost of running the docker exec which is known to have
|
||||||
|
/// a dely of > 200ms which is not acceptable.
|
||||||
|
#[instrument(skip(ctx))]
|
||||||
|
pub async fn long_running_execute(
|
||||||
|
&self,
|
||||||
|
ctx: &RpcContext,
|
||||||
|
pkg_id: &PackageId,
|
||||||
|
pkg_version: &Version,
|
||||||
|
volumes: &Volumes,
|
||||||
|
) -> Result<(LongRunning, RpcClient), Error> {
|
||||||
|
let container_name = DockerProcedure::container_name(pkg_id, None);
|
||||||
|
|
||||||
|
let mut cmd = LongRunning::setup_long_running_docker_cmd(
|
||||||
|
self,
|
||||||
|
ctx,
|
||||||
|
&container_name,
|
||||||
|
volumes,
|
||||||
|
pkg_id,
|
||||||
|
pkg_version,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut handle = cmd.spawn().with_kind(crate::ErrorKind::Docker)?;
|
||||||
|
|
||||||
|
let client =
|
||||||
|
if let (Some(stdin), Some(stdout)) = (handle.stdin.take(), handle.stdout.take()) {
|
||||||
|
RpcClient::new(stdin, stdout)
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("No stdin/stdout handle for container init"),
|
||||||
|
crate::ErrorKind::Incoherent,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let running_output = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
|
if let Err(err) = handle
|
||||||
|
.wait()
|
||||||
|
.await
|
||||||
|
.map_err(|e| eyre!("Runtime error: {e:?}"))
|
||||||
|
{
|
||||||
|
tracing::error!("{}", err);
|
||||||
|
tracing::debug!("{:?}", err);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok((LongRunning { running_output }, client))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@@ -122,23 +169,6 @@ impl DockerProcedure {
|
|||||||
shm_size_mb: container.shm_size_mb,
|
shm_size_mb: container.shm_size_mb,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "js_engine")]
|
|
||||||
pub fn main_docker_procedure_js(
|
|
||||||
container: &DockerContainer,
|
|
||||||
_procedure: &super::js_scripts::JsProcedure,
|
|
||||||
) -> DockerProcedure {
|
|
||||||
DockerProcedure {
|
|
||||||
image: container.image.clone(),
|
|
||||||
system: container.system,
|
|
||||||
entrypoint: "sleep".to_string(),
|
|
||||||
args: Vec::new(),
|
|
||||||
inject: false,
|
|
||||||
mounts: container.mounts.clone(),
|
|
||||||
io_format: None,
|
|
||||||
sigterm_timeout: container.sigterm_timeout,
|
|
||||||
shm_size_mb: container.shm_size_mb,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate(
|
pub fn validate(
|
||||||
&self,
|
&self,
|
||||||
@@ -346,64 +376,6 @@ impl DockerProcedure {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We created a new exec runner, where we are going to be passing the commands for it to run.
|
|
||||||
/// Idea is that we are going to send it command and get the inputs be filtered back from the manager.
|
|
||||||
/// Then we could in theory run commands without the cost of running the docker exec which is known to have
|
|
||||||
/// a dely of > 200ms which is not acceptable.
|
|
||||||
#[instrument(skip(ctx, input))]
|
|
||||||
pub async fn long_running_execute<S>(
|
|
||||||
&self,
|
|
||||||
ctx: &RpcContext,
|
|
||||||
pkg_id: &PackageId,
|
|
||||||
pkg_version: &Version,
|
|
||||||
name: ProcedureName,
|
|
||||||
volumes: &Volumes,
|
|
||||||
input: S,
|
|
||||||
) -> Result<LongRunning, Error>
|
|
||||||
where
|
|
||||||
S: Stream<Item = InputJsonRpc> + Send + 'static,
|
|
||||||
{
|
|
||||||
let name = name.docker_name();
|
|
||||||
let name: Option<&str> = name.as_deref();
|
|
||||||
let container_name = Self::container_name(pkg_id, name);
|
|
||||||
|
|
||||||
let mut cmd = LongRunning::setup_long_running_docker_cmd(
|
|
||||||
self,
|
|
||||||
ctx,
|
|
||||||
&container_name,
|
|
||||||
volumes,
|
|
||||||
pkg_id,
|
|
||||||
pkg_version,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut handle = cmd.spawn().with_kind(crate::ErrorKind::Docker)?;
|
|
||||||
let input_handle = LongRunning::spawn_input_handle(&mut handle, input)?
|
|
||||||
.map_err(|e| eyre!("Input Handle Error: {e:?}"));
|
|
||||||
|
|
||||||
let (output, output_handle) = LongRunning::spawn_output_handle(&mut handle)?;
|
|
||||||
let output_handle = output_handle.map_err(|e| eyre!("Output Handle Error: {e:?}"));
|
|
||||||
let err_handle = LongRunning::spawn_error_handle(&mut handle)?
|
|
||||||
.map_err(|e| eyre!("Err Handle Error: {e:?}"));
|
|
||||||
|
|
||||||
let running_output = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
|
||||||
if let Err(err) = tokio::select!(
|
|
||||||
x = handle.wait().map_err(|e| eyre!("Runtime error: {e:?}")) => x.map(|_| ()),
|
|
||||||
x = err_handle => x.map(|_| ()),
|
|
||||||
x = output_handle => x.map(|_| ()),
|
|
||||||
x = input_handle => x.map(|_| ())
|
|
||||||
) {
|
|
||||||
tracing::debug!("{:?}", err);
|
|
||||||
tracing::error!("Join error");
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(LongRunning {
|
|
||||||
output,
|
|
||||||
running_output,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(_ctx, input))]
|
#[instrument(skip(_ctx, input))]
|
||||||
pub async fn inject<I: Serialize, O: DeserializeOwned>(
|
pub async fn inject<I: Serialize, O: DeserializeOwned>(
|
||||||
&self,
|
&self,
|
||||||
@@ -788,13 +760,12 @@ impl<T> RingVec<T> {
|
|||||||
/// We wanted a long running since we want to be able to have the equivelent to the docker execute without the heavy costs of 400 + ms time lag.
|
/// We wanted a long running since we want to be able to have the equivelent to the docker execute without the heavy costs of 400 + ms time lag.
|
||||||
/// Also the long running let's us have the ability to start/ end the services quicker.
|
/// Also the long running let's us have the ability to start/ end the services quicker.
|
||||||
pub struct LongRunning {
|
pub struct LongRunning {
|
||||||
pub output: UnboundedReceiver<OutputJsonRpc>,
|
|
||||||
pub running_output: NonDetachingJoinHandle<()>,
|
pub running_output: NonDetachingJoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LongRunning {
|
impl LongRunning {
|
||||||
async fn setup_long_running_docker_cmd(
|
async fn setup_long_running_docker_cmd(
|
||||||
docker: &DockerProcedure,
|
docker: &DockerContainer,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
container_name: &str,
|
container_name: &str,
|
||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
@@ -865,7 +836,7 @@ impl LongRunning {
|
|||||||
cmd.arg(docker.image.for_package(pkg_id, Some(pkg_version)));
|
cmd.arg(docker.image.for_package(pkg_id, Some(pkg_version)));
|
||||||
}
|
}
|
||||||
cmd.stdout(std::process::Stdio::piped());
|
cmd.stdout(std::process::Stdio::piped());
|
||||||
cmd.stderr(std::process::Stdio::piped());
|
cmd.stderr(std::process::Stdio::inherit());
|
||||||
cmd.stdin(std::process::Stdio::piped());
|
cmd.stdin(std::process::Stdio::piped());
|
||||||
Ok(cmd)
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
@@ -894,104 +865,6 @@ impl LongRunning {
|
|||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn spawn_input_handle<S>(
|
|
||||||
handle: &mut Child,
|
|
||||||
input: S,
|
|
||||||
) -> Result<NonDetachingJoinHandle<()>, Error>
|
|
||||||
where
|
|
||||||
S: Stream<Item = InputJsonRpc> + Send + 'static,
|
|
||||||
{
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
let mut stdin = handle
|
|
||||||
.stdin
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| eyre!("Can't takeout stdin"))
|
|
||||||
.with_kind(crate::ErrorKind::Docker)?;
|
|
||||||
let handle = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
|
||||||
let input = input;
|
|
||||||
tokio::pin!(input);
|
|
||||||
while let Some(input) = input.next().await {
|
|
||||||
let input = match serde_json::to_string(&input) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
tracing::error!("Docker Input Serialization issue");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = stdin.write_all(format!("{input}\n").as_bytes()).await {
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
tracing::error!("Docker Input issue");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
Ok(handle)
|
|
||||||
}
|
|
||||||
fn spawn_error_handle(handle: &mut Child) -> Result<NonDetachingJoinHandle<()>, Error> {
|
|
||||||
let id = handle.id();
|
|
||||||
let mut output = tokio::io::BufReader::new(
|
|
||||||
handle
|
|
||||||
.stderr
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| eyre!("Can't takeout stderr"))
|
|
||||||
.with_kind(crate::ErrorKind::Docker)?,
|
|
||||||
)
|
|
||||||
.lines();
|
|
||||||
Ok(NonDetachingJoinHandle::from(tokio::spawn(async move {
|
|
||||||
while let Ok(Some(line)) = output.next_line().await {
|
|
||||||
tracing::debug!("{:?}", id);
|
|
||||||
tracing::error!("Error from long running container");
|
|
||||||
tracing::error!("{}", line);
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_output_handle(
|
|
||||||
handle: &mut Child,
|
|
||||||
) -> Result<(UnboundedReceiver<OutputJsonRpc>, NonDetachingJoinHandle<()>), Error> {
|
|
||||||
let mut output = tokio::io::BufReader::new(
|
|
||||||
handle
|
|
||||||
.stdout
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| eyre!("Can't takeout stdout for long running"))
|
|
||||||
.with_kind(crate::ErrorKind::Docker)?,
|
|
||||||
)
|
|
||||||
.lines();
|
|
||||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<OutputJsonRpc>();
|
|
||||||
Ok((
|
|
||||||
receiver,
|
|
||||||
NonDetachingJoinHandle::from(tokio::spawn(async move {
|
|
||||||
loop {
|
|
||||||
let next = output.next_line().await;
|
|
||||||
let next = match next {
|
|
||||||
Ok(Some(a)) => a,
|
|
||||||
Ok(None) => {
|
|
||||||
tracing::error!("The docker pipe is closed?");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
tracing::error!("Output from docker, killing");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let next = match serde_json::from_str(&next) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(_e) => {
|
|
||||||
tracing::trace!("Could not decode output from long running binary");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = sender.send(next) {
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
tracing::error!("Could no longer send output");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async fn buf_reader_to_lines(
|
async fn buf_reader_to_lines(
|
||||||
reader: impl AsyncBufRead + Unpin,
|
reader: impl AsyncBufRead + Unpin,
|
||||||
|
|||||||
@@ -2,17 +2,20 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
|
use embassy_container_init::{ProcessGroupId, SignalGroup, SignalGroupParams};
|
||||||
|
use helpers::RpcClient;
|
||||||
pub use js_engine::JsError;
|
pub use js_engine::JsError;
|
||||||
use js_engine::{JsExecutionEnvironment, PathForVolumeId};
|
use js_engine::{JsExecutionEnvironment, PathForVolumeId};
|
||||||
use models::VolumeId;
|
use models::{ErrorKind, VolumeId};
|
||||||
use models::{ExecCommand, TermCommand};
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::ProcedureName;
|
use super::ProcedureName;
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::Version;
|
use crate::util::{GeneralGuard, Version};
|
||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
@@ -42,7 +45,7 @@ impl PathForVolumeId for Volumes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct JsProcedure {
|
pub struct JsProcedure {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -54,7 +57,7 @@ impl JsProcedure {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(directory, input, exec_command, term_command))]
|
#[instrument(skip(directory, input, rpc_client))]
|
||||||
pub async fn execute<I: Serialize, O: DeserializeOwned>(
|
pub async fn execute<I: Serialize, O: DeserializeOwned>(
|
||||||
&self,
|
&self,
|
||||||
directory: &PathBuf,
|
directory: &PathBuf,
|
||||||
@@ -64,17 +67,32 @@ impl JsProcedure {
|
|||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
exec_command: ExecCommand,
|
gid: ProcessGroupId,
|
||||||
term_command: TermCommand,
|
rpc_client: Option<Arc<RpcClient>>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
Ok(async move {
|
let cleaner_client = rpc_client.clone();
|
||||||
|
let cleaner = GeneralGuard::new(move || {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Some(client) = cleaner_client {
|
||||||
|
client
|
||||||
|
.request(SignalGroup, SignalGroupParams { gid, signal: 9 })
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::new(eyre!("{}: {:?}", e.message, e.data), ErrorKind::Docker)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let res = async move {
|
||||||
let running_action = JsExecutionEnvironment::load_from_package(
|
let running_action = JsExecutionEnvironment::load_from_package(
|
||||||
directory,
|
directory,
|
||||||
pkg_id,
|
pkg_id,
|
||||||
pkg_version,
|
pkg_version,
|
||||||
Box::new(volumes.clone()),
|
Box::new(volumes.clone()),
|
||||||
exec_command,
|
gid,
|
||||||
term_command,
|
rpc_client,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.run_action(name, input, self.args.clone());
|
.run_action(name, input, self.args.clone());
|
||||||
@@ -88,7 +106,9 @@ impl JsProcedure {
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(|(error, message)| (error.as_code_num(), message)))
|
.map_err(|(error, message)| (error.as_code_num(), message));
|
||||||
|
cleaner.drop().await.unwrap()?;
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(ctx, input))]
|
#[instrument(skip(ctx, input))]
|
||||||
@@ -108,12 +128,8 @@ impl JsProcedure {
|
|||||||
pkg_id,
|
pkg_id,
|
||||||
pkg_version,
|
pkg_version,
|
||||||
Box::new(volumes.clone()),
|
Box::new(volumes.clone()),
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async { Err("Can't run commands in sandox mode".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| {
|
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.read_only_effects()
|
.read_only_effects()
|
||||||
@@ -193,10 +209,8 @@ async fn js_action_execute() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -252,10 +266,8 @@ async fn js_action_execute_error() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -300,10 +312,8 @@ async fn js_action_fetch() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -350,14 +360,9 @@ async fn js_test_slow() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
) => { a.unwrap().unwrap(); },
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
|
||||||
=> {a
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();},
|
|
||||||
_ = tokio::time::sleep(Duration::from_secs(1)) => ()
|
_ = tokio::time::sleep(Duration::from_secs(1)) => ()
|
||||||
}
|
}
|
||||||
tracing::debug!("testing end should");
|
tracing::debug!("testing end should");
|
||||||
@@ -404,10 +409,8 @@ async fn js_action_var_arg() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -452,10 +455,8 @@ async fn js_action_test_rename() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -500,10 +501,8 @@ async fn js_action_test_deep_dir() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -547,10 +546,8 @@ async fn js_action_test_deep_dir_escape() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -595,10 +592,8 @@ async fn js_rsync() {
|
|||||||
&volumes,
|
&volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
Arc::new(|_, _, _, _| {
|
ProcessGroupId(0),
|
||||||
Box::pin(async move { Err("Can't run commands in test".to_string()) })
|
None,
|
||||||
}),
|
|
||||||
Arc::new(|_| Box::pin(async move { Err("Can't run commands in test".to_string()) })),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use patch_db::HasModel;
|
use patch_db::HasModel;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use self::docker::{DockerContainers, DockerProcedure};
|
use self::docker::{DockerContainers, DockerProcedure};
|
||||||
@@ -82,7 +83,7 @@ impl PackageProcedure {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "js_engine")]
|
#[cfg(feature = "js_engine")]
|
||||||
PackageProcedure::Script(procedure) => {
|
PackageProcedure::Script(procedure) => {
|
||||||
let exec_command = match ctx
|
let (gid, rpc_client) = match ctx
|
||||||
.managers
|
.managers
|
||||||
.get(&(pkg_id.clone(), pkg_version.clone()))
|
.get(&(pkg_id.clone(), pkg_version.clone()))
|
||||||
.await
|
.await
|
||||||
@@ -93,23 +94,16 @@ impl PackageProcedure {
|
|||||||
ErrorKind::NotFound,
|
ErrorKind::NotFound,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Some(x) => x,
|
Some(man) => (
|
||||||
}
|
if matches!(name, ProcedureName::Main) {
|
||||||
.exec_command();
|
man.new_main_gid()
|
||||||
let term_command = match ctx
|
} else {
|
||||||
.managers
|
man.new_gid()
|
||||||
.get(&(pkg_id.clone(), pkg_version.clone()))
|
},
|
||||||
.await
|
man.rpc_client(),
|
||||||
{
|
),
|
||||||
None => {
|
};
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("No manager found for {}", pkg_id),
|
|
||||||
ErrorKind::NotFound,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
}
|
|
||||||
.term_command();
|
|
||||||
procedure
|
procedure
|
||||||
.execute(
|
.execute(
|
||||||
&ctx.datadir,
|
&ctx.datadir,
|
||||||
@@ -119,77 +113,14 @@ impl PackageProcedure {
|
|||||||
volumes,
|
volumes,
|
||||||
input,
|
input,
|
||||||
timeout,
|
timeout,
|
||||||
exec_command,
|
gid,
|
||||||
term_command,
|
rpc_client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(ctx, input))]
|
|
||||||
pub async fn inject<I: Serialize, O: DeserializeOwned + 'static>(
|
|
||||||
&self,
|
|
||||||
ctx: &RpcContext,
|
|
||||||
pkg_id: &PackageId,
|
|
||||||
pkg_version: &Version,
|
|
||||||
name: ProcedureName,
|
|
||||||
volumes: &Volumes,
|
|
||||||
input: Option<I>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
|
||||||
match self {
|
|
||||||
PackageProcedure::Docker(procedure) => {
|
|
||||||
procedure
|
|
||||||
.inject(ctx, pkg_id, pkg_version, name, volumes, input, timeout)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
#[cfg(feature = "js_engine")]
|
|
||||||
PackageProcedure::Script(procedure) => {
|
|
||||||
let exec_command = match ctx
|
|
||||||
.managers
|
|
||||||
.get(&(pkg_id.clone(), pkg_version.clone()))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
None => {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("No manager found for {}", pkg_id),
|
|
||||||
ErrorKind::NotFound,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
}
|
|
||||||
.exec_command();
|
|
||||||
let term_command = match ctx
|
|
||||||
.managers
|
|
||||||
.get(&(pkg_id.clone(), pkg_version.clone()))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
None => {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("No manager found for {}", pkg_id),
|
|
||||||
ErrorKind::NotFound,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Some(x) => x,
|
|
||||||
}
|
|
||||||
.term_command();
|
|
||||||
procedure
|
|
||||||
.execute(
|
|
||||||
&ctx.datadir,
|
|
||||||
pkg_id,
|
|
||||||
pkg_version,
|
|
||||||
name,
|
|
||||||
volumes,
|
|
||||||
input,
|
|
||||||
timeout,
|
|
||||||
exec_command,
|
|
||||||
term_command,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[instrument(skip(ctx, input))]
|
#[instrument(skip(ctx, input))]
|
||||||
pub async fn sandboxed<I: Serialize, O: DeserializeOwned>(
|
pub async fn sandboxed<I: Serialize, O: DeserializeOwned>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use nom::combinator::success;
|
|
||||||
use sha2_old::{Digest, Sha512};
|
use sha2_old::{Digest, Sha512};
|
||||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom};
|
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use patch_db::HasModel;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use super::git_hash::GitHash;
|
||||||
use crate::action::Actions;
|
use crate::action::Actions;
|
||||||
use crate::backup::BackupActions;
|
use crate::backup::BackupActions;
|
||||||
use crate::config::action::ConfigActions;
|
use crate::config::action::ConfigActions;
|
||||||
@@ -20,8 +21,6 @@ use crate::version::{Current, VersionT};
|
|||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
use super::git_hash::GitHash;
|
|
||||||
|
|
||||||
fn current_version() -> Version {
|
fn current_version() -> Version {
|
||||||
Current::new().semver().into()
|
Current::new().semver().into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ use crate::sound::{
|
|||||||
CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4,
|
CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4,
|
||||||
};
|
};
|
||||||
use crate::update::latest_information::LatestInformation;
|
use crate::update::latest_information::LatestInformation;
|
||||||
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
use crate::{Error, ErrorKind, ResultExt, IS_RASPBERRY_PI};
|
use crate::{Error, ErrorKind, ResultExt, IS_RASPBERRY_PI};
|
||||||
@@ -250,7 +249,7 @@ impl EosUrl {
|
|||||||
};
|
};
|
||||||
Ok(format!("{host}::{version}/{arch}/")
|
Ok(format!("{host}::{version}/{arch}/")
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?)
|
.map_err(|_| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ use emver::VersionRange;
|
|||||||
|
|
||||||
use super::v0_3_0::V0_3_0_COMPAT;
|
use super::v0_3_0::V0_3_0_COMPAT;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::config::util::MergeWith;
|
||||||
config::util::MergeWith,
|
use crate::hostname::{generate_id, sync_hostname};
|
||||||
hostname::{generate_id, sync_hostname},
|
|
||||||
};
|
|
||||||
|
|
||||||
const V0_3_2: emver::Version = emver::Version::new(0, 3, 2, 0);
|
const V0_3_2: emver::Version = emver::Version::new(0, 3, 2, 0);
|
||||||
|
|
||||||
|
|||||||
170
libs/Cargo.lock
generated
170
libs/Cargo.lock
generated
@@ -200,6 +200,12 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitmaps"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitvec"
|
name = "bitvec"
|
||||||
version = "0.22.3"
|
version = "0.22.3"
|
||||||
@@ -312,8 +318,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"time",
|
||||||
|
"wasm-bindgen",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -424,6 +433,15 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff"
|
checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -787,6 +805,10 @@ dependencies = [
|
|||||||
"async-stream",
|
"async-stream",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"futures",
|
"futures",
|
||||||
|
"helpers",
|
||||||
|
"imbl 2.0.0",
|
||||||
|
"nix 0.25.0",
|
||||||
|
"procfs",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -795,6 +817,7 @@ dependencies = [
|
|||||||
"tracing-error 0.2.0",
|
"tracing-error 0.2.0",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
"tracing-subscriber 0.3.16",
|
"tracing-subscriber 0.3.16",
|
||||||
|
"yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -829,6 +852,27 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||||
|
dependencies = [
|
||||||
|
"errno-dragonfly",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno-dragonfly"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
@@ -863,6 +907,16 @@ dependencies = [
|
|||||||
"nix 0.24.2",
|
"nix 0.24.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -1138,9 +1192,11 @@ dependencies = [
|
|||||||
"models",
|
"models",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1322,7 +1378,7 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "543682c9082b25e63d03b5acbd65ad111fd49dd93e70843e5175db4ff81d606b"
|
checksum = "543682c9082b25e63d03b5acbd65ad111fd49dd93e70843e5175db4ff81d606b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitmaps",
|
"bitmaps 2.1.0",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"rand_xoshiro",
|
"rand_xoshiro",
|
||||||
"sized-chunks",
|
"sized-chunks",
|
||||||
@@ -1330,6 +1386,28 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imbl"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2806b69cd9f4664844027b64465eacb444c67c1db9c778e341adff0c25cdb0d"
|
||||||
|
dependencies = [
|
||||||
|
"bitmaps 3.2.0",
|
||||||
|
"imbl-sized-chunks",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"rand_xoshiro",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imbl-sized-chunks"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6957ea0b2541c5ca561d3ef4538044af79f8a05a1eb3a3b148936aaceaa1076"
|
||||||
|
dependencies = [
|
||||||
|
"bitmaps 3.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -1355,6 +1433,12 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.5.1"
|
version = "2.5.1"
|
||||||
@@ -1568,6 +1652,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -1679,7 +1769,6 @@ dependencies = [
|
|||||||
"bollard",
|
"bollard",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"embassy_container_init",
|
|
||||||
"emver",
|
"emver",
|
||||||
"mbrman",
|
"mbrman",
|
||||||
"openssl",
|
"openssl",
|
||||||
@@ -1744,6 +1833,20 @@ dependencies = [
|
|||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"memoffset",
|
||||||
|
"pin-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
@@ -1960,7 +2063,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"fd-lock-rs",
|
"fd-lock-rs",
|
||||||
"futures",
|
"futures",
|
||||||
"imbl",
|
"imbl 1.0.1",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"json-ptr",
|
"json-ptr",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -2131,6 +2234,21 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "procfs"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dfb6451c91904606a1abe93e83a8ec851f45827fa84273f256ade45dc095818"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
|
"flate2",
|
||||||
|
"hex",
|
||||||
|
"lazy_static",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
@@ -2352,7 +2470,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"yajrc",
|
"yajrc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2407,6 +2525,20 @@ dependencies = [
|
|||||||
"semver 1.0.14",
|
"semver 1.0.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.35.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.20.7"
|
version = "0.20.7"
|
||||||
@@ -2724,7 +2856,7 @@ version = "0.6.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
|
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitmaps",
|
"bitmaps 2.1.0",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3408,6 +3540,17 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -3797,6 +3940,12 @@ version = "0.9.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
@@ -4081,6 +4230,17 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yajrc"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/dr-bonez/yajrc.git?branch=develop#72a22f7ac2197d7a5cdce4be601cf20e5280eec5"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|||||||
@@ -11,17 +11,22 @@ unstable = []
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-stream = "0.3.*"
|
async-stream = "0.3"
|
||||||
color-eyre = "0.6.*"
|
color-eyre = "0.6"
|
||||||
futures = "0.3.*"
|
futures = "0.3"
|
||||||
serde = { version = "1.*", features = ["derive", "rc"] }
|
serde = { version = "1", features = ["derive", "rc"] }
|
||||||
serde_json = "1.*"
|
serde_json = "1"
|
||||||
tokio = { version = "1.*", features = ["full"] }
|
helpers = { path = "../helpers" }
|
||||||
|
imbl = "2"
|
||||||
|
nix = "0.25"
|
||||||
|
procfs = "0.14"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-stream = { version = "0.1.11" }
|
tokio-stream = { version = "0.1.11" }
|
||||||
tracing = "0.1.*"
|
tracing = "0.1"
|
||||||
tracing-error = "0.2.*"
|
tracing-error = "0.2"
|
||||||
tracing-futures = "0.2.*"
|
tracing-futures = "0.2"
|
||||||
tracing-subscriber = { version = "0.3.*", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
yajrc = { version = "*", git = "https://github.com/dr-bonez/yajrc.git", branch = "develop" }
|
||||||
|
|
||||||
[profile.test]
|
[profile.test]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
@@ -1,104 +1,173 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use nix::unistd::Pid;
|
||||||
use tracing::instrument;
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
use yajrc::RpcMethod;
|
||||||
|
|
||||||
/// The inputs that the executable is expecting
|
|
||||||
pub type InputJsonRpc = JsonRpc<Input>;
|
|
||||||
/// The outputs that the executable is expected to output
|
|
||||||
pub type OutputJsonRpc = JsonRpc<Output>;
|
|
||||||
|
|
||||||
/// Based on the jsonrpc spec, but we are limiting the rpc to a subset
|
|
||||||
#[derive(Debug, Serialize, Copy, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum RpcId {
|
|
||||||
UInt(u32),
|
|
||||||
}
|
|
||||||
/// Know what the process is called
|
/// Know what the process is called
|
||||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ProcessId(pub u32);
|
pub struct ProcessId(pub u32);
|
||||||
|
impl From<ProcessId> for Pid {
|
||||||
/// We use the JSON rpc as the format to share between the stdin and stdout for the executable.
|
fn from(pid: ProcessId) -> Self {
|
||||||
/// Note: We are not allowing the id to not exist, used to ensure all pairs of messages are tracked
|
Pid::from_raw(pid.0 as i32)
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
}
|
||||||
pub struct JsonRpc<T> {
|
}
|
||||||
id: RpcId,
|
impl From<Pid> for ProcessId {
|
||||||
#[serde(flatten)]
|
fn from(pid: Pid) -> Self {
|
||||||
pub version_rpc: VersionRpc<T>,
|
ProcessId(pid.as_raw() as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<i32> for ProcessId {
|
||||||
|
fn from(pid: i32) -> Self {
|
||||||
|
ProcessId(pid as u32)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[serde(tag = "jsonrpc", rename_all = "camelCase")]
|
pub struct ProcessGroupId(pub u32);
|
||||||
pub enum VersionRpc<T> {
|
|
||||||
#[serde(rename = "2.0")]
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
Two(T),
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum OutputStrategy {
|
||||||
|
Inherit,
|
||||||
|
Collect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> JsonRpc<T>
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct RunCommand;
|
||||||
|
impl Serialize for RunCommand {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
T: Serialize + for<'de> serde::Deserialize<'de> + std::fmt::Debug,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
/// Using this to simplify creating this nested struct. Used for creating input mostly for executable stdin
|
Serialize::serialize(Self.as_str(), serializer)
|
||||||
pub fn new(id: RpcId, body: T) -> Self {
|
|
||||||
JsonRpc {
|
|
||||||
id,
|
|
||||||
version_rpc: VersionRpc::Two(body),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Use this to get the data out of the probably destructed output
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub fn into_pair(self) -> (RpcId, T) {
|
pub struct RunCommandParams {
|
||||||
let Self { id, version_rpc } = self;
|
pub gid: Option<ProcessGroupId>,
|
||||||
let VersionRpc::Two(body) = version_rpc;
|
pub command: String,
|
||||||
(id, body)
|
pub args: Vec<String>,
|
||||||
}
|
pub output: OutputStrategy,
|
||||||
/// Used during the execution.
|
|
||||||
#[instrument]
|
|
||||||
pub fn maybe_serialize(&self) -> Option<String> {
|
|
||||||
match serde_json::to_string(self) {
|
|
||||||
Ok(x) => Some(x),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Could not stringify and skipping");
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Used during the execution
|
|
||||||
#[instrument]
|
|
||||||
pub fn maybe_parse(s: &str) -> Option<Self> {
|
|
||||||
match serde_json::from_str::<Self>(s) {
|
|
||||||
Ok(a) => Some(a),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Could not parse and skipping: {}", s);
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
impl RpcMethod for RunCommand {
|
||||||
|
type Params = RunCommandParams;
|
||||||
|
type Response = ProcessId;
|
||||||
|
fn as_str<'a>(&'a self) -> &'a str {
|
||||||
|
"command"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outputs embedded in the JSONRpc output of the executable.
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
pub struct ReadLineStdout;
|
||||||
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
|
impl Serialize for ReadLineStdout {
|
||||||
pub enum Output {
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
/// Will be called almost right away and only once per RpcId. Indicates what
|
where
|
||||||
/// was spawned in the container.
|
S: Serializer,
|
||||||
ProcessId(ProcessId),
|
{
|
||||||
/// This is the line buffered output of the command
|
Serialize::serialize(Self.as_str(), serializer)
|
||||||
Line(String),
|
}
|
||||||
/// This is some kind of error with the program
|
}
|
||||||
Error(String),
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
/// Indication that the command is done
|
pub struct ReadLineStdoutParams {
|
||||||
Done(Option<i32>),
|
pub pid: ProcessId,
|
||||||
|
}
|
||||||
|
impl RpcMethod for ReadLineStdout {
|
||||||
|
type Params = ReadLineStdoutParams;
|
||||||
|
type Response = String;
|
||||||
|
fn as_str<'a>(&'a self) -> &'a str {
|
||||||
|
"read-line-stdout"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
|
pub struct ReadLineStderr;
|
||||||
pub enum Input {
|
impl Serialize for ReadLineStderr {
|
||||||
/// Create a new command, with the args
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
Command { command: String, args: Vec<String> },
|
where
|
||||||
/// Send the sigkill to the process
|
S: Serializer,
|
||||||
Kill(),
|
{
|
||||||
/// Send the sigterm to the process
|
Serialize::serialize(Self.as_str(), serializer)
|
||||||
Term(),
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ReadLineStderrParams {
|
||||||
|
pub pid: ProcessId,
|
||||||
|
}
|
||||||
|
impl RpcMethod for ReadLineStderr {
|
||||||
|
type Params = ReadLineStderrParams;
|
||||||
|
type Response = String;
|
||||||
|
fn as_str<'a>(&'a self) -> &'a str {
|
||||||
|
"read-line-stderr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Output;
|
||||||
|
impl Serialize for Output {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
Serialize::serialize(Self.as_str(), serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct OutputParams {
|
||||||
|
pub pid: ProcessId,
|
||||||
|
}
|
||||||
|
impl RpcMethod for Output {
|
||||||
|
type Params = OutputParams;
|
||||||
|
type Response = String;
|
||||||
|
fn as_str<'a>(&'a self) -> &'a str {
|
||||||
|
"output"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SendSignal;
|
||||||
|
impl Serialize for SendSignal {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
Serialize::serialize(Self.as_str(), serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SendSignalParams {
|
||||||
|
pub pid: ProcessId,
|
||||||
|
pub signal: u32,
|
||||||
|
}
|
||||||
|
impl RpcMethod for SendSignal {
|
||||||
|
type Params = SendSignalParams;
|
||||||
|
type Response = ();
|
||||||
|
fn as_str<'a>(&'a self) -> &'a str {
|
||||||
|
"signal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SignalGroup;
|
||||||
|
impl Serialize for SignalGroup {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
Serialize::serialize(Self.as_str(), serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SignalGroupParams {
|
||||||
|
pub gid: ProcessGroupId,
|
||||||
|
pub signal: u32,
|
||||||
|
}
|
||||||
|
impl RpcMethod for SignalGroup {
|
||||||
|
type Params = SignalGroupParams;
|
||||||
|
type Response = ();
|
||||||
|
fn as_str<'a>(&'a self) -> &'a str {
|
||||||
|
"signal-group"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,78 +1,319 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::os::unix::process::ExitStatusExt;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_stream::stream;
|
|
||||||
use embassy_container_init::{
|
use embassy_container_init::{
|
||||||
Input, InputJsonRpc, JsonRpc, Output, OutputJsonRpc, ProcessId, RpcId,
|
OutputParams, OutputStrategy, ProcessGroupId, ProcessId, ReadLineStderrParams,
|
||||||
|
ReadLineStdoutParams, RunCommandParams, SendSignalParams, SignalGroupParams,
|
||||||
};
|
};
|
||||||
use futures::{pin_mut, Stream, StreamExt};
|
use futures::StreamExt;
|
||||||
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
use nix::errno::Errno;
|
||||||
|
use nix::sys::signal::Signal;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::{Child, Command};
|
use tokio::process::{Child, ChildStderr, ChildStdout, Command};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::{oneshot, Mutex};
|
use tokio::sync::{watch, Mutex};
|
||||||
use tracing::instrument;
|
use yajrc::{Id, RpcError};
|
||||||
|
|
||||||
const MAX_COMMANDS: usize = 10;
|
/// Outputs embedded in the JSONRpc output of the executable.
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
enum DoneProgramStatus {
|
#[serde(untagged)]
|
||||||
Wait(Result<std::process::ExitStatus, std::io::Error>),
|
enum Output {
|
||||||
Killed,
|
Command(ProcessId),
|
||||||
}
|
ReadLineStdout(String),
|
||||||
/// Created from the child and rpc, to prove that the cmd was the one who died
|
ReadLineStderr(String),
|
||||||
struct DoneProgram {
|
Output(String),
|
||||||
id: RpcId,
|
Signal,
|
||||||
status: DoneProgramStatus,
|
SignalGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to attach the running command with the rpc
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct ChildAndRpc {
|
#[serde(tag = "method", content = "params", rename_all = "kebab-case")]
|
||||||
id: RpcId,
|
enum Input {
|
||||||
child: Child,
|
/// Run a new command, with the args
|
||||||
|
Command(RunCommandParams),
|
||||||
|
// /// Get a line of stdout from the command
|
||||||
|
// ReadLineStdout(ReadLineStdoutParams),
|
||||||
|
// /// Get a line of stderr from the command
|
||||||
|
// ReadLineStderr(ReadLineStderrParams),
|
||||||
|
/// Get output of command
|
||||||
|
Output(OutputParams),
|
||||||
|
/// Send the sigterm to the process
|
||||||
|
Signal(SendSignalParams),
|
||||||
|
/// Signal a group of processes
|
||||||
|
SignalGroup(SignalGroupParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChildAndRpc {
|
#[derive(Deserialize)]
|
||||||
fn new(id: RpcId, mut command: tokio::process::Command) -> ::std::io::Result<Self> {
|
struct IncomingRpc {
|
||||||
Ok(Self {
|
id: Id,
|
||||||
id,
|
#[serde(flatten)]
|
||||||
child: command.spawn()?,
|
input: Input,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChildInfo {
|
||||||
|
gid: Option<ProcessGroupId>,
|
||||||
|
child: Arc<Mutex<Option<Child>>>,
|
||||||
|
output: Option<InheritOutput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InheritOutput {
|
||||||
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
|
stdout: watch::Receiver<String>,
|
||||||
|
stderr: watch::Receiver<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Handler {
|
||||||
|
children: Arc<Mutex<BTreeMap<ProcessId, ChildInfo>>>,
|
||||||
|
}
|
||||||
|
impl Handler {
|
||||||
|
fn new() -> Self {
|
||||||
|
Handler {
|
||||||
|
children: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn handle(&self, req: Input) -> Result<Output, RpcError> {
|
||||||
|
Ok(match req {
|
||||||
|
Input::Command(RunCommandParams {
|
||||||
|
gid,
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
output,
|
||||||
|
}) => Output::Command(self.command(gid, command, args, output).await?),
|
||||||
|
// Input::ReadLineStdout(ReadLineStdoutParams { pid }) => {
|
||||||
|
// Output::ReadLineStdout(self.read_line_stdout(pid).await?)
|
||||||
|
// }
|
||||||
|
// Input::ReadLineStderr(ReadLineStderrParams { pid }) => {
|
||||||
|
// Output::ReadLineStderr(self.read_line_stderr(pid).await?)
|
||||||
|
// }
|
||||||
|
Input::Output(OutputParams { pid }) => Output::Output(self.output(pid).await?),
|
||||||
|
Input::Signal(SendSignalParams { pid, signal }) => {
|
||||||
|
self.signal(pid, signal).await?;
|
||||||
|
Output::Signal
|
||||||
|
}
|
||||||
|
Input::SignalGroup(SignalGroupParams { gid, signal }) => {
|
||||||
|
self.signal_group(gid, signal).await?;
|
||||||
|
Output::SignalGroup
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn id(&self) -> Option<ProcessId> {
|
|
||||||
self.child.id().map(ProcessId)
|
async fn command(
|
||||||
|
&self,
|
||||||
|
gid: Option<ProcessGroupId>,
|
||||||
|
command: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
output: OutputStrategy,
|
||||||
|
) -> Result<ProcessId, RpcError> {
|
||||||
|
let mut cmd = Command::new(command);
|
||||||
|
cmd.args(args);
|
||||||
|
cmd.kill_on_drop(true);
|
||||||
|
cmd.stdout(Stdio::piped());
|
||||||
|
cmd.stderr(Stdio::piped());
|
||||||
|
let mut child = cmd.spawn().map_err(|e| {
|
||||||
|
let mut err = yajrc::INTERNAL_ERROR.clone();
|
||||||
|
err.data = Some(json!(e.to_string()));
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
let pid = ProcessId(child.id().ok_or_else(|| {
|
||||||
|
let mut err = yajrc::INTERNAL_ERROR.clone();
|
||||||
|
err.data = Some(json!("Child has no pid"));
|
||||||
|
err
|
||||||
|
})?);
|
||||||
|
let output = match output {
|
||||||
|
OutputStrategy::Inherit => {
|
||||||
|
let (stdout_send, stdout) = watch::channel(String::new());
|
||||||
|
let (stderr_send, stderr) = watch::channel(String::new());
|
||||||
|
if let (Some(child_stdout), Some(child_stderr)) =
|
||||||
|
(child.stdout.take(), child.stderr.take())
|
||||||
|
{
|
||||||
|
Some(InheritOutput {
|
||||||
|
_thread: tokio::spawn(async move {
|
||||||
|
tokio::join!(
|
||||||
|
async {
|
||||||
|
if let Err(e) = async {
|
||||||
|
let mut lines = BufReader::new(child_stdout).lines();
|
||||||
|
while let Some(line) = lines.next_line().await? {
|
||||||
|
tracing::info!("({}): {}", pid.0, line);
|
||||||
|
let _ = stdout_send.send(line);
|
||||||
}
|
}
|
||||||
async fn wait(&mut self) -> DoneProgram {
|
Ok::<_, std::io::Error>(())
|
||||||
let status = DoneProgramStatus::Wait(self.child.wait().await);
|
}
|
||||||
DoneProgram {
|
.await
|
||||||
id: self.id.clone(),
|
{
|
||||||
status,
|
tracing::error!(
|
||||||
|
"Error reading stdout of pid {}: {}",
|
||||||
|
pid.0,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
if let Err(e) = async {
|
||||||
|
let mut lines = BufReader::new(child_stderr).lines();
|
||||||
|
while let Some(line) = lines.next_line().await? {
|
||||||
|
tracing::warn!("({}): {}", pid.0, line);
|
||||||
|
let _ = stderr_send.send(line);
|
||||||
|
}
|
||||||
|
Ok::<_, std::io::Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"Error reading stdout of pid {}: {}",
|
||||||
|
pid.0,
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn kill(mut self) -> DoneProgram {
|
);
|
||||||
if let Err(err) = self.child.kill().await {
|
})
|
||||||
let id = &self.id;
|
.into(),
|
||||||
tracing::error!("Error while trying to kill a process {id:?}");
|
stdout,
|
||||||
tracing::debug!("{err:?}");
|
stderr,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
DoneProgram {
|
|
||||||
id: self.id.clone(),
|
|
||||||
status: DoneProgramStatus::Killed,
|
|
||||||
}
|
}
|
||||||
|
OutputStrategy::Collect => None,
|
||||||
|
};
|
||||||
|
self.children.lock().await.insert(
|
||||||
|
pid,
|
||||||
|
ChildInfo {
|
||||||
|
gid,
|
||||||
|
child: Arc::new(Mutex::new(Some(child))),
|
||||||
|
output,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn output(&self, pid: ProcessId) -> Result<String, RpcError> {
|
||||||
|
let not_found = || {
|
||||||
|
let mut err = yajrc::INTERNAL_ERROR.clone();
|
||||||
|
err.data = Some(json!(format!("Child with pid {} not found", pid.0)));
|
||||||
|
err
|
||||||
|
};
|
||||||
|
let mut child = {
|
||||||
|
self.children
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get(&pid)
|
||||||
|
.ok_or_else(not_found)?
|
||||||
|
.child
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
.lock_owned()
|
||||||
|
.await;
|
||||||
|
if let Some(child) = child.take() {
|
||||||
|
let output = child.wait_with_output().await?;
|
||||||
|
if output.status.success() {
|
||||||
|
Ok(String::from_utf8(output.stdout).map_err(|_| yajrc::PARSE_ERROR)?)
|
||||||
|
} else {
|
||||||
|
Err(RpcError {
|
||||||
|
code: output
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.or_else(|| output.status.signal().map(|s| 128 + s))
|
||||||
|
.unwrap_or(0),
|
||||||
|
message: "Command failed".into(),
|
||||||
|
data: Some(json!(String::from_utf8(if output.stderr.is_empty() {
|
||||||
|
output.stdout
|
||||||
|
} else {
|
||||||
|
output.stderr
|
||||||
|
})
|
||||||
|
.map_err(|_| yajrc::PARSE_ERROR)?)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(not_found())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controlls the tracing + other io events
|
async fn signal(&self, pid: ProcessId, signal: u32) -> Result<(), RpcError> {
|
||||||
/// Can get the inputs from stdin
|
let not_found = || {
|
||||||
/// Can start a command from an intputrpc returning stream of outputs
|
let mut err = yajrc::INTERNAL_ERROR.clone();
|
||||||
/// Can output to stdout
|
err.data = Some(json!(format!("Child with pid {} not found", pid.0)));
|
||||||
#[derive(Debug, Clone)]
|
err
|
||||||
struct Io {
|
};
|
||||||
commands: Arc<Mutex<BTreeMap<RpcId, oneshot::Sender<()>>>>,
|
|
||||||
ids: Arc<Mutex<BTreeMap<RpcId, ProcessId>>>,
|
Self::killall(pid, Signal::try_from(signal as i32)?)?;
|
||||||
|
|
||||||
|
if signal == 9 {
|
||||||
|
self.children
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.remove(&pid)
|
||||||
|
.ok_or_else(not_found)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Io {
|
async fn signal_group(&self, gid: ProcessGroupId, signal: u32) -> Result<(), RpcError> {
|
||||||
fn start() -> Self {
|
let mut to_kill = Vec::new();
|
||||||
|
{
|
||||||
|
let mut children_ref = self.children.lock().await;
|
||||||
|
let children = std::mem::take(children_ref.deref_mut());
|
||||||
|
for (pid, child_info) in children {
|
||||||
|
if child_info.gid == Some(gid) {
|
||||||
|
to_kill.push(pid);
|
||||||
|
} else {
|
||||||
|
children_ref.insert(pid, child_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pid in to_kill {
|
||||||
|
tracing::info!("Killing pid {}", pid.0);
|
||||||
|
Self::killall(pid, Signal::try_from(signal as i32)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn killall(pid: ProcessId, signal: Signal) -> Result<(), RpcError> {
|
||||||
|
for proc in procfs::process::all_processes()? {
|
||||||
|
let stat = proc?.stat()?;
|
||||||
|
if ProcessId::from(stat.ppid) == pid {
|
||||||
|
Self::killall(stat.pid.into(), signal)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = nix::sys::signal::kill(pid.into(), Some(signal)) {
|
||||||
|
if e != Errno::ESRCH {
|
||||||
|
tracing::error!("Failed to kill pid {}: {}", pid.0, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn graceful_exit(self) {
|
||||||
|
let kill_all = futures::stream::iter(
|
||||||
|
std::mem::take(self.children.lock().await.deref_mut()).into_iter(),
|
||||||
|
)
|
||||||
|
.for_each_concurrent(None, |(pid, child)| async move {
|
||||||
|
let _ = Self::killall(pid, Signal::SIGTERM);
|
||||||
|
if let Some(child) = child.child.lock().await.take() {
|
||||||
|
let _ = child.wait_with_output().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
kill_all.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
|
let mut sigint = signal(SignalKind::interrupt()).unwrap();
|
||||||
|
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
||||||
|
let mut sigquit = signal(SignalKind::quit()).unwrap();
|
||||||
|
let mut sighangup = signal(SignalKind::hangup()).unwrap();
|
||||||
|
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
@@ -86,214 +327,62 @@ impl Io {
|
|||||||
.with(ErrorLayer::default())
|
.with(ErrorLayer::default())
|
||||||
.init();
|
.init();
|
||||||
color_eyre::install().unwrap();
|
color_eyre::install().unwrap();
|
||||||
Self {
|
|
||||||
commands: Default::default(),
|
|
||||||
ids: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument]
|
let handler = Handler::new();
|
||||||
fn command(&self, input: InputJsonRpc) -> impl Stream<Item = OutputJsonRpc> {
|
let mut lines = BufReader::new(tokio::io::stdin()).lines();
|
||||||
let io = self.clone();
|
let handler_thread = async {
|
||||||
stream! {
|
while let Some(line) = lines.next_line().await? {
|
||||||
let (id, command) = input.into_pair();
|
let local_hdlr = handler.clone();
|
||||||
match command {
|
tokio::spawn(async move {
|
||||||
Input::Command {
|
if let Err(e) = async {
|
||||||
ref command,
|
eprintln!("{}", line);
|
||||||
ref args,
|
let req = serde_json::from_str::<IncomingRpc>(&line)?;
|
||||||
} => {
|
match local_hdlr.handle(req.input).await {
|
||||||
let mut cmd = Command::new(command);
|
Ok(output) => {
|
||||||
cmd.args(args);
|
println!(
|
||||||
|
"{}",
|
||||||
cmd.stdout(Stdio::piped());
|
json!({ "id": req.id, "jsonrpc": "2.0", "result": output })
|
||||||
cmd.stderr(Stdio::piped());
|
|
||||||
let mut child_and_rpc = match ChildAndRpc::new(id.clone(), cmd) {
|
|
||||||
Err(_e) => return,
|
|
||||||
Ok(a) => a,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(child_id) = child_and_rpc.id() {
|
|
||||||
io.ids.lock().await.insert(id.clone(), child_id.clone());
|
|
||||||
yield JsonRpc::new(id.clone(), Output::ProcessId(child_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout = child_and_rpc.child
|
|
||||||
.stdout
|
|
||||||
.take()
|
|
||||||
.expect("child did not have a handle to stdout");
|
|
||||||
let stderr = child_and_rpc.child
|
|
||||||
.stderr
|
|
||||||
.take()
|
|
||||||
.expect("child did not have a handle to stderr");
|
|
||||||
|
|
||||||
let mut buff_out = BufReader::new(stdout).lines();
|
|
||||||
let mut buff_err = BufReader::new(stderr).lines();
|
|
||||||
|
|
||||||
let spawned = tokio::spawn({
|
|
||||||
let id = id.clone();
|
|
||||||
async move {
|
|
||||||
let end_command_receiver = io.create_end_command(id.clone()).await;
|
|
||||||
tokio::select!{
|
|
||||||
waited = child_and_rpc
|
|
||||||
.wait() => {
|
|
||||||
io.clean_id(&waited).await;
|
|
||||||
match &waited.status {
|
|
||||||
DoneProgramStatus::Wait(Ok(st)) => return st.code(),
|
|
||||||
DoneProgramStatus::Wait(Err(err)) => tracing::debug!("Child {id:?} got error: {err:?}"),
|
|
||||||
DoneProgramStatus::Killed => tracing::debug!("Child {id:?} already killed?"),
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
_ = end_command_receiver => {
|
|
||||||
let status = child_and_rpc.kill().await;
|
|
||||||
io.clean_id(&status).await;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
while let Ok(Some(line)) = buff_out.next_line().await {
|
|
||||||
let output = Output::Line(line);
|
|
||||||
let output = JsonRpc::new(id.clone(), output);
|
|
||||||
tracing::trace!("OutputJsonRpc {{ id, output_rpc }} = {:?}", output);
|
|
||||||
yield output;
|
|
||||||
}
|
|
||||||
while let Ok(Some(line)) = buff_err.next_line().await {
|
|
||||||
yield JsonRpc::new(id.clone(), Output::Error(line));
|
|
||||||
}
|
|
||||||
let code = spawned.await.ok().flatten();
|
|
||||||
yield JsonRpc::new(id, Output::Done(code));
|
|
||||||
},
|
|
||||||
Input::Kill() => {
|
|
||||||
io.trigger_end_command(id).await;
|
|
||||||
}
|
|
||||||
Input::Term() => {
|
|
||||||
io.term_by_rpc(&id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Used to get the string lines from the stdin
|
|
||||||
fn inputs(&self) -> impl Stream<Item = String> {
|
|
||||||
use std::io::BufRead;
|
|
||||||
let (sender, receiver) = tokio::sync::mpsc::channel(100);
|
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
let stdin = std::io::stdin();
|
|
||||||
for line in stdin.lock().lines().flatten() {
|
|
||||||
tracing::trace!("Line = {}", line);
|
|
||||||
sender.blocking_send(line).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tokio_stream::wrappers::ReceiverStream::new(receiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
///Convert a stream of string to stdout
|
|
||||||
async fn output(&self, outputs: impl Stream<Item = String>) {
|
|
||||||
pin_mut!(outputs);
|
|
||||||
while let Some(output) = outputs.next().await {
|
|
||||||
println!("{}", output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper for the command fn
|
|
||||||
/// Part of a pair for the signal map, that indicates that we should kill the command
|
|
||||||
async fn trigger_end_command(&self, id: RpcId) {
|
|
||||||
if let Some(command) = self.commands.lock().await.remove(&id) {
|
|
||||||
if command.send(()).is_err() {
|
|
||||||
tracing::trace!("Command {id:?} could not be ended, possible error or was done");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper for the command fn
|
|
||||||
/// Part of a pair for the signal map, that indicates that we should kill the command
|
|
||||||
async fn create_end_command(&self, id: RpcId) -> oneshot::Receiver<()> {
|
|
||||||
let (send, receiver) = oneshot::channel();
|
|
||||||
if let Some(other_command) = self.commands.lock().await.insert(id.clone(), send) {
|
|
||||||
if other_command.send(()).is_err() {
|
|
||||||
tracing::trace!(
|
|
||||||
"Found other command {id:?} could not be ended, possible error or was done"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
receiver
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used during cleaning up a procress
|
|
||||||
async fn clean_id(
|
|
||||||
&self,
|
|
||||||
done_program: &DoneProgram,
|
|
||||||
) -> (Option<ProcessId>, Option<oneshot::Sender<()>>) {
|
|
||||||
(
|
|
||||||
self.ids.lock().await.remove(&done_program.id),
|
|
||||||
self.commands.lock().await.remove(&done_program.id),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
/// Given the rpcid, will try and term the running command
|
println!("{}", json!({ "id": req.id, "jsonrpc": "2.0", "error": e }))
|
||||||
async fn term_by_rpc(&self, rpc: &RpcId) {
|
|
||||||
let output = match self.remove_cmd_id(rpc).await {
|
|
||||||
Some(id) => {
|
|
||||||
let mut cmd = tokio::process::Command::new("kill");
|
|
||||||
cmd.arg(format!("{}", id.0));
|
|
||||||
cmd.output().await
|
|
||||||
}
|
}
|
||||||
None => return,
|
}
|
||||||
|
Ok::<_, serde_json::Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Error parsing RPC request: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok::<_, std::io::Error>(())
|
||||||
};
|
};
|
||||||
match output {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Could not kill rpc {rpc:?}");
|
|
||||||
tracing::debug!("{err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used as a cleanup
|
|
||||||
async fn term_all(self) {
|
|
||||||
let ids: Vec<_> = self.ids.lock().await.keys().cloned().collect();
|
|
||||||
for id in ids {
|
|
||||||
self.term_by_rpc(&id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn remove_cmd_id(&self, rpc: &RpcId) -> Option<ProcessId> {
|
|
||||||
self.ids.lock().await.remove(rpc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
use futures::StreamExt;
|
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
|
||||||
let mut sigint = signal(SignalKind::interrupt()).unwrap();
|
|
||||||
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
|
||||||
let mut sigquit = signal(SignalKind::quit()).unwrap();
|
|
||||||
let mut sighangup = signal(SignalKind::hangup()).unwrap();
|
|
||||||
let io = Io::start();
|
|
||||||
let outputs = io
|
|
||||||
.inputs()
|
|
||||||
.filter_map(|x| async move { InputJsonRpc::maybe_parse(&x) })
|
|
||||||
.flat_map_unordered(MAX_COMMANDS, |x| io.command(x).boxed())
|
|
||||||
.filter_map(|x| async move { x.maybe_serialize() });
|
|
||||||
|
|
||||||
select! {
|
select! {
|
||||||
_ = io.output(outputs) => {
|
res = handler_thread => {
|
||||||
tracing::debug!("Done with inputs/outputs")
|
match res {
|
||||||
|
Ok(()) => tracing::debug!("Done with inputs/outputs"),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error reading RPC input: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ = sigint.recv() => {
|
_ = sigint.recv() => {
|
||||||
tracing::debug!("Sigint")
|
tracing::debug!("SIGINT");
|
||||||
},
|
},
|
||||||
_ = sigterm.recv() => {
|
_ = sigterm.recv() => {
|
||||||
tracing::debug!("Sig Term")
|
tracing::debug!("SIGTERM");
|
||||||
},
|
},
|
||||||
_ = sigquit.recv() => {
|
_ = sigquit.recv() => {
|
||||||
tracing::debug!("Sigquit")
|
tracing::debug!("SIGQUIT");
|
||||||
},
|
},
|
||||||
_ = sighangup.recv() => {
|
_ = sighangup.recv() => {
|
||||||
tracing::debug!("Sighangup")
|
tracing::debug!("SIGHUP");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io.term_all().await;
|
handler.graceful_exit().await;
|
||||||
::std::process::exit(0);
|
::std::process::exit(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ futures = "0.3.21"
|
|||||||
models = { path = "../models" }
|
models = { path = "../models" }
|
||||||
pin-project = "1.0.11"
|
pin-project = "1.0.11"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
|
serde_json = "1.0"
|
||||||
tokio = { version = "1.19.2", features = ["full"] }
|
tokio = { version = "1.19.2", features = ["full"] }
|
||||||
tokio-stream = { version = "0.1.9", features = ["io-util", "sync"] }
|
tokio-stream = { version = "0.1.9", features = ["io-util", "sync"] }
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
|
yajrc = { version = "*", git = "https://github.com/dr-bonez/yajrc.git", branch = "develop" }
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ use tokio::sync::oneshot;
|
|||||||
use tokio::task::{JoinError, JoinHandle, LocalSet};
|
use tokio::task::{JoinError, JoinHandle, LocalSet};
|
||||||
|
|
||||||
mod byte_replacement_reader;
|
mod byte_replacement_reader;
|
||||||
|
mod rpc_client;
|
||||||
mod rsync;
|
mod rsync;
|
||||||
mod script_dir;
|
mod script_dir;
|
||||||
pub use byte_replacement_reader::*;
|
pub use byte_replacement_reader::*;
|
||||||
|
pub use rpc_client::RpcClient;
|
||||||
pub use rsync::*;
|
pub use rsync::*;
|
||||||
pub use script_dir::*;
|
pub use script_dir::*;
|
||||||
|
|
||||||
|
|||||||
116
libs/helpers/src/rpc_client.rs
Normal file
116
libs/helpers/src/rpc_client.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
use models::{Error, ErrorKind, ResultExt};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
|
||||||
|
use tokio::sync::{oneshot, Mutex};
|
||||||
|
use yajrc::{Id, RpcError, RpcMethod, RpcRequest, RpcResponse};
|
||||||
|
|
||||||
|
use crate::NonDetachingJoinHandle;
|
||||||
|
|
||||||
|
type DynWrite = Box<dyn AsyncWrite + Unpin + Send + Sync + 'static>;
|
||||||
|
type ResponseMap = BTreeMap<Id, oneshot::Sender<Result<Value, RpcError>>>;
|
||||||
|
|
||||||
|
pub struct RpcClient {
|
||||||
|
id: AtomicUsize,
|
||||||
|
_handler: NonDetachingJoinHandle<()>,
|
||||||
|
writable: Weak<Mutex<(DynWrite, ResponseMap)>>,
|
||||||
|
}
|
||||||
|
impl RpcClient {
|
||||||
|
pub fn new<
|
||||||
|
W: AsyncWrite + Unpin + Send + Sync + 'static,
|
||||||
|
R: AsyncRead + Unpin + Send + Sync + 'static,
|
||||||
|
>(
|
||||||
|
writer: W,
|
||||||
|
reader: R,
|
||||||
|
) -> Self {
|
||||||
|
let writer: DynWrite = Box::new(writer);
|
||||||
|
let writable = Arc::new(Mutex::new((writer, ResponseMap::new())));
|
||||||
|
let weak_writable = Arc::downgrade(&writable);
|
||||||
|
RpcClient {
|
||||||
|
id: AtomicUsize::new(0),
|
||||||
|
_handler: tokio::spawn(async move {
|
||||||
|
let mut lines = BufReader::new(reader).lines();
|
||||||
|
while let Some(line) = lines.next_line().await.transpose() {
|
||||||
|
let mut w = writable.lock().await;
|
||||||
|
match line.map_err(Error::from).and_then(|l| {
|
||||||
|
serde_json::from_str::<RpcResponse>(&l)
|
||||||
|
.with_kind(ErrorKind::Deserialization)
|
||||||
|
}) {
|
||||||
|
Ok(l) => {
|
||||||
|
if let Some(id) = l.id {
|
||||||
|
if let Some(res) = w.1.remove(&id) {
|
||||||
|
if let Err(e) = res.send(l.result) {
|
||||||
|
tracing::warn!(
|
||||||
|
"RpcClient Response for Unknown ID: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
"RpcClient Response for Unknown ID: {:?}",
|
||||||
|
l.result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::info!("RpcClient Notification: {:?}", l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("RpcClient Error: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
writable: weak_writable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn request<T: RpcMethod>(
|
||||||
|
&self,
|
||||||
|
method: T,
|
||||||
|
params: T::Params,
|
||||||
|
) -> Result<T::Response, RpcError>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
T::Params: Serialize,
|
||||||
|
T::Response: for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
if let Some(w) = self.writable.upgrade() {
|
||||||
|
let mut w = w.lock().await;
|
||||||
|
let id = Id::Number(
|
||||||
|
self.id
|
||||||
|
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
w.0.write_all(
|
||||||
|
(serde_json::to_string(&RpcRequest {
|
||||||
|
id: Some(id.clone()),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
})? + "\n")
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
let mut err = yajrc::INTERNAL_ERROR.clone();
|
||||||
|
err.data = Some(json!(e.to_string()));
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
let (send, recv) = oneshot::channel();
|
||||||
|
w.1.insert(id, send);
|
||||||
|
drop(w);
|
||||||
|
if let Ok(val) = recv.await {
|
||||||
|
return Ok(serde_json::from_value(val?)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut err = yajrc::INTERNAL_ERROR.clone();
|
||||||
|
err.data = Some(json!("RpcClient thread has terminated"));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
use color_eyre::eyre::eyre;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{const_true, ByteReplacementReader, NonDetachingJoinHandle};
|
use color_eyre::eyre::eyre;
|
||||||
use models::{Error, ErrorKind};
|
use models::{Error, ErrorKind};
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
|
||||||
use tokio::process::{Child, Command};
|
use tokio::process::{Child, Command};
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tokio_stream::wrappers::WatchStream;
|
use tokio_stream::wrappers::WatchStream;
|
||||||
|
|
||||||
|
use crate::{const_true, ByteReplacementReader, NonDetachingJoinHandle};
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RsyncOptions {
|
pub struct RsyncOptions {
|
||||||
|
|||||||
@@ -43,27 +43,31 @@ const readFile = (
|
|||||||
const runDaemon = (
|
const runDaemon = (
|
||||||
{ command = requireParam("command"), args = [] } = requireParam("options"),
|
{ command = requireParam("command"), args = [] } = requireParam("options"),
|
||||||
) => {
|
) => {
|
||||||
let id = Deno.core.opAsync("start_command", command, args);
|
let id = Deno.core.opAsync("start_command", command, args, "inherit", null);
|
||||||
let rpcId = id.then(x => x.rpcId)
|
|
||||||
let processId = id.then(x => x.processId)
|
let processId = id.then(x => x.processId)
|
||||||
let waitPromise = null;
|
let waitPromise = null;
|
||||||
return {
|
return {
|
||||||
processId,
|
processId,
|
||||||
rpcId,
|
|
||||||
async wait() {
|
async wait() {
|
||||||
waitPromise = waitPromise || Deno.core.opAsync("wait_command", await rpcId)
|
waitPromise = waitPromise || Deno.core.opAsync("wait_command", await processId)
|
||||||
return waitPromise
|
return waitPromise
|
||||||
},
|
},
|
||||||
async term() {
|
async term(signal = 15) {
|
||||||
return Deno.core.opAsync("term_command", await rpcId)
|
return Deno.core.opAsync("send_signal", await processId, 15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const runCommand = async (
|
const runCommand = async (
|
||||||
{ command = requireParam("command"), args = [], timeoutMillis = 30000 } = requireParam("options"),
|
{ command = requireParam("command"), args = [], timeoutMillis = 30000 } = requireParam("options"),
|
||||||
) => {
|
) => {
|
||||||
let id = Deno.core.opAsync("start_command", command, args, timeoutMillis);
|
let id = Deno.core.opAsync("start_command", command, args, "collect", timeoutMillis);
|
||||||
return Deno.core.opAsync("wait_command", await id)
|
let pid = id.then(x => x.processId)
|
||||||
|
return Deno.core.opAsync("wait_command", await pid)
|
||||||
|
};
|
||||||
|
const signalGroup = async (
|
||||||
|
{ gid = requireParam("gid"), signal = requireParam("signal") } = requireParam("gid and signal")
|
||||||
|
) => {
|
||||||
|
return Deno.core.opAsync("signal_group", gid, signal);
|
||||||
};
|
};
|
||||||
const sleep = (timeMs = requireParam("timeMs"),
|
const sleep = (timeMs = requireParam("timeMs"),
|
||||||
) => Deno.core.opAsync("sleep", timeMs);
|
) => Deno.core.opAsync("sleep", timeMs);
|
||||||
@@ -181,10 +185,17 @@ const effects = {
|
|||||||
runCommand,
|
runCommand,
|
||||||
sleep,
|
sleep,
|
||||||
runDaemon,
|
runDaemon,
|
||||||
|
signalGroup,
|
||||||
runRsync
|
runRsync
|
||||||
};
|
};
|
||||||
|
|
||||||
const runFunction = jsonPointerValue(mainModule, currentFunction);
|
const defaults = {
|
||||||
|
"handleSignal": (effects, { gid, signal }) => {
|
||||||
|
return effects.signalGroup({ gid, signal })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runFunction = jsonPointerValue(mainModule, currentFunction) || jsonPointerValue(defaults, currentFunction);
|
||||||
(async () => {
|
(async () => {
|
||||||
if (typeof runFunction !== "function") {
|
if (typeof runFunction !== "function") {
|
||||||
error(`Expecting ${currentFunction} to be a function`);
|
error(`Expecting ${currentFunction} to be a function`);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::future::Future;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -11,9 +10,9 @@ use deno_core::{
|
|||||||
resolve_import, Extension, JsRuntime, ModuleLoader, ModuleSource, ModuleSourceFuture,
|
resolve_import, Extension, JsRuntime, ModuleLoader, ModuleSource, ModuleSourceFuture,
|
||||||
ModuleSpecifier, ModuleType, OpDecl, RuntimeOptions, Snapshot,
|
ModuleSpecifier, ModuleType, OpDecl, RuntimeOptions, Snapshot,
|
||||||
};
|
};
|
||||||
use embassy_container_init::RpcId;
|
use embassy_container_init::ProcessGroupId;
|
||||||
use helpers::{script_dir, spawn_local, Rsync};
|
use helpers::{script_dir, spawn_local, RpcClient, Rsync};
|
||||||
use models::{ExecCommand, PackageId, ProcedureName, TermCommand, Version, VolumeId};
|
use models::{PackageId, ProcedureName, Version, VolumeId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
@@ -84,7 +83,6 @@ const SNAPSHOT_BYTES: &[u8] = include_bytes!("./artifacts/JS_SNAPSHOT.bin");
|
|||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
const SNAPSHOT_BYTES: &[u8] = include_bytes!("./artifacts/ARM_JS_SNAPSHOT.bin");
|
const SNAPSHOT_BYTES: &[u8] = include_bytes!("./artifacts/ARM_JS_SNAPSHOT.bin");
|
||||||
type WaitFns = Arc<Mutex<BTreeMap<RpcId, Pin<Box<dyn Future<Output = ResultType>>>>>>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct JsContext {
|
struct JsContext {
|
||||||
@@ -96,9 +94,8 @@ struct JsContext {
|
|||||||
volumes: Arc<dyn PathForVolumeId>,
|
volumes: Arc<dyn PathForVolumeId>,
|
||||||
input: Value,
|
input: Value,
|
||||||
variable_args: Vec<serde_json::Value>,
|
variable_args: Vec<serde_json::Value>,
|
||||||
command_inserter: ExecCommand,
|
container_process_gid: ProcessGroupId,
|
||||||
term_command: TermCommand,
|
container_rpc_client: Option<Arc<RpcClient>>,
|
||||||
wait_fns: WaitFns,
|
|
||||||
rsyncs: Arc<Mutex<(usize, BTreeMap<usize, Rsync>)>>,
|
rsyncs: Arc<Mutex<(usize, BTreeMap<usize, Rsync>)>>,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
@@ -186,8 +183,8 @@ pub struct JsExecutionEnvironment {
|
|||||||
package_id: PackageId,
|
package_id: PackageId,
|
||||||
version: Version,
|
version: Version,
|
||||||
volumes: Arc<dyn PathForVolumeId>,
|
volumes: Arc<dyn PathForVolumeId>,
|
||||||
command_inserter: ExecCommand,
|
container_process_gid: ProcessGroupId,
|
||||||
term_command: TermCommand,
|
container_rpc_client: Option<Arc<RpcClient>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsExecutionEnvironment {
|
impl JsExecutionEnvironment {
|
||||||
@@ -196,8 +193,8 @@ impl JsExecutionEnvironment {
|
|||||||
package_id: &PackageId,
|
package_id: &PackageId,
|
||||||
version: &Version,
|
version: &Version,
|
||||||
volumes: Box<dyn PathForVolumeId>,
|
volumes: Box<dyn PathForVolumeId>,
|
||||||
command_inserter: ExecCommand,
|
container_process_gid: ProcessGroupId,
|
||||||
term_command: TermCommand,
|
container_rpc_client: Option<Arc<RpcClient>>,
|
||||||
) -> Result<JsExecutionEnvironment, (JsError, String)> {
|
) -> Result<JsExecutionEnvironment, (JsError, String)> {
|
||||||
let data_dir = data_directory.as_ref();
|
let data_dir = data_directory.as_ref();
|
||||||
let base_directory = data_dir;
|
let base_directory = data_dir;
|
||||||
@@ -231,8 +228,8 @@ impl JsExecutionEnvironment {
|
|||||||
version: version.clone(),
|
version: version.clone(),
|
||||||
volumes: volumes.into(),
|
volumes: volumes.into(),
|
||||||
sandboxed: false,
|
sandboxed: false,
|
||||||
command_inserter,
|
container_process_gid,
|
||||||
term_command,
|
container_rpc_client,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn read_only_effects(mut self) -> Self {
|
pub fn read_only_effects(mut self) -> Self {
|
||||||
@@ -297,7 +294,8 @@ impl JsExecutionEnvironment {
|
|||||||
fns::start_command::decl(),
|
fns::start_command::decl(),
|
||||||
fns::wait_command::decl(),
|
fns::wait_command::decl(),
|
||||||
fns::sleep::decl(),
|
fns::sleep::decl(),
|
||||||
fns::term_command::decl(),
|
fns::send_signal::decl(),
|
||||||
|
fns::signal_group::decl(),
|
||||||
fns::rsync::decl(),
|
fns::rsync::decl(),
|
||||||
fns::rsync_wait::decl(),
|
fns::rsync_wait::decl(),
|
||||||
fns::rsync_progress::decl(),
|
fns::rsync_progress::decl(),
|
||||||
@@ -330,9 +328,8 @@ impl JsExecutionEnvironment {
|
|||||||
sandboxed: self.sandboxed,
|
sandboxed: self.sandboxed,
|
||||||
input,
|
input,
|
||||||
variable_args,
|
variable_args,
|
||||||
command_inserter: self.command_inserter.clone(),
|
container_process_gid: self.container_process_gid,
|
||||||
term_command: self.term_command.clone(),
|
container_rpc_client: self.container_rpc_client.clone(),
|
||||||
wait_fns: Default::default(),
|
|
||||||
rsyncs: Default::default(),
|
rsyncs: Default::default(),
|
||||||
};
|
};
|
||||||
let ext = Extension::builder()
|
let ext = Extension::builder()
|
||||||
@@ -378,21 +375,25 @@ impl JsExecutionEnvironment {
|
|||||||
|
|
||||||
/// Note: Make sure that we have the assumption that all these methods are callable at any time, and all call restrictions should be in rust
|
/// Note: Make sure that we have the assumption that all these methods are callable at any time, and all call restrictions should be in rust
|
||||||
mod fns {
|
mod fns {
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{cell::RefCell, time::Duration};
|
use std::time::Duration;
|
||||||
|
|
||||||
use deno_core::anyhow::{anyhow, bail};
|
use deno_core::anyhow::{anyhow, bail};
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::*;
|
use deno_core::*;
|
||||||
use embassy_container_init::{ProcessId, RpcId};
|
use embassy_container_init::{
|
||||||
|
OutputParams, OutputStrategy, ProcessGroupId, ProcessId, RunCommand, RunCommandParams,
|
||||||
|
SendSignal, SendSignalParams, SignalGroup, SignalGroupParams,
|
||||||
|
};
|
||||||
use helpers::{to_tmp_path, AtomicFile, Rsync, RsyncOptions};
|
use helpers::{to_tmp_path, AtomicFile, Rsync, RsyncOptions};
|
||||||
use models::{TermCommand, VolumeId};
|
use models::VolumeId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::{json, Value};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use super::{AnswerState, JsContext};
|
use super::{AnswerState, JsContext};
|
||||||
@@ -930,22 +931,64 @@ mod fns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
async fn term_command(state: Rc<RefCell<OpState>>, id: u32) -> Result<(), AnyError> {
|
async fn send_signal(
|
||||||
let term_command_impl: TermCommand = {
|
state: Rc<RefCell<OpState>>,
|
||||||
|
pid: u32,
|
||||||
|
signal: u32,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
if let Some(rpc_client) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
ctx.term_command.clone()
|
ctx.container_rpc_client.clone()
|
||||||
};
|
} {
|
||||||
if let Err(err) = term_command_impl(embassy_container_init::RpcId::UInt(id)).await {
|
rpc_client
|
||||||
bail!("{}", err);
|
.request(
|
||||||
}
|
SendSignal,
|
||||||
|
SendSignalParams {
|
||||||
|
pid: ProcessId(pid),
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("{}: {:?}", e.message, e.data))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No RpcClient for command operations"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op]
|
||||||
|
async fn signal_group(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
gid: u32,
|
||||||
|
signal: u32,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
if let Some(rpc_client) = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx = state.borrow::<JsContext>();
|
||||||
|
ctx.container_rpc_client.clone()
|
||||||
|
} {
|
||||||
|
rpc_client
|
||||||
|
.request(
|
||||||
|
SignalGroup,
|
||||||
|
SignalGroupParams {
|
||||||
|
gid: ProcessGroupId(gid),
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("{}: {:?}", e.message, e.data))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No RpcClient for command operations"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct StartCommand {
|
pub struct StartCommand {
|
||||||
rpc_id: RpcId,
|
|
||||||
process_id: ProcessId,
|
process_id: ProcessId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -954,91 +997,77 @@ mod fns {
|
|||||||
state: Rc<RefCell<OpState>>,
|
state: Rc<RefCell<OpState>>,
|
||||||
command: String,
|
command: String,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
|
output: OutputStrategy,
|
||||||
timeout: Option<u64>,
|
timeout: Option<u64>,
|
||||||
) -> Result<StartCommand, AnyError> {
|
) -> Result<StartCommand, AnyError> {
|
||||||
use embassy_container_init::Output;
|
if let (gid, Some(rpc_client)) = {
|
||||||
let (command_inserter, wait_fns) = {
|
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
(ctx.command_inserter.clone(), ctx.wait_fns.clone())
|
(ctx.container_process_gid, ctx.container_rpc_client.clone())
|
||||||
};
|
} {
|
||||||
|
let pid = rpc_client
|
||||||
let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::<Output>();
|
.request(
|
||||||
let rpc_id = match command_inserter(
|
RunCommand,
|
||||||
|
RunCommandParams {
|
||||||
|
gid: Some(gid),
|
||||||
command,
|
command,
|
||||||
args.into_iter().collect(),
|
args,
|
||||||
sender,
|
output,
|
||||||
timeout.map(std::time::Duration::from_millis),
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| anyhow!("{}: {:?}", e.message, e.data))?;
|
||||||
|
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::time::sleep(Duration::from_micros(timeout)).await;
|
||||||
|
if let Err(err) = rpc_client
|
||||||
|
.request(SendSignal, SendSignalParams { pid, signal: 9 })
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("{}: {:?}", e.message, e.data))
|
||||||
{
|
{
|
||||||
Err(err) => bail!(err),
|
tracing::warn!("Could not kill process {pid:?}");
|
||||||
Ok(rpc_id) => rpc_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (process_id_send, process_id_recv) = tokio::sync::oneshot::channel::<ProcessId>();
|
|
||||||
|
|
||||||
let wait = async move {
|
|
||||||
let mut answer = String::new();
|
|
||||||
let mut command_error = String::new();
|
|
||||||
let mut status: Option<i32> = None;
|
|
||||||
let mut process_id_send = Some(process_id_send);
|
|
||||||
while let Some(output) = receiver.recv().await {
|
|
||||||
match output {
|
|
||||||
Output::ProcessId(process_id) => {
|
|
||||||
if let Some(process_id_send) = process_id_send.take() {
|
|
||||||
if let Err(err) = process_id_send.send(process_id) {
|
|
||||||
tracing::error!(
|
|
||||||
"Could not get a process id {process_id:?} sent for {rpc_id:?}"
|
|
||||||
);
|
|
||||||
tracing::debug!("{err:?}");
|
tracing::debug!("{err:?}");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
Output::Line(value) => {
|
|
||||||
answer.push_str(&value);
|
|
||||||
answer.push('\n');
|
|
||||||
}
|
|
||||||
Output::Error(error) => {
|
|
||||||
command_error.push_str(&error);
|
|
||||||
command_error.push('\n');
|
|
||||||
}
|
|
||||||
Output::Done(error_code) => {
|
|
||||||
status = error_code;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !command_error.is_empty() {
|
|
||||||
if let Some(status) = status {
|
|
||||||
return ResultType::ErrorCode(status, command_error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultType::Error(command_error);
|
Ok(StartCommand { process_id: pid })
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No RpcClient for command operations"))
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultType::Result(serde_json::Value::String(answer))
|
|
||||||
};
|
|
||||||
wait_fns.lock().await.insert(rpc_id, Box::pin(wait));
|
|
||||||
let process_id = process_id_recv.await?;
|
|
||||||
Ok(StartCommand { rpc_id, process_id })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
async fn wait_command(state: Rc<RefCell<OpState>>, id: RpcId) -> Result<ResultType, AnyError> {
|
async fn wait_command(
|
||||||
let wait_fns = {
|
state: Rc<RefCell<OpState>>,
|
||||||
|
pid: ProcessId,
|
||||||
|
) -> Result<ResultType, AnyError> {
|
||||||
|
if let Some(rpc_client) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
ctx.wait_fns.clone()
|
ctx.container_rpc_client.clone()
|
||||||
};
|
} {
|
||||||
|
Ok(
|
||||||
let found_future = match wait_fns.lock().await.remove(&id) {
|
match rpc_client
|
||||||
Some(a) => a,
|
.request(embassy_container_init::Output, OutputParams { pid })
|
||||||
None => bail!("No future for id {id:?}, could have been removed already"),
|
.await
|
||||||
};
|
{
|
||||||
|
Ok(a) => ResultType::Result(json!(a)),
|
||||||
Ok(found_future.await)
|
Err(e) => ResultType::ErrorCode(
|
||||||
|
e.code,
|
||||||
|
match e.data {
|
||||||
|
Some(Value::String(s)) => s,
|
||||||
|
e => format!("{:?}", e),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No RpcClient for command operations"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
async fn sleep(time_ms: u64) -> Result<(), AnyError> {
|
async fn sleep(time_ms: u64) -> Result<(), AnyError> {
|
||||||
tokio::time::sleep(Duration::from_millis(time_ms)).await;
|
tokio::time::sleep(Duration::from_millis(time_ms)).await;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ bollard = "0.13.0"
|
|||||||
color-eyre = "0.6.1"
|
color-eyre = "0.6.1"
|
||||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||||
mbrman = "0.5.0"
|
mbrman = "0.5.0"
|
||||||
embassy_container_init = { path = "../embassy_container_init" }
|
|
||||||
emver = { version = "0.1", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
emver = { version = "0.1", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::InvalidId;
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use patch_db::Revision;
|
use patch_db::Revision;
|
||||||
use rpc_toolkit::{hyper::http::uri::InvalidUri, yajrc::RpcError};
|
use rpc_toolkit::hyper::http::uri::InvalidUri;
|
||||||
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
|
|
||||||
|
use crate::InvalidId;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ mod interface_id;
|
|||||||
mod invalid_id;
|
mod invalid_id;
|
||||||
mod package_id;
|
mod package_id;
|
||||||
mod procedure_name;
|
mod procedure_name;
|
||||||
mod type_aliases;
|
|
||||||
mod version;
|
mod version;
|
||||||
mod volume_id;
|
mod volume_id;
|
||||||
|
|
||||||
@@ -20,6 +19,5 @@ pub use interface_id::*;
|
|||||||
pub use invalid_id::*;
|
pub use invalid_id::*;
|
||||||
pub use package_id::*;
|
pub use package_id::*;
|
||||||
pub use procedure_name::*;
|
pub use procedure_name::*;
|
||||||
pub use type_aliases::*;
|
|
||||||
pub use version::*;
|
pub use version::*;
|
||||||
pub use volume_id::*;
|
pub use volume_id::*;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub enum ProcedureName {
|
|||||||
AutoConfig(PackageId),
|
AutoConfig(PackageId),
|
||||||
Health(HealthCheckId),
|
Health(HealthCheckId),
|
||||||
Action(ActionId),
|
Action(ActionId),
|
||||||
|
Signal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcedureName {
|
impl ProcedureName {
|
||||||
@@ -31,6 +32,7 @@ impl ProcedureName {
|
|||||||
ProcedureName::Action(id) => Some(format!("{}Action", id)),
|
ProcedureName::Action(id) => Some(format!("{}Action", id)),
|
||||||
ProcedureName::Check(_) => None,
|
ProcedureName::Check(_) => None,
|
||||||
ProcedureName::AutoConfig(_) => None,
|
ProcedureName::AutoConfig(_) => None,
|
||||||
|
ProcedureName::Signal => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn js_function_name(&self) -> Option<String> {
|
pub fn js_function_name(&self) -> Option<String> {
|
||||||
@@ -47,6 +49,7 @@ impl ProcedureName {
|
|||||||
ProcedureName::Action(id) => Some(format!("/action/{}", id)),
|
ProcedureName::Action(id) => Some(format!("/action/{}", id)),
|
||||||
ProcedureName::Check(id) => Some(format!("/dependencies/{}/check", id)),
|
ProcedureName::Check(id) => Some(format!("/dependencies/{}/check", id)),
|
||||||
ProcedureName::AutoConfig(id) => Some(format!("/dependencies/{}/autoConfigure", id)),
|
ProcedureName::AutoConfig(id) => Some(format!("/dependencies/{}/autoConfigure", id)),
|
||||||
|
ProcedureName::Signal => Some("/handleSignal".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
use embassy_container_init::RpcId;
|
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
|
||||||
|
|
||||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
|
||||||
pub type ExecCommand = Arc<
|
|
||||||
dyn Fn(
|
|
||||||
String,
|
|
||||||
Vec<String>,
|
|
||||||
UnboundedSender<embassy_container_init::Output>,
|
|
||||||
Option<Duration>,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<RpcId, String>> + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
|
||||||
pub type TermCommand = Arc<
|
|
||||||
dyn Fn(RpcId) -> Pin<Box<dyn Future<Output = Result<(), String>> + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
1
system-images/compat/Cargo.lock
generated
1
system-images/compat/Cargo.lock
generated
@@ -1052,6 +1052,7 @@ dependencies = [
|
|||||||
"async-stream",
|
"async-stream",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"futures",
|
"futures",
|
||||||
|
"imbl 2.0.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
Reference in New Issue
Block a user