Implement daemon service

This commit is contained in:
Tony Garnock-Jones 2021-09-20 16:42:35 +02:00
parent c87bfd8a2d
commit 9f316ac659
5 changed files with 130 additions and 15 deletions

11
Cargo.lock generated
View File

@ -1397,6 +1397,15 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.4"
@ -1596,7 +1605,9 @@ dependencies = [
"memchr",
"mio 0.7.13",
"num_cpus",
"once_cell",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
"winapi 0.3.9",
]

View File

@ -26,7 +26,7 @@ structopt = "0.3"
tungstenite = "0.13"
tokio-tungstenite = "0.14"
tokio = { version = "1.10", features = ["io-std", "time"] }
tokio = { version = "1.10", features = ["io-std", "time", "process"] }
tokio-util = "0.6"
tracing = "0.1"

View File

@ -1,4 +1,4 @@
´³bundle·µ³externalServices„´³schema·³version³ definitions·³Service´³refµ„³ DaemonService„³ClearEnv´³orµµ±present´³dict·³clearEnv´³named³clearEnv´³atom³Boolean„„„„„µ±absent´³dict·„„„„„³DaemonId³any³EnvValue´³orµµ±set´³atom³String„„µ±remove´³lit€„„„„³ DaemonDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±absent´³dict·„„„„„³ DaemonEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±absent´³dict·„„„„„³
DaemonSpec´³andµ´³dict·³argv´³named³argv´³seqof´³atom³String„„„„„´³named³env´³refµ„³ DaemonEnv„„´³named³dir´³refµ„³ DaemonDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„´³named³config´³refµ„³
DaemonSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„„„„³ServiceDependency´³rec´³lit³
´³bundle·µ³externalServices„´³schema·³version³ definitions·³Service´³refµ„³ DaemonService„³ClearEnv´³orµµ±present´³dict·³clearEnv´³named³clearEnv´³atom³Boolean„„„„„µ±invalid´³dict·³clearEnv´³named³clearEnv³any„„„„µ±absent´³dict·„„„„„³DaemonId³any³EnvValue´³orµµ±set´³atom³String„„µ±remove´³lit€„„µ±invalid³any„„„³ DaemonDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±invalid´³dict·³dir´³named³dir³any„„„„µ±absent´³dict·„„„„„³ DaemonEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±invalid´³dict·³env´³named³env³any„„„„µ±absent´³dict·„„„„„³
DaemonSpec´³orµµ±simple´³refµ„³ CommandLine„„µ±full´³refµ„³FullDaemonSpec„„„„³ CommandLine´³orµµ±shell´³atom³String„„µ±full´³refµ„³FullCommandLine„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„µ±invalid³any„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„´³named³config´³refµ„³
DaemonSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„„„„³FullDaemonSpec´³andµ´³dict·³argv´³named³argv´³refµ„³ CommandLine„„„„´³named³env´³refµ„³ DaemonEnv„„´³named³dir´³refµ„³ DaemonDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„³ServiceDependency´³rec´³lit³
depends-on„´³tupleµ´³named³depender³any„´³named³dependee³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³internalServices„´³schema·³version³ definitions·³ Milestone´³rec´³lit³ milestone„´³tupleµ´³named³name³any„„„„³ DebtReporter´³lit³ debt-reporter„³ ConfigWatcher´³rec´³lit³config-watcher„´³tupleµ´³named³path´³atom³String„„„„„³TcpRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„„„„³UnixRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Unix„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„

View File

@ -9,10 +9,14 @@ ServiceDependency = <depends-on @depender any @dependee any> .
DaemonProcess = <daemon @id DaemonId @config DaemonSpec>.
DaemonId = any .
DaemonSpec = { argv: [string ...] } & @env DaemonEnv & @dir DaemonDir & @clearEnv ClearEnv .
DaemonEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @absent {} .
DaemonDir = @present { dir: string } / @absent {} .
ClearEnv = @present { clearEnv: bool } / @absent {} .
DaemonSpec = @simple CommandLine / @full FullDaemonSpec .
FullDaemonSpec = { argv: CommandLine } & @env DaemonEnv & @dir DaemonDir & @clearEnv ClearEnv .
DaemonEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @invalid { env: any } / @absent {} .
DaemonDir = @present { dir: string } / @invalid { dir: any } / @absent {} .
ClearEnv = @present { clearEnv: bool } / @invalid { clearEnv: any } / @absent {} .
EnvVariable = @string string / @symbol symbol .
EnvValue = @set string / @remove #f .
CommandLine = @shell string / @full FullCommandLine .
FullCommandLine = [@program string, @args string ...] .
EnvVariable = @string string / @symbol symbol / @invalid any .
EnvValue = @set string / @remove #f / @invalid any .

