Begin teasing out general process specification schema

This commit is contained in:
Tony Garnock-Jones 2021-09-30 16:02:39 +02:00
parent b373d3440a
commit ea7e13b0c0
3 changed files with 90 additions and 74 deletions

View File

@ -2,4 +2,4 @@
ProcessDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±invalid´³dict·³dir´³named³dir³any„„„„µ±absent´³dict·„„„„„³ ProcessDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±invalid´³dict·³dir´³named³dir³any„„„„µ±absent´³dict·„„„„„³
ProcessEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±invalid´³dict·³env´³named³env³any„„„„µ±absent´³dict·„„„„„³ CommandLine´³orµµ±shell´³atom³String„„µ±full´³refµ„³FullCommandLine„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„µ±invalid³any„„„³ FullProcess´³andµ´³dict·³argv´³named³argv´³refµ„³ CommandLine„„„„´³named³env´³refµ„³ ProcessEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±invalid´³dict·³env´³named³env³any„„„„µ±absent´³dict·„„„„„³ CommandLine´³orµµ±shell´³atom³String„„µ±full´³refµ„³FullCommandLine„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„µ±invalid³any„„„³ FullProcess´³andµ´³dict·³argv´³named³argv´³refµ„³ CommandLine„„„„´³named³env´³refµ„³
ProcessEnv„„´³named³dir´³refµ„³ ProcessEnv„„´³named³dir´³refµ„³
ProcessDir„„´³named³clearEnv´³refµ„³ClearEnv„„´³named³ readyOnStart´³refµ„³ ReadyOnStart„„´³named³restart´³refµ„³ RestartField„„„„³ ReadyOnStart´³orµµ±present´³dict·³ readyOnStart´³named³ readyOnStart´³atom³Boolean„„„„„µ±invalid´³dict·³ readyOnStart´³named³ readyOnStart³any„„„„µ±absent´³dict·„„„„„³ RestartField´³orµµ±present´³dict·³restart´³named³restart´³refµ„³ RestartPolicy„„„„„µ±invalid´³dict·³restart´³named³restart³any„„„„µ±absent´³dict·„„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id³any„´³named³config´³refµ„³Process„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id³any„„„„³ RestartPolicy´³orµµ±always´³lit³always„„µ±onError´³lit³on-error„„µ±all´³lit³all„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„„³ 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„„„„„ ProcessDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³ ReadyOnStart´³orµµ±present´³dict·³ readyOnStart´³named³ readyOnStart´³atom³Boolean„„„„„µ±invalid´³dict·³ readyOnStart´³named³ readyOnStart³any„„„„µ±absent´³dict·„„„„„³ RestartField´³orµµ±present´³dict·³restart´³named³restart´³refµ„³ RestartPolicy„„„„„µ±invalid´³dict·³restart´³named³restart³any„„„„µ±absent´³dict·„„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id³any„´³named³config´³refµ„³DaemonProcessSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id³any„„„„³ RestartPolicy´³orµµ±always´³lit³always„„µ±onError´³lit³on-error„„µ±all´³lit³all„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„³DaemonProcessSpec´³orµµ±simple´³refµ„³ CommandLine„„µ±full´³refµ„³FullDaemonProcess„„„„³FullDaemonProcess´³andµ´³named³process´³refµ„³ FullProcess„„´³named³ readyOnStart´³refµ„³ ReadyOnStart„„´³named³restart´³refµ„³ RestartField„„„„„³ 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

