From 1d876df8b3227f27c58c62833700b93bff1d63d5 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Fri, 15 Mar 2019 10:54:54 -0700 Subject: [PATCH] Add command plumbing --- Cargo.lock | 33 ++++++++++- install/Cargo.toml | 6 +- install/src/build_env.rs | 2 + install/src/command.rs | 65 +++++++++++++++++++++ install/src/config.rs | 46 +++++++++++++++ install/src/defaults.rs | 25 ++++++++ install/src/main.rs | 103 ++++++++++++++++++++++++++------- install/src/update_manifest.rs | 19 ++++++ 8 files changed, 277 insertions(+), 22 deletions(-) create mode 100644 install/src/build_env.rs create mode 100644 install/src/command.rs create mode 100644 install/src/config.rs create mode 100644 install/src/defaults.rs create mode 100644 install/src/update_manifest.rs diff --git a/Cargo.lock b/Cargo.lock index d1d3e32eb..625642546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1057,6 +1057,11 @@ dependencies = [ "make-cmd 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "linked-hash-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lock_api" version = "0.1.5" @@ -1922,6 +1927,17 @@ dependencies = [ "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde_yaml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha1" version = "0.6.0" @@ -2193,8 +2209,12 @@ dependencies = [ "bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 1.0.5 (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.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "solana-client 0.13.0", "solana-logger 0.13.0", "solana-sdk 0.13.0", @@ -3006,6 +3026,14 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum MacTypes-sys 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7dbbe033994ae2198a18517c7132d952a29fb1db44249a1234779da7c50f4698" "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" @@ -3128,6 +3156,7 @@ dependencies = [ "checksum libflate 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "bff3ac7d6f23730d3b533c35ed75eef638167634476a499feef16c428d74b57b" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum librocksdb-sys 5.14.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b9024327233e7fac7982440f73301c00046d438c5b1011e8f4e394226ce19007" +"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" @@ -3225,6 +3254,7 @@ dependencies = [ "checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d48f9f99cd749a2de71d29da5f948de7f2764cc5a9d7f3c97e3514d4ee6eabf2" +"checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" @@ -3294,3 +3324,4 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/install/Cargo.toml b/install/Cargo.toml index 20a48bff4..abbeb85b6 100644 --- a/install/Cargo.toml +++ b/install/Cargo.toml @@ -13,8 +13,12 @@ bincode = "1.1.2" bs58 = "0.2.0" clap = { version = "2.32.0"} chrono = { version = "0.4.0", features = ["serde"] } +dirs = "1.0.5" +lazy_static = "1.3.0" log = "0.4.2" -serde_json = "1.0.39" +serde = "1.0.89" +serde_derive = "1.0.89" +serde_yaml = "0.8.8" solana-client = { path = "../client", version = "0.13.0" } solana-logger = { path = "../logger", version = "0.13.0" } solana-sdk = { path = "../sdk", version = "0.13.0" } diff --git a/install/src/build_env.rs b/install/src/build_env.rs new file mode 100644 index 000000000..2ec8581b8 --- /dev/null +++ b/install/src/build_env.rs @@ -0,0 +1,2 @@ +pub const TARGET: &str = env!("TARGET"); +pub const BUILD_SECONDS_SINCE_UNIX_EPOCH: &str = env!("BUILD_SECONDS_SINCE_UNIX_EPOCH"); diff --git a/install/src/command.rs b/install/src/command.rs new file mode 100644 index 000000000..e89fec27f --- /dev/null +++ b/install/src/command.rs @@ -0,0 +1,65 @@ +use crate::config::Config; +use solana_sdk::pubkey::Pubkey; +use std::time::Duration; + +pub fn init( + config_file: &str, + data_dir: &str, + json_rpc_url: &str, + update_pubkey: &Pubkey, +) -> Result<(), String> { + println!("init {:?} {:?}", json_rpc_url, update_pubkey); + Config { + data_dir: data_dir.to_string(), + json_rpc_url: json_rpc_url.to_string(), + update_pubkey: *update_pubkey, + current_install: None, + } + .save(config_file)?; + + Err("Not implemented".to_string()) +} + +pub fn info(config_file: &str, local_info_only: bool) -> Result<(), String> { + let config = Config::load(config_file)?; + println!("config dir: {:?}", config_file); + println!("configuration: {:?}", config); + if local_info_only { + return Ok(()); + } + + // TODO: fetch info about current update manifest from the cluster + + Err("Not implemented".to_string()) +} + +pub fn deploy( + config_file: &str, + download_url: &str, + update_manifest_keypair: &str, +) -> Result<(), String> { + println!("deploy {:?} {:?}", download_url, update_manifest_keypair); + let _config = Config::load(config_file)?; + Err("Not implemented".to_string()) +} + +pub fn update(config_file: &str) -> Result<(), String> { + println!( + "update: BUILD_SECONDS_SINCE_UNIX_EPOCH={:?}", + Duration::from_secs( + u64::from_str_radix(crate::build_env::BUILD_SECONDS_SINCE_UNIX_EPOCH, 10).unwrap() + ) + ); + let _config = Config::load(config_file)?; + Err("Not implemented".to_string()) +} + +pub fn run( + config_file: &str, + program_name: &str, + program_arguments: Vec<&str>, +) -> Result<(), String> { + println!("run {:?} {:?}", program_name, program_arguments); + let _config = Config::load(config_file)?; + Err("Not implemented".to_string()) +} diff --git a/install/src/config.rs b/install/src/config.rs new file mode 100644 index 000000000..d63f2a94f --- /dev/null +++ b/install/src/config.rs @@ -0,0 +1,46 @@ +use crate::update_manifest::SignedUpdateManifest; +use serde_derive::{Deserialize, Serialize}; +use serde_yaml; +use solana_sdk::pubkey::Pubkey; +use std::fs::{create_dir_all, File}; +use std::io::{Error, ErrorKind, Write}; +use std::path::Path; + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq)] +pub struct Config { + pub data_dir: String, + pub json_rpc_url: String, + pub update_pubkey: Pubkey, + pub current_install: Option, +} + +impl Config { + fn _load(config_file: &str) -> Result { + let file = File::open(config_file.to_string())?; + let config = serde_yaml::from_reader(file) + .map_err(|err| Error::new(ErrorKind::Other, format!("{:?}", err)))?; + Ok(config) + } + + pub fn load(config_file: &str) -> Result { + Self::_load(config_file).map_err(|err| format!("Unable to load {}: {:?}", config_file, err)) + } + + fn _save(&self, config_file: &str) -> Result<(), Error> { + let serialized = serde_yaml::to_string(self) + .map_err(|err| Error::new(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(()) + } + + pub fn save(&self, config_file: &str) -> Result<(), String> { + self._save(config_file) + .map_err(|err| format!("Unable to save {}: {:?}", config_file, err)) + } +} diff --git a/install/src/defaults.rs b/install/src/defaults.rs new file mode 100644 index 000000000..8ae3352b9 --- /dev/null +++ b/install/src/defaults.rs @@ -0,0 +1,25 @@ +pub const JSON_RPC_URL: &str = "https://api.testnet.solana.com/"; + +lazy_static! { + pub static ref CONFIG_FILE: Option = { + dirs::config_dir().map(|mut path| { + path.push("solana"); + path.push("install.yml"); + path.to_str().unwrap().to_string() + }) + }; + pub static ref DATA_DIR: Option = { + dirs::data_dir().map(|mut path| { + path.push("solana"); + path.to_str().unwrap().to_string() + }) + }; +} + +pub fn update_pubkey(target: &str) -> Option<&str> { + match target { + "x86_64-apple-darwin" => Some("9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp"), // TODO: This pubkey is invalid + "x86_64-unknown-linux-gnu" => Some("8XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp"), // TODO: This pubkey is invalid + _ => None, + } +} diff --git a/install/src/main.rs b/install/src/main.rs index 6556581d9..6a877989a 100644 --- a/install/src/main.rs +++ b/install/src/main.rs @@ -1,40 +1,76 @@ +#[macro_use] +extern crate lazy_static; + use clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg, SubCommand}; -//use clap::{crate_description, crate_version, load_yaml, App, AppSettings}; -use std::error; -use std::time::Duration; +use solana_sdk::pubkey::Pubkey; -const TARGET: &str = env!("TARGET"); -const BUILD_SECONDS_SINCE_UNIX_EPOCH: &str = env!("BUILD_SECONDS_SINCE_UNIX_EPOCH"); +mod build_env; +mod command; +mod config; +mod defaults; +mod update_manifest; -fn main() -> Result<(), Box> { +fn main() -> Result<(), String> { solana_logger::setup(); let matches = App::new(crate_name!()) .about(crate_description!()) .version(crate_version!()) - .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::SubcommandRequiredElseHelp) + .arg({ + let arg = Arg::with_name("config_file") + .short("c") + .long("config") + .value_name("PATH") + .takes_value(true) + .help("Configuration file to use"); + match *defaults::CONFIG_FILE { + Some(ref config_file) => arg.default_value(&config_file), + None => arg.required(true), + } + }) .subcommand( SubCommand::with_name("init") .about("initializes a new installation") .setting(AppSettings::DisableVersion) + .arg({ + let arg = Arg::with_name("data_dir") + .short("d") + .long("data_dir") + .value_name("PATH") + .takes_value(true) + .help("Directory to store install data"); + match *defaults::DATA_DIR { + Some(ref data_dir) => arg.default_value(&data_dir), + None => arg.required(true), + } + }) .arg( Arg::with_name("json_rpc_url") .short("u") .long("url") .value_name("URL") .takes_value(true) - .default_value("https://api.testnet.solana.com/") + .default_value(defaults::JSON_RPC_URL) .help("JSON RPC URL for the solana cluster"), ) - .arg( - Arg::with_name("update_pubkey") + .arg({ + let arg = Arg::with_name("update_pubkey") .short("p") .long("pubkey") .value_name("PUBKEY") .takes_value(true) - .default_value("Solana-managed update manifest") - .help("Public key of the update manifest"), - ), + .validator(|value| match value.parse::() { + Ok(_) => Ok(()), + Err(err) => Err(format!("{:?}", err)), + }) + .help("Public key of the update manifest"); + + match defaults::update_pubkey(build_env::TARGET) { + Some(default_value) => arg.default_value(default_value), + None => arg.required(true), + } + }), ) .subcommand( SubCommand::with_name("info") @@ -91,11 +127,38 @@ fn main() -> Result<(), Box> { ) .get_matches(); - println!("TARGET={}", TARGET); - println!( - "BUILD_SECONDS_SINCE_UNIX_EPOCH={:?}", - Duration::from_secs(u64::from_str_radix(BUILD_SECONDS_SINCE_UNIX_EPOCH, 10).unwrap()) - ); - println!("{:?}", matches); - Ok(()) + let config_file = matches.value_of("config_file").unwrap(); + + match matches.subcommand() { + ("init", Some(matches)) => { + let json_rpc_url = matches.value_of("json_rpc_url").unwrap(); + let update_pubkey = matches + .value_of("update_pubkey") + .unwrap() + .parse::() + .unwrap(); + let data_dir = matches.value_of("data_dir").unwrap(); + command::init(config_file, data_dir, json_rpc_url, &update_pubkey) + } + ("info", Some(matches)) => { + let local_info_only = matches.is_present("local_info_only"); + command::info(config_file, local_info_only) + } + ("deploy", Some(matches)) => { + let download_url = matches.value_of("download_url").unwrap(); + let update_manifest_keypair = matches.value_of("update_manifest_keypair").unwrap(); + command::deploy(config_file, download_url, update_manifest_keypair) + } + ("update", Some(_matches)) => command::update(config_file), + ("run", Some(matches)) => { + let program_name = matches.value_of("program_name").unwrap(); + let program_arguments = matches + .values_of("program_arguments") + .map(|iter| iter.collect()) + .unwrap_or_else(|| vec![]); + + command::run(config_file, program_name, program_arguments) + } + _ => unreachable!(), + } } diff --git a/install/src/update_manifest.rs b/install/src/update_manifest.rs new file mode 100644 index 000000000..16e99f305 --- /dev/null +++ b/install/src/update_manifest.rs @@ -0,0 +1,19 @@ +use serde_derive::{Deserialize, Serialize}; +use solana_sdk::signature::Signature; + +/// Information required to download and apply a given update +#[derive(Serialize, Deserialize, Default, Debug, PartialEq)] +pub struct UpdateManifest { + pub target: String, // Target triple (TARGET) + pub commit: String, // git sha1 of this update, must match the commit sha1 in the release tar.bz2 + pub timestamp_secs: u64, // When the release was deployed in seconds since UNIX EPOCH + pub download_url: String, // Download URL to the release tar.bz2 + pub download_signature: Signature, // Signature of the release tar.bz2 file, verify with the Account public key +} + +/// Userdata of an Update Manifest program Account. +#[derive(Serialize, Deserialize, Default, Debug, PartialEq)] +pub struct SignedUpdateManifest { + pub manifest: UpdateManifest, + pub manifest_signature: Signature, // Signature of UpdateInfo, verify with the Account public key +}