View File

@ -5,8 +5,10 @@ use std::sync::Arc;
use syndicate::actor::*;
use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use tokio::process;
use crate::language::language;
use crate::schemas::external_services::{DaemonService, DaemonSpec};
use crate::schemas::external_services::*;
use syndicate_macros::during;
@ -24,6 +26,36 @@ pub fn on_demand(t: &mut Activation, config_ds: Arc<Cap>, root_ds: Arc<Cap>) {
});
}
fn cannot_start() -> ActorResult {
Err("Cannot start daemon process")?
}
impl DaemonSpec {
fn elaborate(self) -> FullDaemonSpec {
match self {
DaemonSpec::Simple(command_line) => FullDaemonSpec {
argv: *command_line,
env: DaemonEnv::Absent,
dir: DaemonDir::Absent,
clear_env: ClearEnv::Absent,
},
DaemonSpec::Full(spec) => *spec,
}
}
}
impl CommandLine {
fn elaborate(self) -> FullCommandLine {
match self {
CommandLine::Shell(s) => FullCommandLine {
program: "sh".to_owned(),
args: vec!["-c".to_owned(), s],
},
CommandLine::Full(command_line) => *command_line,
}
}
}
fn run(
t: &mut Activation,
config_ds: Arc<Cap>,
@ -35,11 +67,79 @@ fn run(
config_ds.assert(t, &(), &syndicate_macros::template!("<service-running =spec>"));
}
let id = service.id.0.clone();
Ok(during!(t, config_ds, language(), <daemon #(service.id.0) $config>, |t: &mut Activation| {
match language().parse::<DaemonSpec>(&config) {
Ok(config) => {
let config = config.elaborate();
t.linked_task(syndicate::name!("subprocess"), async move {
tracing::info!(?config);
let argv = config.argv.elaborate();
let mut cmd = process::Command::new(argv.program);
cmd.args(argv.args);
match config.dir {
DaemonDir::Present { dir } => { cmd.current_dir(dir); () },
DaemonDir::Absent => (),
DaemonDir::Invalid { dir } => {
tracing::error!(?dir, "Invalid working directory");
return cannot_start();
}
}
match config.clear_env {
ClearEnv::Present { clear_env: true } => { cmd.env_clear(); () },
ClearEnv::Present { clear_env: false } => (),
ClearEnv::Absent => (),
ClearEnv::Invalid { clear_env } => {
tracing::error!(?clear_env, "Invalid clearEnv setting");
return cannot_start();
}
}
match config.env {
DaemonEnv::Present { env } => {
for (k, v) in env {
if let Some(env_variable) = match k {
EnvVariable::String(k) => Some(k),
EnvVariable::Symbol(k) => Some(k),
EnvVariable::Invalid(env_variable) => {
tracing::error!(?env_variable,
"Invalid environment variable name");
return cannot_start();
}
} {
match v {
EnvValue::Set(value) => { cmd.env(env_variable, value); () }
EnvValue::Remove => { cmd.env_remove(env_variable); () }
EnvValue::Invalid(value) => {
tracing::error!(?env_variable, ?value,
"Invalid environment variable value");
return cannot_start();
}
}
}
}
}
DaemonEnv::Absent => (),
DaemonEnv::Invalid { env } => {
tracing::error!(?env, "Invalid daemon environment");
return cannot_start();
}
}
Ok(during!(t, config_ds, language(), <daemon #(id) $config>, |_t| {
if let Ok(config) = language().parse::<DaemonSpec>(&config) {
tracing::info!("daemon {:?} {:?}", &service, &config);
cmd.stdin(std::process::Stdio::null());
cmd.stdout(std::process::Stdio::inherit());
cmd.stderr(std::process::Stdio::inherit());
cmd.kill_on_drop(true);
tracing::info!(?cmd);
let mut child = cmd.spawn()?;
tracing::info!(status = ?child.wait().await);
Ok(())
});
}
Err(_) => {
tracing::error!(?config, "Invalid DaemonSpec");
return cannot_start();
}
}
Ok(())
}))