@ -4,7 +4,12 @@ embeddedType EntityRef.Cap .
Service = DaemonService . Service = DaemonService .
DaemonService = <daemon @id any> . DaemonService = <daemon @id any> .
DaemonProcess = <daemon @id any @config Process>. DaemonProcess = <daemon @id any @config DaemonProcessSpec>.
DaemonProcessSpec = @simple CommandLine / @full FullDaemonProcess .
FullDaemonProcess = @process FullProcess & @readyOnStart ReadyOnStart & @restart RestartField .
ReadyOnStart = @present { readyOnStart: bool } / @invalid { readyOnStart: any } / @absent {} .
RestartField = @present { restart: RestartPolicy } / @invalid { restart: any } / @absent {} .
Process = @simple CommandLine / @full FullProcess . Process = @simple CommandLine / @full FullProcess .
FullProcess = FullProcess =
@ -12,14 +17,10 @@ FullProcess =
& @env ProcessEnv & @env ProcessEnv
& @dir ProcessDir & @dir ProcessDir
& @clearEnv ClearEnv & @clearEnv ClearEnv
& @readyOnStart ReadyOnStart
& @restart RestartField
. .
ProcessEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @invalid { env: any } / @absent {} . ProcessEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @invalid { env: any } / @absent {} .
ProcessDir = @present { dir: string } / @invalid { dir: any } / @absent {} . ProcessDir = @present { dir: string } / @invalid { dir: any } / @absent {} .
ClearEnv = @present { clearEnv: bool } / @invalid { clearEnv: any } / @absent {} . ClearEnv = @present { clearEnv: bool } / @invalid { clearEnv: any } / @absent {} .
ReadyOnStart = @present { readyOnStart: bool } / @invalid { readyOnStart: any } / @absent {} .
RestartField = @present { restart: RestartPolicy } / @invalid { restart: any } / @absent {} .
CommandLine = @shell string / @full FullCommandLine . CommandLine = @shell string / @full FullCommandLine .
FullCommandLine = [@program string, @args string ...] . FullCommandLine = [@program string, @args string ...] .

View File

