defined env

This commit is contained in:
Jędrzej Stuczyński
2023-11-01 14:14:59 +00:00
parent ea834a60a5
commit 29d2ab4a7a
14 changed files with 254 additions and 11 deletions
Generated
+3
View File
@@ -7600,12 +7600,15 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.4.7",
"dotenvy",
"humantime 2.1.0",
"humantime-serde",
"lazy_static",
"nix 0.27.1",
"nym-bin-common",
"nym-config",
"serde",
"thiserror",
"tokio",
"tracing",
]
+1 -1
View File
@@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
chrono = { version = "0.4.31", features = ["serde"] }
clap = { workspace = true, features = ["cargo", "derive"] }
dotenvy = "0.15.6"
dotenvy = { workspace = true }
humantime-serde = "1.0"
isocountry = "0.3.2"
itertools = "0.10.3"
+1 -1
View File
@@ -10,7 +10,7 @@ bs58 = "0.4"
clap = { workspace = true, features = ["derive"] }
clap_complete = "4.0"
clap_complete_fig = "4.0"
dotenvy = "0.15.6"
dotenvy = { workspace = true }
log = { workspace = true }
pretty_env_logger = "0.4"
serde = { version = "1", features = ["derive"] }
+3
View File
@@ -13,11 +13,14 @@ license.workspace = true
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
dotenvy = { workspace = true }
humantime = "2.1.0"
humantime-serde = "1.1.1"
lazy_static = { workspace = true }
nix = "0.27.1"
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["rt", "macros", "signal", "process"] }
thiserror = { workspace = true }
tracing = { workspace = true }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymvisorError;
use nym_bin_common::output_format::OutputFormat;
#[derive(clap::Args, Debug)]
@@ -9,7 +10,7 @@ pub(crate) struct Args {
output: OutputFormat,
}
pub(crate) fn execute(args: Args) -> anyhow::Result<()> {
pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
println!("add upgrade");
Ok(())
}
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymvisorError;
use nym_bin_common::bin_info_owned;
use nym_bin_common::output_format::OutputFormat;
@@ -10,7 +11,7 @@ pub(crate) struct Args {
output: OutputFormat,
}
pub(crate) fn execute(args: Args) -> anyhow::Result<()> {
pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
println!("{}", args.output.format(&bin_info_owned!()));
Ok(())
}
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymvisorError;
use nym_bin_common::output_format::OutputFormat;
#[derive(clap::Args, Debug)]
@@ -9,7 +10,7 @@ pub(crate) struct Args {
output: OutputFormat,
}
pub(crate) fn execute(args: Args) -> anyhow::Result<()> {
pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
println!("config");
Ok(())
}
+5 -1
View File
@@ -1,6 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::env::Env;
use crate::error::NymvisorError;
use nym_bin_common::output_format::OutputFormat;
#[derive(clap::Args, Debug)]
@@ -9,7 +11,9 @@ pub(crate) struct Args {
output: OutputFormat,
}
pub(crate) fn execute(args: Args) -> anyhow::Result<()> {
pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
let env = Env::try_read()?;
println!("init");
Ok(())
}
+9 -3
View File
@@ -7,6 +7,8 @@ mod config;
mod init;
mod run;
use crate::env::setup_env;
use crate::error::NymvisorError;
use clap::{Parser, Subcommand};
use lazy_static::lazy_static;
use nym_bin_common::bin_info;
@@ -23,14 +25,18 @@ fn pretty_build_info_static() -> &'static str {
#[derive(Parser, Debug)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Cli {
// I doubt we're gonna need any global flags here, but I'm going to leave the the option open
// so that'd be easier to add them later if needed
/// Path pointing to an env file that configures the nymvisor and overrides any preconfigured values.
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
impl Cli {
pub(crate) fn execute(self) -> anyhow::Result<()> {
pub(crate) fn execute(self) -> Result<(), NymvisorError> {
setup_env(&self.config_env_file)?;
match self.command {
Commands::Init(args) => init::execute(args),
Commands::Run(args) => run::execute(args),
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymvisorError;
use tokio::runtime;
#[derive(clap::Args, Debug)]
@@ -10,7 +11,7 @@ pub(crate) struct Args {
daemon_args: Vec<String>,
}
pub(crate) fn execute(args: Args) -> anyhow::Result<()> {
pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
// TODO: experiment with the minimal runtime
// look at futures::executor::LocalPool
let rt = runtime::Builder::new_current_thread().enable_io().build()?;
+15
View File
@@ -123,6 +123,7 @@ impl Config {
#[serde(deny_unknown_fields)]
pub struct Nymvisor {
/// ID specifies the human readable ID of this particular nymvisor instance.
/// Can be overridden with $NYMVISOR_ID environmental variable.
pub id: String,
/// Further optional configuration options associated with the nymvisor.
@@ -134,10 +135,12 @@ pub struct Nymvisor {
pub struct NymvisorDebug {
/// If set to true, this will disable `nymvisor` logs (but not the underlying process)
/// default: false
/// Can be overridden with $NYMVISOR_DISABLE_LOGS environmental variable.
pub disable_logs: bool,
/// Set custom directory for upgrade data - binaries and upgrade plans.
/// If not set, the global nymvisors' data directory will be used instead.
/// Can be overridden with $NYMVISOR_DATA_UPGRADE_DIRECTORY environmental variable.
#[serde(deserialize_with = "de_maybe_path")]
pub data_upgrade_directory: Option<PathBuf>,
}
@@ -156,11 +159,13 @@ impl Default for NymvisorDebug {
#[serde(deny_unknown_fields)]
pub struct Daemon {
/// The name of the managed binary itself (e.g. nym-api, nym-mixnode, nym-gateway, etc.)
/// Can be overridden with $DAEMON_NAME environmental variable.
pub name: String,
/// The location where the `nymvisor/` directory is kept that contains the auxiliary files associated
/// with the underlying daemon, such as any backups or current version information.
/// (e.g. $HOME/.nym/nym-api/my-nym-api, $HOME/.nym/mixnodes/my-mixnode, etc.).
/// Can be overridden with $DAEMON_HOME environmental variable.
pub home: String,
/// Further optional configuration options associated with the daemon.
@@ -172,26 +177,31 @@ pub struct Daemon {
pub struct DaemonDebug {
/// If set to true, this will enable auto-downloading of new binaries using the url provided in the `upgrade-info.json`
/// default: true
/// Can be overridden with $DAEMON_ALLOW_BINARIES_DOWNLOAD environmental variable.
pub allow_binaries_download: bool,
/// If enabled nymvisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded.
/// If disabled, nymvisor will not require a checksum to be provided, but still check the checksum if one is provided.
/// default: true
/// Can be overridden with $DAEMON_ENFORCE_DOWNLOAD_CHECKSUM environmental variable.
pub enforce_download_checksum: bool,
/// If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade.
/// Otherwise (if disabled), nymvisor will stop running after an upgrade and will require the system administrator to manually restart it.
/// Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs.
/// default: true
/// Can be overridden with $DAEMON_RESTART_AFTER_UPGRADE environmental variable.
pub restart_after_upgrade: bool,
/// If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags after it has crashed
/// default: false
/// Can be overridden with $DAEMON_RESTART_ON_FAILURE environmental variable.
pub restart_on_failure: bool,
/// If `restart_on_failure` is enabled, the following value defines the amount of time `nymvisor` shall wait before
/// restarting the subprocess.
/// default: 10s
/// Can be overridden with $DAEMON_FAILURE_RESTART_DELAY environmental variable.
// The default value is so relatively high as to prevent constant restart loops in case of some underlying issue.
#[serde(with = "humantime_serde")]
pub failure_restart_delay: Duration,
@@ -199,11 +209,13 @@ pub struct DaemonDebug {
/// Defines the maximum number of startup failures the subprocess can experience in a quick succession before
/// no further restarts will be attempted and `nymvisor` will exit/
/// default: 10
/// Can be overridden with $DAEMON_MAX_STARTUP_FAILURES environmental variable.
pub max_startup_failures: usize,
/// Defines the length of time during which the subprocess is still considered to be in the startup phase
/// when its failures are going to be considered in `max_startup_failures`.
/// default: 120s
/// Can be overridden with $DAEMON_STARTUP_PERIOD_DURATION environmental variable.
#[serde(with = "humantime_serde")]
pub startup_period_duration: Duration,
@@ -211,15 +223,18 @@ pub struct DaemonDebug {
/// (for either an upgrade or shutdown of the `nymvisor` itself)
/// Once the time passes, a kill signal is going to be sent instead.
/// default: 10s
/// Can be overridden with $DAEMON_SHUTDOWN_GRACE_PERIOD environmental variable.
#[serde(with = "humantime_serde")]
pub shutdown_grace_period: Duration,
/// Set custom backup directory for daemon data. If not set, the daemon's home directory will be used instead.
/// Can be overridden with $DAEMON_DATA_BACKUP_DIRECTORYenvironmental variable.
#[serde(deserialize_with = "de_maybe_path")]
pub data_backup_directory: Option<PathBuf>,
/// If enabled, `nymvisor` will perform upgrades directly without performing any backups.
/// default: false
/// Can be overridden with $DAEMON_UNSAFE_SKIP_BACKUP environmental variable.
pub unsafe_skip_backup: bool,
}
+147
View File
@@ -0,0 +1,147 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymvisorError;
use std::env::VarError;
use std::path::PathBuf;
use std::time::Duration;
const TRUTHY_BOOLS: &[&str] = &["true", "t", "1"];
const FALSY_BOOLS: &[&str] = &["false", "f", "0"];
pub mod vars {
pub const NYMVISOR_ID: &str = "NYMVISOR_ID";
pub const NYMVISOR_CONFIG_PATH: &str = "NYMVISOR_CONFIG_PATH";
pub const NYMVISOR_DATA_DIRECTORY: &str = "NYMVISOR_DATA_DIRECTORY";
pub const NYMVISOR_DISABLE_LOGS: &str = "NYMVISOR_DISABLE_LOGS";
pub const NYMVISOR_DATA_UPGRADE_DIRECTORY: &str = "NYMVISOR_DATA_UPGRADE_DIRECTORY";
pub const DAEMON_NAME: &str = "DAEMON_NAME";
pub const DAEMON_HOME: &str = "DAEMON_HOME";
pub const DAEMON_ALLOW_BINARIES_DOWNLOAD: &str = "DAEMON_ALLOW_BINARIES_DOWNLOAD";
pub const DAEMON_ENFORCE_DOWNLOAD_CHECKSUM: &str = "DAEMON_ENFORCE_DOWNLOAD_CHECKSUM";
pub const DAEMON_RESTART_AFTER_UPGRADE: &str = "DAEMON_RESTART_AFTER_UPGRADE";
pub const DAEMON_RESTART_ON_FAILURE: &str = "DAEMON_RESTART_ON_FAILURE";
pub const DAEMON_FAILURE_RESTART_DELAY: &str = "DAEMON_FAILURE_RESTART_DELAY";
pub const DAEMON_MAX_STARTUP_FAILURES: &str = "DAEMON_MAX_STARTUP_FAILURES";
pub const DAEMON_STARTUP_PERIOD_DURATION: &str = "DAEMON_STARTUP_PERIOD_DURATION";
pub const DAEMON_SHUTDOWN_GRACE_PERIOD: &str = "DAEMON_SHUTDOWN_GRACE_PERIOD";
pub const DAEMON_DATA_BACKUP_DIRECTORY: &str = "DAEMON_DATA_BACKUP_DIRECTORY";
pub const DAEMON_UNSAFE_SKIP_BACKUP: &str = "DAEMON_UNSAFE_SKIP_BACKUP";
}
pub(crate) fn setup_env(config_env_file: &Option<PathBuf>) -> Result<(), NymvisorError> {
if let Some(env_file) = config_env_file {
dotenvy::from_path_override(env_file).map_err(Into::into)
} else {
Ok(())
}
}
pub(crate) struct Env {
pub(crate) nymvisor_id: Option<String>,
pub(crate) nymvisor_config_path: Option<PathBuf>,
pub(crate) nymvisor_data_directory: Option<PathBuf>,
pub(crate) nymvisor_disable_logs: Option<bool>,
pub(crate) nymvisor_data_upgrade_directory: Option<PathBuf>,
pub(crate) daemon_name: Option<String>,
pub(crate) daemon_home: Option<PathBuf>,
pub(crate) daemon_allow_binaries_download: Option<bool>,
pub(crate) daemon_enforce_download_checksum: Option<bool>,
pub(crate) daemon_restart_after_upgrade: Option<bool>,
pub(crate) daemon_restart_on_failure: Option<bool>,
pub(crate) daemon_failure_restart_delay: Option<Duration>,
pub(crate) daemon_max_startup_failures: Option<usize>,
pub(crate) daemon_startup_period_duration: Option<Duration>,
pub(crate) daemon_shutdown_grace_period: Option<Duration>,
pub(crate) daemon_data_backup_directory: Option<PathBuf>,
pub(crate) daemon_unsafe_skip_backup: Option<bool>,
}
// TODO: all of those seem like they could be moved to some common crate if we ever needed similar functionality elsewhere
fn read_string(var: &str) -> Result<Option<String>, NymvisorError> {
match std::env::var(var) {
Ok(val) => Ok(Some(val)),
Err(VarError::NotPresent) => Ok(None),
Err(VarError::NotUnicode(value)) => Err(NymvisorError::MalformedEnvVariable {
variable: var.to_string(),
value,
}),
}
}
fn read_bool(var: &str) -> Result<Option<bool>, NymvisorError> {
read_string(var)?
.map(|raw| {
let normalised = raw.to_ascii_lowercase();
if TRUTHY_BOOLS.contains(&&*normalised) {
Ok(true)
} else if FALSY_BOOLS.contains(&&*normalised) {
Ok(false)
} else {
Err(NymvisorError::MalformedBoolEnvVariable {
variable: var.to_string(),
value: raw.to_string(),
})
}
})
.transpose()
}
fn read_duration(var: &str) -> Result<Option<Duration>, NymvisorError> {
read_string(var)?
.map(|raw| {
humantime::parse_duration(&raw).map_err(|source| {
NymvisorError::MalformedDurationEnvVariable {
variable: var.to_string(),
value: raw.to_string(),
source,
}
})
})
.transpose()
}
fn read_pathbuf(var: &str) -> Result<Option<PathBuf>, NymvisorError> {
Ok(read_string(var)?.map(PathBuf::from))
}
fn read_usize(var: &str) -> Result<Option<usize>, NymvisorError> {
read_string(var)?
.map(|raw| {
raw.parse()
.map_err(|source| NymvisorError::MalformedNumberEnvVariable {
variable: var.to_string(),
value: raw.to_string(),
source,
})
})
.transpose()
}
impl Env {
// in general, if variable is missing from the environment that's fine.
// however, if something is out there, it MUST BE valid
pub(crate) fn try_read() -> Result<Self, NymvisorError> {
Ok(Env {
nymvisor_id: read_string(vars::NYMVISOR_ID)?,
nymvisor_config_path: read_pathbuf(vars::NYMVISOR_CONFIG_PATH)?,
nymvisor_data_directory: read_pathbuf(vars::NYMVISOR_DATA_DIRECTORY)?,
nymvisor_disable_logs: read_bool(vars::NYMVISOR_DISABLE_LOGS)?,
nymvisor_data_upgrade_directory: read_pathbuf(vars::NYMVISOR_DATA_UPGRADE_DIRECTORY)?,
daemon_name: read_string(vars::DAEMON_NAME)?,
daemon_home: read_pathbuf(vars::DAEMON_HOME)?,
daemon_allow_binaries_download: read_bool(vars::DAEMON_ALLOW_BINARIES_DOWNLOAD)?,
daemon_enforce_download_checksum: read_bool(vars::DAEMON_ENFORCE_DOWNLOAD_CHECKSUM)?,
daemon_restart_after_upgrade: read_bool(vars::DAEMON_RESTART_AFTER_UPGRADE)?,
daemon_restart_on_failure: read_bool(vars::DAEMON_RESTART_ON_FAILURE)?,
daemon_failure_restart_delay: read_duration(vars::DAEMON_FAILURE_RESTART_DELAY)?,
daemon_max_startup_failures: read_usize(vars::DAEMON_MAX_STARTUP_FAILURES)?,
daemon_startup_period_duration: read_duration(vars::DAEMON_STARTUP_PERIOD_DURATION)?,
daemon_shutdown_grace_period: read_duration(vars::DAEMON_SHUTDOWN_GRACE_PERIOD)?,
daemon_data_backup_directory: read_pathbuf(vars::DAEMON_DATA_BACKUP_DIRECTORY)?,
daemon_unsafe_skip_backup: read_bool(vars::DAEMON_UNSAFE_SKIP_BACKUP)?,
})
}
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::ffi::OsString;
use std::io;
use std::num::ParseIntError;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub(crate) enum NymvisorError {
#[error(
"failed to load config file for id {id} using path '{}'. detailed message: {source}", path.display()
)]
ConfigLoadFailure {
id: String,
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display()
)]
ConfigSaveFailure {
id: String,
path: PathBuf,
#[source]
source: io::Error,
},
#[error("the provided env file was malformed: {source}")]
MalformedEnvFile {
#[from]
source: dotenvy::Error,
},
#[error("the value provided for environmental variable '{variable}' was not valid unicode: {value:?}")]
MalformedEnvVariable { variable: String, value: OsString },
#[error("the value provided for environmental boolean variable '{variable}': '{value}' is not a valid boolean")]
MalformedBoolEnvVariable { variable: String, value: String },
#[error("the value provided for environmental duration variable '{variable}': '{value}' is not a valid duration: {source}")]
MalformedDurationEnvVariable {
variable: String,
value: String,
#[source]
source: humantime::DurationError,
},
#[error("the value provided for environmental numerical variable '{variable}': '{value}' is not a valid number: {source}")]
MalformedNumberEnvVariable {
variable: String,
value: String,
#[source]
source: ParseIntError,
},
}
+3 -1
View File
@@ -6,10 +6,12 @@ use clap::Parser;
pub(crate) mod cli;
pub(crate) mod config;
pub(crate) mod env;
pub(crate) mod error;
fn main() -> anyhow::Result<()> {
let args = Cli::parse();
println!("{args:#?}");
args.execute()
Ok(args.execute()?)
}