This commit is contained in:
Jędrzej Stuczyński
2023-11-06 12:03:36 +00:00
parent e853e8ffc1
commit 95e9a96ae1
14 changed files with 314 additions and 44 deletions
Generated
+2
View File
@@ -7612,8 +7612,10 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"time",
"tokio",
"tracing",
"url",
]
[[package]]
+1
View File
@@ -159,6 +159,7 @@ schemars = "0.8.1"
serde = "1.0.152"
serde_json = "1.0.91"
tap = "1.0.1"
time = "0.3.30"
thiserror = "1.0.48"
tokio = "1.24.1"
tokio-tungstenite = "0.20.1"
+1 -1
View File
@@ -21,7 +21,7 @@ rand = {version = "0.6", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
time = { version = "0.3.6", features = ["parsing", "formatting"] }
time = { workspace = true, features = ["parsing", "formatting"] }
toml = "0.5.6"
url = { workspace = true }
tap = "1"
@@ -25,12 +25,12 @@ humantime-serde = "1.1.1"
# TO CHECK WHETHER STILL NEEDED:
log = { workspace = true }
time = { version = "0.3.6", features = ["parsing", "formatting"] }
time = { workspace = true, features = ["parsing", "formatting"] }
ts-rs = { workspace = true, optional = true }
[dev-dependencies]
rand_chacha = "0.3"
time = { version = "0.3.5", features = ["serde", "macros"] }
time = { workspace = true, features = ["serde", "macros"] }
[features]
default = []
+27 -10
View File
@@ -551,6 +551,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
dependencies = [
"powerfmt",
]
[[package]]
name = "derivative"
version = "2.2.0"
@@ -1558,6 +1567,12 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1870,9 +1885,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.160"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
dependencies = [
"serde_derive",
]
@@ -1888,9 +1903,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.160"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
@@ -2085,11 +2100,13 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.20"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
"deranged",
"itoa",
"powerfmt",
"serde",
"time-core",
"time-macros",
@@ -2097,15 +2114,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.0"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.8"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
dependencies = [
"time-core",
]
+1 -1
View File
@@ -37,7 +37,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
tap = "1.0"
thiserror = { workspace = true }
time = { version = "0.3.14", features = ["serde-human-readable", "parsing"] }
time = { workspace = true, features = ["serde-human-readable", "parsing"] }
tokio = { version = "1.24.1", features = [
"rt-multi-thread",
"macros",
+20 -12
View File
@@ -1578,10 +1578,11 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.7"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
dependencies = [
"powerfmt",
"serde",
]
@@ -5291,6 +5292,12 @@ dependencies = [
"universal-hash",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -6214,9 +6221,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.183"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
dependencies = [
"serde_derive",
]
@@ -6241,9 +6248,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.183"
version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
@@ -7394,15 +7401,16 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.25"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
"deranged",
"itoa 1.0.9",
"js-sys",
"libc",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
@@ -7410,15 +7418,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.11"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
dependencies = [
"time-core",
]
+1
View File
@@ -21,6 +21,7 @@ lazy_static = { workspace = true }
nix = { version = "0.27.1", features = ["signal"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
time = { workspace = true, features = [ "serde-human-readable" ] }
tokio = { workspace = true, features = ["rt", "macros", "signal", "process", "sync"] }
thiserror = { workspace = true }
tracing = { workspace = true }
+37 -13
View File
@@ -1,16 +1,18 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{default_config_filepath, Config, BIN_DIR};
use crate::config::{default_config_filepath, Config, BIN_DIR, GENESIS_DIR};
use crate::daemon::Daemon;
use crate::env::Env;
use crate::error::NymvisorError;
use crate::upgrades::UpgradeInfo;
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_bin_common::logging::{setup_logging, setup_tracing_logger};
use nym_bin_common::output_format::OutputFormat;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Duration;
use time::OffsetDateTime;
use tracing::{debug, error, info, trace, warn};
use url::Url;
@@ -239,10 +241,10 @@ fn init_paths(config: &Config) -> Result<(), NymvisorError> {
Ok(())
}
fn copy_genesis_binary(
fn setup_genesis_binary(
config: &Config,
source_dir: &Path,
daemon_info: &BinaryBuildInformationOwned,
daemon_info: BinaryBuildInformationOwned,
) -> Result<(), NymvisorError> {
info!("setting up the genesis binary");
let target = config.genesis_daemon_binary();
@@ -262,6 +264,8 @@ fn copy_genesis_binary(
};
}
generate_and_save_genesis_upgrade_info(&config, daemon_info)?;
// TODO: setup initial upgrade-info.json file
fs::copy(source_dir, &target).map_err(|source| NymvisorError::DaemonBinaryCopyFailure {
source_path: source_dir.to_path_buf(),
@@ -304,6 +308,25 @@ fn create_current_symlink(config: &Config) -> Result<(), NymvisorError> {
})
}
fn generate_and_save_genesis_upgrade_info(
config: &Config,
genesis_info: BinaryBuildInformationOwned,
) -> Result<(), NymvisorError> {
let info = UpgradeInfo {
manual: true,
name: GENESIS_DIR.to_string(),
notes: "".to_string(),
publish_date: None,
version: genesis_info.build_version.clone(),
platforms: Default::default(),
upgrade_time: OffsetDateTime::UNIX_EPOCH,
binary_details: Some(genesis_info),
};
let save_path = config.upgrade_info_filepath(&info.name);
info.save(save_path)
}
fn save_config(config: Config, env: &Env) -> Result<(), NymvisorError> {
let id = &config.nymvisor.id;
let config_save_location = env
@@ -327,15 +350,16 @@ fn save_config(config: Config, env: &Env) -> Result<(), NymvisorError> {
}
/// Initialise the nymvisor by performing the following:
/// - [✅] executing the `build-info` command on the daemon executable to check its validity and obtain its name
/// - [✅] creating `<DAEMON_HOME>/nymvisor` folder if it doesn't yet exist
/// - [✅] creating `<DAEMON_BACKUP_DATA_DIRECTORY>` folder if it doesn't yet exist
/// - [✅] creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>` folder if it doesn't yet exist
/// - [✅] creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/bin` folder if it doesn't yet exist
/// - [✅] creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/upgrades` folder if it doesn't yet exist
/// - [⚠️] copying the provided executable to `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/bin/<DAEMON_NAME>`
/// - [✅] creating a `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/current` symlink pointing to `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis`
/// - [✅] saving nymvisor's config file to `<NYMVISOR_CONFIG_PATH>` and creating the full directory structure.
/// - executing the `build-info` command on the daemon executable to check its validity and obtain its name
/// - creating `<DAEMON_HOME>/nymvisor` folder if it doesn't yet exist
/// - creating `<DAEMON_BACKUP_DATA_DIRECTORY>` folder if it doesn't yet exist
/// - creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>` folder if it doesn't yet exist
/// - creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/bin` folder if it doesn't yet exist
/// - creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/upgrades` folder if it doesn't yet exist
/// - copying the provided executable to `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/bin/<DAEMON_NAME>`
/// - generating initial `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/upgrade-info.json` file
/// - creating a `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/current` symlink pointing to `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis`
/// - saving nymvisor's config file to `<NYMVISOR_CONFIG_PATH>` and creating the full directory structure.
///
/// note: it requires either passing `--daemon-home` flag or setting the `$DAEMON_HOME` environmental variable
pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
@@ -356,7 +380,7 @@ pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
let config = try_build_config(&args, &env, &daemon_info)?;
init_paths(&config)?;
copy_genesis_binary(&config, &args.daemon_binary, &daemon_info)?;
setup_genesis_binary(&config, &args.daemon_binary, daemon_info)?;
create_current_symlink(&config)?;
save_config(config, &env)?;
+6 -1
View File
@@ -7,6 +7,7 @@ use crate::env::Env;
use crate::error::NymvisorError;
use async_file_watcher::AsyncFileWatcher;
use futures::channel::mpsc;
use futures::future::{AbortHandle, Abortable};
use futures::StreamExt;
use nym_bin_common::logging::setup_tracing_logger;
use std::sync::Arc;
@@ -59,13 +60,17 @@ pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
events_sender,
)?;
let handle2 = tokio::spawn(async move { watcher.watch().await });
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let handle2 =
tokio::spawn(async move { Abortable::new(watcher.watch(), abort_registration).await });
let event = events_receiver.next().await;
println!("watcher event: {event:?}");
interrupt_notify.notify_one();
handle1.await;
abort_handle.abort();
handle2.await;
// println!("{:?}", status);
+30
View File
@@ -100,6 +100,7 @@ impl Display for Config {
{:<35}{}
{:<35}{}
{:<35}{}
{:<35}{}
{:<35}{:?}
{:<35}{}
{:<35}{}
@@ -109,6 +110,7 @@ impl Display for Config {
{:<35}{}
{:<35}{}
{:<35}{}
{:<35}{}
{:<35}{:?}
{:<35}{}
"#,
@@ -118,6 +120,8 @@ impl Display for Config {
self.daemon.name,
"daemon home:",
self.daemon.home.display(),
"upstream base upgrade url:",
self.nymvisor.debug.upstream_base_upgrade_url,
"disable nymvisor logs:",
self.nymvisor.debug.disable_logs,
"CUSTOM upgrade data directory",
@@ -127,6 +131,13 @@ impl Display for Config {
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_default(),
"upstream absolute upgrade url:",
self.daemon
.debug
.absolute_upstream_upgrade_url
.as_ref()
.map(|p| p.to_string())
.unwrap_or_default(),
"allow binaries download:",
self.daemon.debug.allow_binaries_download,
"enforce download checksum:",
@@ -229,6 +240,7 @@ impl Config {
}
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/
pub fn upgrade_data_dir(&self) -> PathBuf {
self.nymvisor
.debug
@@ -237,30 +249,48 @@ impl Config {
.unwrap_or(default_global_data_directory().join(&self.daemon.name))
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/genesis
pub fn genesis_daemon_dir(&self) -> PathBuf {
self.upgrade_data_dir().join(GENESIS_DIR)
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/genesis/bin/nym-api
pub fn genesis_daemon_binary(&self) -> PathBuf {
self.genesis_daemon_dir()
.join(BIN_DIR)
.join(&self.daemon.name)
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/current
pub fn current_daemon_dir(&self) -> PathBuf {
self.upgrade_data_dir().join(CURRENT_DIR)
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/current/bin/nym-api
pub fn current_daemon_binary(&self) -> PathBuf {
self.current_daemon_dir()
.join(BIN_DIR)
.join(&self.daemon.name)
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades/<upgrade-name>/upgrade-info.json
// or $HOME/.nym/nymvisors/data/nym-api/genesis/upgrade-info.json
pub fn upgrade_info_filepath<S: AsRef<str>>(&self, upgrade_name: S) -> PathBuf {
// special case for genesis
let name = upgrade_name.as_ref();
if name == GENESIS_DIR {
self.genesis_daemon_dir().join(UPGRADE_INFO_FILENAME)
} else {
self.upgrades_dir().join(name).join(UPGRADE_INFO_FILENAME)
}
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades/
pub fn upgrades_dir(&self) -> PathBuf {
self.upgrade_data_dir().join(UPGRADES_DIR)
}
// e.g. $HOME/.nym/nymvisors/data/nym-api/upgrade-plan.json
pub fn upgrade_plan_filepath(&self) -> PathBuf {
self.upgrade_data_dir().join(UPGRADE_PLAN_FILENAME)
}
+20
View File
@@ -33,6 +33,26 @@ pub(crate) enum NymvisorError {
source: io::Error,
},
#[error(
"failed to load upgrade info for upgrade '{name}' using path '{}'. detailed message: {source}", path.display()
)]
UpgradeInfoLoadFailure {
name: String,
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"failed to save upgrade info for upgrade '{name}' using path '{}'. detailed message: {source}", path.display()
)]
UpgradeInfoSaveFailure {
name: String,
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to initialise the path '{}': {source}", path.display())]
PathInitFailure {
path: PathBuf,
+19 -1
View File
@@ -8,7 +8,25 @@ pub(crate) struct DaemonLauncher {
}
impl DaemonLauncher {
pub(crate) fn run(args: Vec<String>) {
pub(crate) fn new(config: Config) -> Self {
todo!()
}
// responsible for running until exit or until update is detected
pub(crate) fn run(&self, args: Vec<String>) {
/*
tokio select on:
- daemon terminating
- upgrade-plan.json changes
- https://nymtech.net/.wellknown/<DAEMON_NAME>/update-info.json changes
// todo: maybe move to a higher layer
- signals received (to propagate them to daemon before terminating to prevent creating zombie processes)
*/
todo!()
}
}
+147 -3
View File
@@ -1,23 +1,167 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymvisorError;
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::path::Path;
use time::OffsetDateTime;
mod http_upstream;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub struct UpgradePlan {
//
pub current: UpgradeInfo,
pub next: Vec<UpgradeInfo>,
}
impl UpgradePlan {
pub(crate) fn update_on_disk(&self) -> Result<(), NymvisorError> {
// 1. copy upgrade-plan.json to upgrade-plan.json.tmp
// 2. update upgrade-plan.json.tmp
// 3. move upgrade-plan.json.tmp to upgrade-plan.json
todo!()
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum DigestAlgorithm {
Sha256,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub struct DownloadUrl {
// TODO: get it from base64
/// The checksum of the file behind the download url.
pub checksum: Vec<u8>,
/// The algorithm used for computing the checksum
pub checksum_algorithm: DigestAlgorithm,
/// Download url for this particular platform
pub url: String,
}
mod option_offsetdatetime {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use time::OffsetDateTime;
pub fn serialize<S>(value: &Option<OffsetDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper<'a>(#[serde(with = "time::serde::rfc3339")] &'a OffsetDateTime);
value.as_ref().map(Helper).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper(#[serde(with = "time::serde::rfc3339")] OffsetDateTime);
let helper = Option::deserialize(deserializer)?;
Ok(helper.map(|Helper(external)| external))
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub struct UpgradeInfo {
//
/// Specifies whether this upgrade requires manual intervention and cannot be done automatically by the nymvisor.
pub manual: bool,
/// Name of this upgrade, for example `2023.4-galaxy`
pub name: String,
/// Additional information about this release
pub notes: String,
/// Optional rfc3339 datetime of the publish date of the release,
#[serde(with = "option_offsetdatetime")]
pub publish_date: Option<OffsetDateTime>,
/// Version of this upgrade, for example `1.1.69`
pub version: String,
/// Platform specific download urls, for example `linux-x86_64`
pub platforms: HashMap<String, DownloadUrl>,
/// Time when the upgrade should happen.
#[serde(with = "time::serde::rfc3339")]
pub upgrade_time: OffsetDateTime,
/// Optional build information of the upgraded binary for additional verification
pub binary_details: Option<BinaryBuildInformationOwned>,
}
impl UpgradeInfo {
pub(crate) fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), NymvisorError> {
let path = path.as_ref();
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.map_err(|source| NymvisorError::UpgradeInfoSaveFailure {
name: self.name.clone(),
path: path.to_path_buf(),
source,
})?;
// we're not using any non-standard serializer and thus the serialization should not ever fail
#[allow(clippy::expect_used)]
serde_json::to_writer_pretty(file, self)
.expect("unexpected UpgradeInfo serialization failure");
Ok(())
}
pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> Result<Self, NymvisorError> {
let file = std::fs::File::open(path)?;
serde_json::from_reader(file).map_err(Into::into)
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub struct UpgradeHistory(Vec<UpgradeHistoryEntry>);
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub struct UpgradeHistoryEntry {
performed_at: u64,
#[serde(with = "time::serde::rfc3339")]
performed_at: OffsetDateTime,
info: UpgradeInfo,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn foo() {
let a = UpgradeInfo {
manual: false,
name: "".to_string(),
notes: "".to_string(),
publish_date: None,
version: "".to_string(),
platforms: Default::default(),
upgrade_time: OffsetDateTime::now_utc(),
binary_details: None,
};
let aa = serde_json::to_string_pretty(&a).unwrap();
println!("{aa}")
}
}