Skip to content

Commit

Permalink
refactor: Support automatic config version migration (#331)
Browse files Browse the repository at this point in the history
near-cli will try to bump user's config version to the latest, including
modifying/adding/removing fields
  • Loading branch information
frolvanya authored May 3, 2024
1 parent 0a6c816 commit 104fb4f
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 46 deletions.
34 changes: 31 additions & 3 deletions src/commands/config/add_connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ pub struct AddNetworkConnection {
#[interactive_clap(skip_default_input_arg)]
fastnear_url: Option<String>,
#[interactive_clap(long)]
staking_pools_factory_account_id: crate::types::account_id::AccountId,
#[interactive_clap(skip_default_input_arg)]
staking_pools_factory_account_id: Option<crate::types::account_id::AccountId>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -77,11 +78,13 @@ impl AddNetworkConnectionContext {
staking_pools_factory_account_id: scope
.staking_pools_factory_account_id
.clone()
.into(),
.map(|staking_pools_factory_account_id| {
staking_pools_factory_account_id.into()
}),
},
);
eprintln!();
crate::common::write_config_toml(config)?;
config.write_config_toml()?;
eprintln!(
"Network connection \"{}\" was successfully added to config.toml",
&scope.connection_name
Expand Down Expand Up @@ -244,4 +247,29 @@ impl AddNetworkConnection {
Ok(None)
}
}

fn input_staking_pools_factory_account_id(
_context: &crate::GlobalContext,
) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
eprintln!();
#[derive(strum_macros::Display)]
enum ConfirmOptions {
#[strum(to_string = "Yes, I want to enter the staking pools factory account ID")]
Yes,
#[strum(to_string = "No, I don't want to enter the staking pools factory account ID")]
No,
}
let select_choose_input = Select::new(
"Do you want to enter the staking pools factory account ID?",
vec![ConfirmOptions::Yes, ConfirmOptions::No],
)
.prompt()?;
if let ConfirmOptions::Yes = select_choose_input {
let account_id: crate::types::account_id::AccountId =
CustomType::new("What is the staking pools factory account ID?").prompt()?;
Ok(Some(account_id))
} else {
Ok(None)
}
}
}
2 changes: 1 addition & 1 deletion src/commands/config/delete_connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl DeleteNetworkConnectionContext {
let mut config = previous_context.config;
config.network_connection.remove(&scope.connection_name);
eprintln!();
crate::common::write_config_toml(config)?;
config.write_config_toml()?;
eprintln!(
"Network connection \"{}\" was successfully removed from config.toml",
&scope.connection_name
Expand Down
41 changes: 7 additions & 34 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,39 +1253,6 @@ pub fn save_access_key_to_legacy_keychain(
}
}

pub fn get_config_toml() -> color_eyre::eyre::Result<crate::config::Config> {
if let Some(mut path_config_toml) = dirs::config_dir() {
path_config_toml.extend(&["near-cli", "config.toml"]);

if !path_config_toml.is_file() {
write_config_toml(crate::config::Config::default())?;
};
let config_toml = std::fs::read_to_string(&path_config_toml)?;
toml::from_str(&config_toml).or_else(|err| {
eprintln!("Warning: `near` CLI configuration file stored at {path_config_toml:?} could not be parsed due to: {err}");
eprintln!("Note: The default configuration printed below will be used instead:\n");
let default_config = crate::config::Config::default();
eprintln!("{}", toml::to_string(&default_config)?);
Ok(default_config)
})
} else {
Ok(crate::config::Config::default())
}
}
pub fn write_config_toml(config: crate::config::Config) -> CliResult {
let config_toml = toml::to_string(&config)?;
let mut path_config_toml = dirs::config_dir().wrap_err("Impossible to get your config dir!")?;
path_config_toml.push("near-cli");
std::fs::create_dir_all(&path_config_toml)?;
path_config_toml.push("config.toml");
std::fs::File::create(&path_config_toml)
.wrap_err_with(|| format!("Failed to create file: {path_config_toml:?}"))?
.write(config_toml.as_bytes())
.wrap_err_with(|| format!("Failed to write to file: {path_config_toml:?}"))?;
eprintln!("Note: `near` CLI configuration is stored in {path_config_toml:?}");
Ok(())
}

pub fn try_external_subcommand_execution(error: clap::Error) -> CliResult {
let (subcommand, args) = {
let mut args = std::env::args().skip(1);
Expand Down Expand Up @@ -1525,8 +1492,14 @@ pub fn fetch_validators_api(

pub fn fetch_validators_rpc(
json_rpc_client: &near_jsonrpc_client::JsonRpcClient,
staking_pools_factory_account_id: near_primitives::types::AccountId,
staking_pools_factory_account_id: Option<near_primitives::types::AccountId>,
) -> color_eyre::Result<std::collections::BTreeSet<near_primitives::types::AccountId>> {
let Some(staking_pools_factory_account_id) = staking_pools_factory_account_id else {
return Err(color_eyre::Report::msg(
"Staking pools factory account ID is not set for selected network",
));
};

let query_view_method_response = json_rpc_client
.blocking_call(near_jsonrpc_client::methods::query::RpcQueryRequest {
block_reference: near_primitives::types::Finality::Final.into(),
Expand Down
94 changes: 94 additions & 0 deletions src/config/migrations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use crate::config::Config as ConfigV2;
use crate::config::NetworkConfig as NetworkConfigV2;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ConfigV1 {
pub credentials_home_dir: std::path::PathBuf,
pub network_connection: linked_hash_map::LinkedHashMap<String, NetworkConfigV1>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct NetworkConfigV1 {
pub network_name: String,
pub rpc_url: url::Url,
pub rpc_api_key: Option<crate::types::api_key::ApiKey>,
pub wallet_url: url::Url,
pub explorer_transaction_url: url::Url,
// https://github.com/near/near-cli-rs/issues/116
pub linkdrop_account_id: Option<near_primitives::types::AccountId>,
// https://docs.near.org/social/contract
pub near_social_db_contract_account_id: Option<near_primitives::types::AccountId>,
pub faucet_url: Option<url::Url>,
pub meta_transaction_relayer_url: Option<url::Url>,
}

impl From<ConfigV1> for ConfigV2 {
fn from(config: ConfigV1) -> Self {
ConfigV2 {
credentials_home_dir: config.credentials_home_dir,
network_connection: config
.network_connection
.into_iter()
.map(|(network_name, network_config)| (network_name, network_config.into()))
.collect(),
}
}
}

impl From<NetworkConfigV1> for NetworkConfigV2 {
fn from(network_config: NetworkConfigV1) -> Self {
match network_config.network_name.as_str() {
"mainnet" => NetworkConfigV2 {
network_name: network_config.network_name,
rpc_url: network_config.rpc_url,
wallet_url: network_config.wallet_url,
explorer_transaction_url: network_config.explorer_transaction_url,
rpc_api_key: network_config.rpc_api_key,
linkdrop_account_id: network_config.linkdrop_account_id,
near_social_db_contract_account_id: network_config
.near_social_db_contract_account_id,
faucet_url: network_config.faucet_url,
meta_transaction_relayer_url: network_config.meta_transaction_relayer_url,
fastnear_url: Some(String::from("https://api.fastnear.com")),
staking_pools_factory_account_id: Some("poolv1.near".parse().unwrap()),
},
"testnet" => NetworkConfigV2 {
network_name: network_config.network_name,
rpc_url: network_config.rpc_url,
wallet_url: network_config.wallet_url,
explorer_transaction_url: network_config.explorer_transaction_url,
rpc_api_key: network_config.rpc_api_key,
linkdrop_account_id: network_config.linkdrop_account_id,
near_social_db_contract_account_id: network_config
.near_social_db_contract_account_id,
faucet_url: network_config.faucet_url,
meta_transaction_relayer_url: network_config.meta_transaction_relayer_url,
fastnear_url: None,
staking_pools_factory_account_id: Some("pool.f863973.m0".parse().unwrap()),
},
_ => NetworkConfigV2 {
network_name: network_config.network_name,
rpc_url: network_config.rpc_url,
wallet_url: network_config.wallet_url,
explorer_transaction_url: network_config.explorer_transaction_url,
rpc_api_key: network_config.rpc_api_key,
linkdrop_account_id: network_config.linkdrop_account_id,
near_social_db_contract_account_id: network_config
.near_social_db_contract_account_id,
faucet_url: network_config.faucet_url,
meta_transaction_relayer_url: network_config.meta_transaction_relayer_url,
fastnear_url: None,
staking_pools_factory_account_id: None,
},
}
}
}

#[derive(serde::Serialize, serde::Deserialize)]
#[serde(tag = "version")]
pub enum ConfigVersion {
#[serde(rename = "1")]
V1(ConfigV1),
#[serde(rename = "2")]
V2(ConfigV2),
}
80 changes: 75 additions & 5 deletions src/config.rs → src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use color_eyre::eyre::WrapErr;
use std::str::FromStr;
mod migrations;

pub type CliResult = color_eyre::eyre::Result<()>;

use color_eyre::eyre::{ContextCompat, WrapErr};
use std::{io::Write, str::FromStr};
use tracing_indicatif::span_ext::IndicatifSpanExt;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -30,7 +34,7 @@ impl Default for Config {
faucet_url: None,
meta_transaction_relayer_url: None,
fastnear_url: Some(String::from("https://api.fastnear.com")),
staking_pools_factory_account_id: "poolv1.near".parse().unwrap(),
staking_pools_factory_account_id: Some("poolv1.near".parse().unwrap()),
},
);
network_connection.insert(
Expand All @@ -48,7 +52,7 @@ impl Default for Config {
faucet_url: Some("https://helper.nearprotocol.com/account".parse().unwrap()),
meta_transaction_relayer_url: None,
fastnear_url: None,
staking_pools_factory_account_id: "pool.f863973.m0".parse().unwrap(),
staking_pools_factory_account_id: Some("pool.f863973.m0".parse().unwrap()),
},
);

Expand All @@ -66,6 +70,57 @@ impl Config {
.map(|(_, network_config)| network_config.network_name.clone())
.collect()
}

pub fn into_latest_version(self) -> migrations::ConfigVersion {
migrations::ConfigVersion::V2(self)
}

pub fn get_config_toml() -> color_eyre::eyre::Result<Self> {
if let Some(mut path_config_toml) = dirs::config_dir() {
path_config_toml.extend(&["near-cli", "config.toml"]);

if !path_config_toml.is_file() {
Self::write_config_toml(crate::config::Config::default())?;
};

let config_toml = std::fs::read_to_string(&path_config_toml)?;

let config_version = toml::from_str::<migrations::ConfigVersion>(&config_toml).or_else::<color_eyre::eyre::Report, _>(|err| {
if let Ok(config_v1) = toml::from_str::<migrations::ConfigV1>(&config_toml) {
Ok(migrations::ConfigVersion::V1(config_v1))
} else {
eprintln!("Warning: `near` CLI configuration file stored at {path_config_toml:?} could not be parsed due to: {err}");
eprintln!("Note: The default configuration printed below will be used instead:\n");
let default_config = crate::config::Config::default();
eprintln!("{}", toml::to_string(&default_config)?);
Ok(default_config.into_latest_version())
}
})?;

Ok(config_version.into())
} else {
Ok(crate::config::Config::default())
}
}

pub fn write_config_toml(self) -> CliResult {
let config_toml = toml::to_string(&self.into_latest_version())?;
let mut path_config_toml =
dirs::config_dir().wrap_err("Impossible to get your config dir!")?;

path_config_toml.push("near-cli");
std::fs::create_dir_all(&path_config_toml)?;
path_config_toml.push("config.toml");

std::fs::File::create(&path_config_toml)
.wrap_err_with(|| format!("Failed to create file: {path_config_toml:?}"))?
.write(config_toml.as_bytes())
.wrap_err_with(|| format!("Failed to write to file: {path_config_toml:?}"))?;

eprintln!("Note: `near` CLI configuration is stored in {path_config_toml:?}");

Ok(())
}
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand All @@ -82,7 +137,7 @@ pub struct NetworkConfig {
pub faucet_url: Option<url::Url>,
pub meta_transaction_relayer_url: Option<url::Url>,
pub fastnear_url: Option<String>,
pub staking_pools_factory_account_id: near_primitives::types::AccountId,
pub staking_pools_factory_account_id: Option<near_primitives::types::AccountId>,
}

impl NetworkConfig {
Expand Down Expand Up @@ -115,3 +170,18 @@ impl NetworkConfig {
}
}
}

impl From<migrations::ConfigVersion> for Config {
fn from(mut config_version: migrations::ConfigVersion) -> Self {
loop {
config_version = match config_version {
migrations::ConfigVersion::V1(config_v1) => {
migrations::ConfigVersion::V2(config_v1.into())
}
migrations::ConfigVersion::V2(config_v2) => {
break config_v2;
}
};
}
}
}
2 changes: 1 addition & 1 deletion src/js_command_match/generate_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct GenerateKeyArgs {

impl GenerateKeyArgs {
pub fn to_cli_args(&self, network_config: String) -> color_eyre::eyre::Result<Vec<String>> {
let config = crate::common::get_config_toml()?;
let config = crate::config::Config::get_config_toml()?;
let mut generation_method = "use-auto-generation".to_string();
if self.use_ledger_key.is_some() {
generation_method = "use-ledger".to_string();
Expand Down
2 changes: 1 addition & 1 deletion src/js_command_match/set_api_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct SetApiKeyArgs {

impl SetApiKeyArgs {
pub fn to_cli_args(&self, network_name: String) -> color_eyre::eyre::Result<Vec<String>> {
let config = crate::common::get_config_toml()?;
let config = crate::config::Config::get_config_toml()?;
let network_config = match config.network_connection.get(&network_name) {
Some(network_config) => network_config,
None => {
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl From<CmdContext> for crate::GlobalContext {
}

fn main() -> crate::common::CliResult {
let config = crate::common::get_config_toml()?;
let config = crate::config::Config::get_config_toml()?;

if !crate::common::is_used_account_list_exist(&config.credentials_home_dir) {
crate::common::create_used_account_list_from_keychain(&config.credentials_home_dir)?;
Expand Down

0 comments on commit 104fb4f

Please sign in to comment.