evergreen/
init.rs

1//! Connect to OpenSRF/Redis, load host settings, and load the IDL.
2use crate::idl;
3use crate::norm::Normalizer;
4use crate::osrf::conf;
5use crate::osrf::conf::LogFile;
6use crate::osrf::logging;
7use crate::osrf::sclient::HostSettings;
8use crate::Client;
9use crate::EgResult;
10use daemonize;
11use std::env;
12use std::fs::File;
13
14const DEFAULT_OSRF_CONFIG: &str = "/openils/conf/opensrf_core.xml";
15const DEFAULT_IDL_PATH: &str = "/openils/conf/fm_IDL.xml";
16
17#[derive(Default)]
18pub struct InitOptions {
19    /// Skip logging initialization.
20    /// Useful if changes to the logging config first.
21    pub skip_logging: bool,
22
23    /// Skip fetching the host settings from opensrf.settings
24    pub skip_host_settings: bool,
25
26    /// Application name to use with syslog.
27    pub appname: Option<String>,
28}
29
30impl InitOptions {
31    pub fn new() -> InitOptions {
32        Default::default()
33    }
34}
35
36/// Read environment variables, parse the core config, setup logging.
37///
38/// This does not connect to the bus.
39pub fn init() -> EgResult<Client> {
40    with_options(&InitOptions::new())
41}
42
43/// If a pid file is provided, daemonize this process and write
44/// the PID file.
45fn maybe_daemonize() -> EgResult<()> {
46    // If a pid file is provided, we're running in daemonized mode.
47    let pid_file = match env::var("OSRF_PID_FILE") {
48        Ok(f) => f,
49        Err(_) => return Ok(()),
50    };
51
52    let out_file = match env::var("OSRF_STDERR_FILE") {
53        Ok(f) => f.to_string(),
54        Err(_) => format!("{pid_file}.stderr"),
55    };
56
57    // For now, stdout and stderr are routed to the same file.
58    let stdout_file =
59        File::create(&out_file).map_err(|e| format!("Cannot create stderr file: {e}"))?;
60
61    let stderr_file =
62        File::create(out_file).map_err(|e| format!("Cannot create stderr file: {e}"))?;
63
64    let daemon = daemonize::Daemonize::new()
65        .pid_file(pid_file)
66        .chown_pid_file(true) // is optional, see `Daemonize` documentation
67        .working_directory("/tmp") // for default behaviour.
68        .stdout(stdout_file)
69        .stderr(stderr_file);
70
71    daemon
72        .start()
73        .map_err(|e| format!("Cannot daemonize:, {e}").into())
74}
75
76/// Parse the OpenSRF config file, connect to the message bus, and
77/// optionally fetch the host settings and initialize logging.
78pub fn osrf_init(options: &InitOptions) -> EgResult<Client> {
79    maybe_daemonize()?;
80
81    let builder = if let Ok(fname) = env::var("OSRF_CONFIG") {
82        conf::ConfigBuilder::from_file(&fname)?
83    } else {
84        conf::ConfigBuilder::from_file(DEFAULT_OSRF_CONFIG)?
85    };
86
87    let mut config = builder.build()?;
88
89    if env::var("OSRF_LOCALHOST").is_ok() {
90        config.set_hostname("localhost");
91    } else if let Ok(v) = env::var("OSRF_HOSTNAME") {
92        config.set_hostname(&v);
93    }
94
95    if env::var("OSRF_LOG_STDOUT").is_ok() {
96        config
97            .client_mut()
98            .logging_mut()
99            .set_log_file(LogFile::Stdout);
100    }
101
102    // When custom client connection/logging values are provided via
103    // the ENV, propagate them to all variations of a client connection
104    // supported by the current opensrf_core.xml format.
105
106    if let Ok(level) = env::var("OSRF_LOG_LEVEL") {
107        config.client_mut().logging_mut().set_log_level(&level);
108        if let Some(gateway) = config.gateway_mut() {
109            gateway.logging_mut().set_log_level(&level);
110        }
111        for router in config.routers_mut() {
112            router.client_mut().logging_mut().set_log_level(&level);
113        }
114    }
115
116    if let Ok(facility) = env::var("OSRF_LOG_FACILITY") {
117        config
118            .client_mut()
119            .logging_mut()
120            .set_syslog_facility(&facility)?;
121        if let Some(gateway) = config.gateway_mut() {
122            gateway.logging_mut().set_syslog_facility(&facility)?;
123        }
124        for router in config.routers_mut() {
125            router
126                .client_mut()
127                .logging_mut()
128                .set_syslog_facility(&facility)?;
129        }
130    }
131
132    if let Ok(username) = env::var("OSRF_BUS_USERNAME") {
133        config.client_mut().set_username(&username);
134        if let Some(gateway) = config.gateway_mut() {
135            gateway.set_username(&username);
136        }
137        for router in config.routers_mut() {
138            router.client_mut().set_username(&username);
139        }
140    }
141
142    if let Ok(password) = env::var("OSRF_BUS_PASSWORD") {
143        config.client_mut().set_password(&password);
144        if let Some(gateway) = config.gateway_mut() {
145            gateway.set_password(&password);
146        }
147        for router in config.routers_mut() {
148            router.client_mut().set_password(&password);
149        }
150    }
151
152    if !options.skip_logging {
153        let mut logger = logging::Logger::new(config.client().logging())?;
154        if let Some(name) = options.appname.as_ref() {
155            logger.set_application(name);
156        }
157        logger
158            .init()
159            .map_err(|e| format!("Error initializing logger: {e}"))?;
160    }
161
162    // Save the config as the one-true-global-osrf-config
163    config.store()?;
164
165    let client = Client::connect()?;
166
167    // We try to get the IDL path from opensrf.settings, but that will
168    // fail if we are not connected to a domain running opensrf.settings
169    // (e.g. a public domain).
170
171    if !options.skip_host_settings {
172        HostSettings::load(&client)?;
173    }
174
175    Ok(client)
176}
177
178pub fn with_options(options: &InitOptions) -> EgResult<Client> {
179    let client = osrf_init(options)?;
180
181    // Compile the normalizer regex's so the caller doesn't have to.
182    Normalizer::init();
183
184    load_idl()?;
185
186    Ok(client)
187}
188
189/// Locate and parse the IDL file.
190pub fn load_idl() -> EgResult<()> {
191    if let Ok(v) = env::var("EG_IDL_FILE") {
192        return idl::Parser::load_file(&v);
193    }
194
195    if HostSettings::is_loaded() {
196        if let Some(fname) = HostSettings::get("/IDL")?.as_str() {
197            return idl::Parser::load_file(fname);
198        }
199    }
200
201    idl::Parser::load_file(DEFAULT_IDL_PATH)
202}