Add config get/set functionality to wallet (#5452)

automerge
This commit is contained in:
Tyera Eulberg 2019-08-07 13:17:11 -06:00 committed by Grimes
parent f154a53e5e
commit 2a17e90b7b
5 changed files with 193 additions and 7 deletions

5
Cargo.lock generated
View File

@ -3836,10 +3836,15 @@ dependencies = [
"bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
"solana 0.18.0-pre1",
"solana-budget-api 0.18.0-pre1",
"solana-budget-program 0.18.0-pre1",

View File

@ -13,10 +13,15 @@ bincode = "1.1.4"
bs58 = "0.2.0"
chrono = { version = "0.4.7", features = ["serde"] }
clap = "2.33.0"
console = "0.7.7"
dirs = "2.0.2"
lazy_static = "1.3.0"
log = "0.4.8"
num-traits = "0.2"
serde = "1.0.98"
serde_derive = "1.0.98"
serde_json = "1.0.40"
serde_yaml = "0.8.9"
solana-budget-api = { path = "../programs/budget_api", version = "0.18.0-pre1" }
solana-client = { path = "../client", version = "0.18.0-pre1" }
solana-drone = { path = "../drone", version = "0.18.0-pre1" }
@ -36,4 +41,3 @@ solana-budget-program = { path = "../programs/budget_program", version = "0.18.0
[features]
cuda = []

49
wallet/src/config.rs Normal file
View File

@ -0,0 +1,49 @@
// Wallet settings that can be configured for long-term use
use serde_derive::{Deserialize, Serialize};
use std::fs::{create_dir_all, File};
use std::io::{self, Write};
use std::path::Path;
lazy_static! {
pub static ref CONFIG_FILE: Option<String> = {
dirs::home_dir().map(|mut path| {
path.extend(&[".config", "solana", "wallet", "config.yml"]);
path.to_str().unwrap().to_string()
})
};
}
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct Config {
pub url: String,
pub keypair: String,
}
impl Config {
pub fn new(url: &str, keypair: &str) -> Self {
Self {
url: url.to_string(),
keypair: keypair.to_string(),
}
}
pub fn load(config_file: &str) -> Result<Self, io::Error> {
let file = File::open(config_file.to_string())?;
let config = serde_yaml::from_reader(file)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
Ok(config)
}
pub fn save(&self, config_file: &str) -> Result<(), io::Error> {
let serialized = serde_yaml::to_string(self)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
if let Some(outdir) = Path::new(&config_file).parent() {
create_dir_all(outdir)?;
}
let mut file = File::create(config_file)?;
file.write_all(&serialized.into_bytes())?;
Ok(())
}
}

View File

@ -1 +1,5 @@
#[macro_use]
extern crate lazy_static;
pub mod config;
pub mod wallet;

View File

@ -1,10 +1,69 @@
use clap::{crate_description, crate_name, crate_version, Arg, ArgMatches};
use clap::{crate_description, crate_name, crate_version, Arg, ArgGroup, ArgMatches, SubCommand};
use console::style;
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
use solana_wallet::config::{self, Config};
use solana_wallet::wallet::{app, parse_command, process_command, WalletConfig, WalletError};
use std::error;
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
let parse_args = match matches.subcommand() {
("get", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let config = Config::load(config_file).unwrap_or_default();
if let Some(field) = subcommand_matches.value_of("specific_setting") {
let value = match field {
"url" => config.url,
"keypair" => config.keypair,
_ => unreachable!(),
};
println_name_value(&format!("* {}:", field), &value);
} else {
println_name_value("Wallet Config:", config_file);
println_name_value("* url:", &config.url);
println_name_value("* keypair:", &config.keypair);
}
} else {
println!("{} Either provide the `--config` arg or ensure home directory exists to use the default config location", style("No config file found.").bold());
}
false
}
("set", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let mut config = Config::load(config_file).unwrap_or_default();
if let Some(url) = subcommand_matches.value_of("url") {
config.url = url.to_string();
}
if let Some(keypair) = subcommand_matches.value_of("keypair") {
config.keypair = keypair.to_string();
}
config.save(config_file)?;
println_name_value("Wallet Config Updated:", config_file);
println_name_value("* url:", &config.url);
println_name_value("* keypair:", &config.keypair);
} else {
println!("{} Either provide the `--config` arg or ensure home directory exists to use the default config location", style("No config file found.").bold());
}
false
}
_ => true,
};
Ok(parse_args)
}
pub fn parse_args(matches: &ArgMatches<'_>) -> Result<WalletConfig, Box<dyn error::Error>> {
let json_rpc_url = matches.value_of("json_rpc_url").unwrap().to_string();
let config = if let Some(config_file) = matches.value_of("config_file") {
Config::load(config_file).unwrap_or_default()
} else {
Config::default()
};
let json_rpc_url = if let Some(url) = matches.value_of("json_rpc_url") {
url.to_string()
} else if config.url != "" {
config.url
} else {
let default = WalletConfig::default();
default.json_rpc_url
};
let drone_host = if let Some(drone_host) = matches.value_of("drone_host") {
Some(solana_netutil::parse_host(drone_host).or_else(|err| {
@ -31,6 +90,8 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result<WalletConfig, Box<dyn erro
let mut path = dirs::home_dir().expect("home directory");
let id_path = if matches.is_present("keypair") {
matches.value_of("keypair").unwrap()
} else if config.keypair != "" {
&config.keypair
} else {
path.extend(&[".config", "solana", "id.json"]);
if !path.exists() {
@ -59,6 +120,16 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result<WalletConfig, Box<dyn erro
})
}
// Pretty print a "name value"
fn println_name_value(name: &str, value: &str) {
let styled_value = if value == "" {
style("(not set)").italic()
} else {
style(value)
};
println!("{} {}", style(name).bold(), styled_value);
}
// Return an error if a url cannot be parsed.
fn is_url(string: String) -> Result<(), String> {
match url::Url::parse(&string) {
@ -80,13 +151,25 @@ fn main() -> Result<(), Box<dyn error::Error>> {
let default_drone_port = format!("{}", default.drone_port);
let matches = app(crate_name!(), crate_description!(), crate_version!())
.arg({
let arg = Arg::with_name("config_file")
.short("c")
.long("config")
.value_name("PATH")
.takes_value(true)
.help("Configuration file to use");
if let Some(ref config_file) = *config::CONFIG_FILE {
arg.default_value(&config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.short("u")
.long("url")
.value_name("URL")
.takes_value(true)
.default_value(&default.json_rpc_url)
.validator(is_url)
.help("JSON RPC URL for the solana cluster"),
)
@ -113,10 +196,51 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.takes_value(true)
.help("/path/to/id.json"),
)
.subcommand(
SubCommand::with_name("get")
.about("Get wallet config settings")
.arg(
Arg::with_name("specific_setting")
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["url", "keypair"])
.help("Return a specific config setting"),
),
)
.subcommand(
SubCommand::with_name("set")
.about("Set a wallet config setting")
.arg(
Arg::with_name("url")
.short("u")
.long("url")
.value_name("URL")
.takes_value(true)
.validator(is_url)
.help("Set default JSON RPC URL to query"),
)
.arg(
Arg::with_name("keypair")
.short("k")
.long("keypair")
.value_name("PATH")
.takes_value(true)
.help("/path/to/id.json"),
)
.group(
ArgGroup::with_name("config_settings")
.args(&["url", "keypair"])
.multiple(true)
.required(true),
),
)
.get_matches();
let config = parse_args(&matches)?;
let result = process_command(&config)?;
println!("{}", result);
if parse_settings(&matches)? {
let config = parse_args(&matches)?;
let result = process_command(&config)?;
println!("{}", result);
}
Ok(())
}