diff --git a/src/cli.rs b/src/cli.rs index 26b3297..c8248ea 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,5 @@ use crate::lua::*; +use crate::nginx; use crate::nginx::*; use crate::run::run; use crate::types::*; @@ -10,7 +11,6 @@ use std::fmt::Display; use std::fs; use std::fs::File; use std::io::prelude::*; -use std::process; use std::process::Command; use thiserror::Error as ThisError; @@ -159,162 +159,6 @@ fn main_conf(user: &mut UserArgs) -> Vec { conf } -pub struct NginxExec { - prefix: String, - runner: Runner, - bin: String, - label: Option, -} - -impl From for Command { - fn from(ngx: NginxExec) -> Self { - let root = ngx.prefix; - - // resty CLI always adds a trailing slash - let prefix = format!("{}/", root.trim_end_matches('/')); - - let nginx = ngx.bin; - - let mut nginx_args = vec![ - String::from("-p"), - prefix, - String::from("-c"), - String::from("conf/nginx.conf"), - ]; - - if let Some(label) = ngx.label { - nginx_args.insert(0, String::from("-g")); - nginx_args.insert(1, label); - } - - let bin: String; - let mut args: Vec = vec![]; - - match ngx.runner { - Runner::Default => { - bin = nginx; - args.append(&mut nginx_args); - } - Runner::RR => { - bin = String::from("rr"); - args.push(String::from("record")); - args.push(nginx); - args.append(&mut nginx_args); - } - Runner::Stap(opts) => { - bin = String::from("stap"); - args = vec![]; - if let Some(opts) = opts { - args.append(&mut split_shell_args(&opts)); - } - args.push("-c".to_owned()); - nginx_args.insert(0, nginx); - args.push(join_shell_args(&nginx_args)); - } - Runner::Valgrind(opts) => { - bin = "valgrind".to_owned(); - args = vec![]; - if let Some(opts) = opts { - args.append(&mut split_shell_args(&opts)); - } - args.push(nginx); - args.append(&mut nginx_args); - } - Runner::Gdb(opts) => { - bin = String::from("gdb"); - if let Some(opts) = opts { - args.append(&mut split_shell_args(&opts)); - } - args.push("--args".to_owned()); - args.push(nginx); - args.append(&mut nginx_args); - } - Runner::User(runner) => { - let mut user_args = split_shell_args(&runner); - bin = user_args.remove(0); - args.append(&mut user_args); - args.push(nginx); - args.append(&mut nginx_args); - } - }; - - let mut c = process::Command::new(bin); - - c.args(args); - c - } -} - -#[derive(Default, Debug)] -pub(crate) enum Runner { - #[default] - Default, - RR, - Stap(Option), - Valgrind(Option), - Gdb(Option), - User(String), -} - -impl Runner { - fn arg_name(&self) -> String { - match self { - Self::RR => "--rr", - Self::Stap(_) => "--stap", - Self::Gdb(_) => "--gdb", - Self::Valgrind(_) => "--valgrind", - Self::User(_) => "--user-runner", - _ => unreachable!(), - } - .to_owned() - } - - fn opt_name(&self) -> String { - self.arg_name() + "-opts" - } - - fn same(&self, other: &Runner) -> bool { - std::mem::discriminant(self) == std::mem::discriminant(other) - } - - fn takes_opts(&self) -> bool { - match self { - Self::Stap(_) => true, - Self::Gdb(_) => true, - Self::Valgrind(_) => true, - Self::User(_) => false, - Self::RR => false, - Self::Default => false, - } - } - - fn has_opts(&self) -> bool { - match self { - Self::Stap(o) | Self::Gdb(o) | Self::Valgrind(o) => o.is_some(), - Self::User(_) => true, - Self::RR => false, - Self::Default => false, - } - } - - fn update(&mut self, new: Runner) -> Result<(), ArgError> { - if let Runner::Default = self { - *self = new; - Ok(()) - } else if self.same(&new) { - // e.g. we already saw --gdb and are now adding opts with --gdb-opts - if self.takes_opts() && !self.has_opts() && new.has_opts() { - *self = new; - Ok(()) - } else { - Err(ArgError::Duplicate(new.opt_name())) - } - } else { - Err(ArgError::Conflict(self.arg_name(), new.arg_name())) - } - } -} - #[derive(ThisError, Debug)] pub enum ArgError { #[error("ERROR: could not find {0} include file '{1}'")] @@ -490,8 +334,10 @@ impl Action { user.inline_lua.insert(0, jit.to_lua()); } + let resty_compat_version = get_resty_compat_version(); let mut label = None; - if get_resty_compat_version() >= 30 { + + if resty_compat_version >= 30 { let mut s = String::from("# "); if !user.inline_lua.is_empty() { s.push_str("-e '"); @@ -527,16 +373,6 @@ impl Action { } }; - let vars = Vars { - main_conf: main_conf(&mut user), - stream_enabled: !user.no_stream, - stream_conf: stream_conf(&mut user), - http_conf: http_conf(&mut user), - events_conf: vec![format!("worker_connections {};", user.worker_connections)], - lua_loader, - resty_compat_version: crate::nginx::get_resty_compat_version(), - }; - let conf_path = prefix.conf.join("nginx.conf"); let mut file = match fs::File::create(conf_path) { Ok(file) => file, @@ -546,14 +382,26 @@ impl Action { } }; - if let Err(e) = render_config(&mut file, vars).and_then(|_| file.flush()) { + let events_conf = vec![format!("worker_connections {};", user.worker_connections)]; + + let res = nginx::ConfBuilder::new() + .main(main_conf(&mut user)) + .events(events_conf) + .stream(stream_conf(&mut user), !user.no_stream) + .http(http_conf(&mut user)) + .lua(lua_loader) + .resty_compat_version(resty_compat_version) + .render(&mut file) + .and_then(|_| file.flush()); + + if let Err(e) = res { eprintln!("failed writing nginx.conf file: {}", e); return 2; } drop(file); - let ngx = NginxExec { + let ngx = nginx::Exec { bin: find_nginx_bin(user.nginx_bin).to_str().unwrap().to_string(), prefix: prefix.root.to_str().unwrap().to_string(), runner: user.runner, diff --git a/src/nginx.rs b/src/nginx.rs index f8d0eb7..9789b94 100644 --- a/src/nginx.rs +++ b/src/nginx.rs @@ -1,6 +1,9 @@ +use crate::cli::ArgError; +use crate::util::*; use std::env; use std::io; use std::path::PathBuf; +use std::process::Command; const RESTY_COMPAT_VAR: &str = "RESTY_CLI_COMPAT_VERSION"; const RESTY_COMPAT_LATEST: u64 = 30; @@ -8,47 +11,206 @@ const RESTY_COMPAT_LATEST: u64 = 30; const BLOCK_OPEN: &str = "{"; const BLOCK_CLOSE: &str = "}"; -pub struct Vars { - pub events_conf: Vec, - pub main_conf: Vec, +#[derive(Debug, Default)] +pub struct ConfBuilder { + events: Option>, + main: Option>, + stream_enabled: bool, + stream: Option>, + http: Option>, + lua: Option>, + resty_compat_version: Option, +} + +impl ConfBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn events(mut self, t: T) -> Self + where + T: IntoIterator, + { + self.events = Some(Vec::from_iter(t)); + self + } + + pub fn main(mut self, t: T) -> Self + where + T: IntoIterator, + { + self.main = Some(Vec::from_iter(t)); + self + } + + pub fn http(mut self, t: T) -> Self + where + T: IntoIterator, + { + self.http = Some(Vec::from_iter(t)); + self + } + + pub fn stream(mut self, t: T, enabled: bool) -> Self + where + T: IntoIterator, + { + self.stream = Some(Vec::from_iter(t)); + self.stream_enabled = enabled; + self + } + + pub fn lua(mut self, t: T) -> Self + where + T: IntoIterator, + { + self.lua = Some(Vec::from_iter(t)); + self + } + + pub fn resty_compat_version(mut self, v: u64) -> Self { + self.resty_compat_version = Some(v); + self + } + + pub fn render(self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + let buf = &mut io::BufWriter::new(buf); + + let Self { + events, + main, + stream_enabled, + stream, + http, + lua, + resty_compat_version, + } = self; + + Conf { + events: events.unwrap_or_default(), + main: main.unwrap_or_default(), + stream: stream.unwrap_or_default(), + stream_enabled, + http: http.unwrap_or_default(), + lua: lua.unwrap_or_default(), + resty_compat_version: resty_compat_version.unwrap_or_else(get_resty_compat_version), + } + .render(buf) + } +} + +struct Conf { + pub events: Vec, + pub main: Vec, pub stream_enabled: bool, - pub stream_conf: Vec, - pub http_conf: Vec, - pub lua_loader: Vec, + pub stream: Vec, + pub http: Vec, + pub lua: Vec, pub resty_compat_version: u64, } -fn render_stream(buf: &mut T, vars: &Vars) -> io::Result<()> -where - T: io::Write, -{ - if vars.stream_enabled { - writeln!(buf, "stream {}", BLOCK_OPEN)?; - writeln!( - buf, - r##" - access_log off; - lua_socket_log_errors off; - lua_regex_cache_max_entries 40960; -"## - )?; - - for line in &vars.stream_conf { +impl Conf { + fn render(self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + self.render_main(buf)?; + self.render_events(buf)?; + self.render_stream(buf)?; + self.render_http(buf)?; + + Ok(()) + } + + fn render_main(&self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + writeln!(buf, "# generated by rusty-cli")?; + writeln!(buf, "# resty-cli compat: v0.{}", self.resty_compat_version)?; + writeln!(buf)?; + + writeln!(buf, "daemon off;")?; + writeln!(buf, "master_process off;")?; + writeln!(buf, "worker_processes 1;")?; + writeln!(buf, "pid logs/nginx.pid;")?; + + for line in &self.main { + writeln!(buf, "{}", line)?; + } + + writeln!(buf)?; + + Ok(()) + } + + fn render_events(&self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + writeln!(buf, "events {}", BLOCK_OPEN)?; + + for line in &self.events { writeln!(buf, " {}", line)?; } writeln!(buf, "{}", BLOCK_CLOSE)?; writeln!(buf)?; + + Ok(()) } - Ok(()) -} + fn render_stream(&self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + if self.stream_enabled { + writeln!(buf, "stream {}", BLOCK_OPEN)?; + + writeln!(buf, " access_log off;")?; + writeln!(buf, " lua_socket_log_errors off;")?; + writeln!(buf, " lua_regex_cache_max_entries 40960;")?; + + for line in &self.stream { + writeln!(buf, " {}", line)?; + } + + writeln!(buf, "{}", BLOCK_CLOSE)?; + writeln!(buf)?; + } + + Ok(()) + } + + fn render_http(&self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + writeln!(buf, "http {}", BLOCK_OPEN)?; + writeln!(buf, " access_log off;")?; + writeln!(buf, " lua_socket_log_errors off;")?; + writeln!(buf, " lua_regex_cache_max_entries 40960;")?; -fn render_lua(buf: &mut T, vars: &Vars) -> io::Result<()> -where - T: io::Write, -{ - const INIT_BY_LUA_OPEN: &str = r##" + for line in &self.http { + writeln!(buf, " {}", line)?; + } + writeln!(buf)?; + + self.render_lua(buf)?; + + writeln!(buf, "{}", BLOCK_CLOSE)?; + + Ok(()) + } + + fn render_lua(&self, buf: &mut T) -> io::Result<()> + where + T: io::Write, + { + const INIT_BY_LUA_OPEN: &str = r##" init_by_lua_block { ngx.config.is_console = true @@ -115,12 +277,12 @@ where ngx.eof = function (...) return true end "##; - const INIT_BY_LUA_CLOSE: &str = " + const INIT_BY_LUA_CLOSE: &str = " ngx.exit = os.exit } "; - const INIT_WORKER_BY_LUA_OPEN: &str = r##" + const INIT_WORKER_BY_LUA_OPEN: &str = r##" init_worker_by_lua_block { local exit = os.exit local stderr = io.stderr @@ -151,7 +313,7 @@ where end "##; - const INIT_WORKER_BY_LUA_CLOSE: &str = r##" + const INIT_WORKER_BY_LUA_CLOSE: &str = r##" -- print("calling timer.at...") local ok, err = ngx.timer.at(0, function () -- io.stderr:write("timer firing") @@ -181,111 +343,30 @@ where } "##; - writeln!(buf, "{}", INIT_BY_LUA_OPEN)?; - writeln!(buf)?; - - if vars.resty_compat_version >= 29 { - writeln!(buf, " ngx.orig_exit = ngx.exit")?; + writeln!(buf, "{}", INIT_BY_LUA_OPEN)?; writeln!(buf)?; - } - - writeln!(buf, "{}", INIT_BY_LUA_CLOSE)?; - writeln!(buf)?; - - writeln!(buf, "{}", INIT_WORKER_BY_LUA_OPEN)?; - writeln!(buf)?; - for line in &vars.lua_loader { - writeln!(buf, " {}", line)?; - } - writeln!(buf)?; - - writeln!(buf, "{}", INIT_WORKER_BY_LUA_CLOSE)?; - writeln!(buf)?; - - Ok(()) -} - -fn render_http(buf: &mut T, vars: &Vars) -> io::Result<()> -where - T: io::Write, -{ - writeln!(buf, "http {}", BLOCK_OPEN)?; - writeln!( - buf, - r##" - access_log off; - lua_socket_log_errors off; - lua_regex_cache_max_entries 40960; -"## - )?; - - for line in &vars.http_conf { - writeln!(buf, " {}", line)?; - } - writeln!(buf)?; - - render_lua(buf, vars)?; - - writeln!(buf, "{}", BLOCK_CLOSE)?; - - Ok(()) -} + if self.resty_compat_version >= 29 { + writeln!(buf, " ngx.orig_exit = ngx.exit")?; + writeln!(buf)?; + } -fn render_events(buf: &mut T, vars: &Vars) -> io::Result<()> -where - T: io::Write, -{ - writeln!(buf, "events {}", BLOCK_OPEN)?; + writeln!(buf, "{}", INIT_BY_LUA_CLOSE)?; + writeln!(buf)?; - for line in &vars.events_conf { - writeln!(buf, " {}", line)?; - } + writeln!(buf, "{}", INIT_WORKER_BY_LUA_OPEN)?; + writeln!(buf)?; - writeln!(buf, "{}", BLOCK_CLOSE)?; + for line in &self.lua { + writeln!(buf, " {}", line)?; + } + writeln!(buf)?; - Ok(()) -} + writeln!(buf, "{}", INIT_WORKER_BY_LUA_CLOSE)?; + writeln!(buf)?; -pub fn render_main(buf: &mut T, vars: &Vars) -> io::Result<()> -where - T: io::Write, -{ - writeln!(buf, "# generated by rusty-cli")?; - writeln!(buf, "# resty-cli compat: v0.{}", vars.resty_compat_version)?; - writeln!(buf)?; - - writeln!( - buf, - r##" -daemon off; -master_process off; -worker_processes 1; -pid logs/nginx.pid; -"## - )?; - - for line in &vars.main_conf { - writeln!(buf, "{}", line)?; + Ok(()) } - - writeln!(buf)?; - - Ok(()) -} - -pub fn render_config(buf: &mut T, vars: Vars) -> io::Result<()> -where - T: io::Write, -{ - let buf = &mut io::BufWriter::new(buf); - - render_main(buf, &vars)?; - render_events(buf, &vars)?; - render_stream(buf, &vars)?; - render_http(buf, &vars)?; - - Ok(()) } pub fn find_nginx_bin(nginx: Option) -> PathBuf { @@ -329,3 +410,159 @@ pub fn get_resty_compat_version() -> u64 { None => RESTY_COMPAT_LATEST, } } + +pub struct Exec { + pub prefix: String, + pub runner: Runner, + pub bin: String, + pub label: Option, +} + +impl From for Command { + fn from(ngx: Exec) -> Self { + let root = ngx.prefix; + + // resty CLI always adds a trailing slash + let prefix = format!("{}/", root.trim_end_matches('/')); + + let nginx = ngx.bin; + + let mut nginx_args = vec![ + String::from("-p"), + prefix, + String::from("-c"), + String::from("conf/nginx.conf"), + ]; + + if let Some(label) = ngx.label { + nginx_args.insert(0, String::from("-g")); + nginx_args.insert(1, label); + } + + let bin: String; + let mut args: Vec = vec![]; + + match ngx.runner { + Runner::Default => { + bin = nginx; + args.append(&mut nginx_args); + } + Runner::RR => { + bin = String::from("rr"); + args.push(String::from("record")); + args.push(nginx); + args.append(&mut nginx_args); + } + Runner::Stap(opts) => { + bin = String::from("stap"); + args = vec![]; + if let Some(opts) = opts { + args.append(&mut split_shell_args(&opts)); + } + args.push("-c".to_owned()); + nginx_args.insert(0, nginx); + args.push(join_shell_args(&nginx_args)); + } + Runner::Valgrind(opts) => { + bin = "valgrind".to_owned(); + args = vec![]; + if let Some(opts) = opts { + args.append(&mut split_shell_args(&opts)); + } + args.push(nginx); + args.append(&mut nginx_args); + } + Runner::Gdb(opts) => { + bin = String::from("gdb"); + if let Some(opts) = opts { + args.append(&mut split_shell_args(&opts)); + } + args.push("--args".to_owned()); + args.push(nginx); + args.append(&mut nginx_args); + } + Runner::User(runner) => { + let mut user_args = split_shell_args(&runner); + bin = user_args.remove(0); + args.append(&mut user_args); + args.push(nginx); + args.append(&mut nginx_args); + } + }; + + let mut c = Command::new(bin); + + c.args(args); + c + } +} + +#[derive(Default, Debug)] +pub enum Runner { + #[default] + Default, + RR, + Stap(Option), + Valgrind(Option), + Gdb(Option), + User(String), +} + +impl Runner { + fn arg_name(&self) -> String { + match self { + Self::RR => "--rr", + Self::Stap(_) => "--stap", + Self::Gdb(_) => "--gdb", + Self::Valgrind(_) => "--valgrind", + Self::User(_) => "--user-runner", + _ => unreachable!(), + } + .to_owned() + } + + fn opt_name(&self) -> String { + self.arg_name() + "-opts" + } + + fn same(&self, other: &Runner) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } + + fn takes_opts(&self) -> bool { + match self { + Self::Stap(_) => true, + Self::Gdb(_) => true, + Self::Valgrind(_) => true, + Self::User(_) => false, + Self::RR => false, + Self::Default => false, + } + } + + fn has_opts(&self) -> bool { + match self { + Self::Stap(o) | Self::Gdb(o) | Self::Valgrind(o) => o.is_some(), + Self::User(_) => true, + Self::RR => false, + Self::Default => false, + } + } + + pub fn update(&mut self, new: Runner) -> Result<(), ArgError> { + if let Runner::Default = self { + *self = new; + Ok(()) + } else if self.same(&new) { + // e.g. we already saw --gdb and are now adding opts with --gdb-opts + if self.takes_opts() && !self.has_opts() && new.has_opts() { + *self = new; + Ok(()) + } else { + Err(ArgError::Duplicate(new.opt_name())) + } + } else { + Err(ArgError::Conflict(self.arg_name(), new.arg_name())) + } + } +}