@ -4,7 +4,6 @@ use std::sync::Arc;
use syndicate::actor::*; use syndicate::actor::*;
use syndicate::enclose; use syndicate::enclose;
use syndicate::error::Error;
use syndicate::supervise::{Supervisor, SupervisorConfiguration}; use syndicate::supervise::{Supervisor, SupervisorConfiguration};
use tokio::process; use tokio::process;
@ -30,10 +29,6 @@ pub fn on_demand(t: &mut Activation, config_ds: Arc<Cap>, root_ds: Arc<Cap>) {
}); });
} }
fn cannot_start<R>() -> Result<R, Error> {
Err("Cannot start daemon process")?
}
impl Process { impl Process {
fn elaborate(self) -> FullProcess { fn elaborate(self) -> FullProcess {
match self { match self {
@ -42,10 +37,83 @@ impl Process {
env: ProcessEnv::Absent, env: ProcessEnv::Absent,
dir: ProcessDir::Absent, dir: ProcessDir::Absent,
clear_env: ClearEnv::Absent, clear_env: ClearEnv::Absent,
},
Process::Full(spec) => *spec,
}
}
}
impl FullProcess {
fn build_command(&self) -> Option<process::Command> {
let argv = self.argv.clone().elaborate();
let mut cmd = process::Command::new(argv.program);
cmd.args(argv.args);
match &self.dir {
ProcessDir::Present { dir } => { cmd.current_dir(dir); () },
ProcessDir::Absent => (),
ProcessDir::Invalid { dir } => {
tracing::error!(?dir, "Invalid working directory");
return None;
}
}
match &self.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 None;
}
}
match &self.env {
ProcessEnv::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 None;
}
} {
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 None;
}
}
}
}
}
ProcessEnv::Absent => (),
ProcessEnv::Invalid { env } => {
tracing::error!(?env, "Invalid daemon environment");
return None;
}
}
cmd.stdin(std::process::Stdio::null());
cmd.stdout(std::process::Stdio::inherit());
cmd.stderr(std::process::Stdio::inherit());
cmd.kill_on_drop(true);
Some(cmd)
}
}
impl DaemonProcessSpec {
fn elaborate(self) -> FullDaemonProcess {
match self {
DaemonProcessSpec::Simple(command_line) => FullDaemonProcess {
process: Process::Simple(command_line).elaborate(),
ready_on_start: ReadyOnStart::Absent, ready_on_start: ReadyOnStart::Absent,
restart: RestartField::Absent, restart: RestartField::Absent,
}, },
Process::Full(spec) => *spec, DaemonProcessSpec::Full(spec) => *spec,
} }
} }
} }
@ -62,7 +130,7 @@ impl CommandLine {
} }
} }
struct ProcessInstance { struct DaemonInstance {
name: tracing::Span, name: tracing::Span,
cmd: process::Command, cmd: process::Command,
announce_presumed_readiness: bool, announce_presumed_readiness: bool,
@ -71,7 +139,7 @@ struct ProcessInstance {
restart_policy: RestartPolicy, restart_policy: RestartPolicy,
} }
impl ProcessInstance { impl DaemonInstance {
fn handle_exit(self, t: &mut Activation, error_message: Option<String>) -> ActorResult { fn handle_exit(self, t: &mut Activation, error_message: Option<String>) -> ActorResult {
let delay = let delay =
std::time::Duration::from_millis(if let None = error_message { 200 } else { 1000 }); std::time::Duration::from_millis(if let None = error_message { 200 } else { 1000 });
@ -184,68 +252,20 @@ fn run(
counter::adjust(t, &unready_configs, 1); counter::adjust(t, &unready_configs, 1);
counter::adjust(t, &total_configs, 1); counter::adjust(t, &total_configs, 1);
match language().parse::<Process>(&config) { match language().parse::<DaemonProcessSpec>(&config) {
Ok(config) => { Ok(config) => {
tracing::info!(?config); tracing::info!(?config);
let config = config.elaborate(); let config = config.elaborate();
let facet = t.facet.clone(); let facet = t.facet.clone();
t.linked_task(syndicate::name!("subprocess"), async move { t.linked_task(syndicate::name!("subprocess"), async move {
let argv = config.argv.elaborate(); let cmd = config.process.build_command().ok_or("Cannot start daemon process")?;
let mut cmd = process::Command::new(argv.program);
cmd.args(argv.args);
match config.dir {
ProcessDir::Present { dir } => { cmd.current_dir(dir); () },
ProcessDir::Absent => (),
ProcessDir::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 {
ProcessEnv::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();
}
}
}
}
}
ProcessEnv::Absent => (),
ProcessEnv::Invalid { env } => {
tracing::error!(?env, "Invalid daemon environment");
return cannot_start();
}
}
let announce_presumed_readiness = match config.ready_on_start { let announce_presumed_readiness = match config.ready_on_start {
ReadyOnStart::Present { ready_on_start } => ready_on_start, ReadyOnStart::Present { ready_on_start } => ready_on_start,
ReadyOnStart::Absent => true, ReadyOnStart::Absent => true,
ReadyOnStart::Invalid { ready_on_start } => { ReadyOnStart::Invalid { ready_on_start } => {
tracing::error!(?ready_on_start, "Invalid readyOnStart value"); tracing::error!(?ready_on_start, "Invalid readyOnStart value");
return cannot_start(); Err("Invalid readyOnStart value")?
} }
}; };
let restart_policy = match config.restart { let restart_policy = match config.restart {
@ -253,16 +273,11 @@ fn run(
RestartField::Absent => RestartPolicy::All, RestartField::Absent => RestartPolicy::All,
RestartField::Invalid { restart } => { RestartField::Invalid { restart } => {
tracing::error!(?restart, "Invalid restart value"); tracing::error!(?restart, "Invalid restart value");
return cannot_start(); Err("Invalid restart value")?
} }
}; };
cmd.stdin(std::process::Stdio::null()); let daemon_instance = DaemonInstance {
cmd.stdout(std::process::Stdio::inherit());
cmd.stderr(std::process::Stdio::inherit());
cmd.kill_on_drop(true);
let process_instance = ProcessInstance {
name: tracing::Span::current(), name: tracing::Span::current(),
cmd, cmd,
announce_presumed_readiness, announce_presumed_readiness,
@ -272,7 +287,7 @@ fn run(
}; };
facet.activate(Account::new(syndicate::name!("instance-startup")), |t| { facet.activate(Account::new(syndicate::name!("instance-startup")), |t| {
process_instance.start(t) daemon_instance.start(t)
})?; })?;
Ok(LinkedTaskTermination::KeepFacet) Ok(LinkedTaskTermination::KeepFacet)
}); });