Merge pull request #247 from AleoHQ/feature/leo-cli

Leo CLI support for login and add
This commit is contained in:
Howard Wu 2020-08-17 01:37:03 -07:00 committed by GitHub
commit 72311b0951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 321 additions and 95 deletions

82
Cargo.lock generated
View File

@ -33,6 +33,18 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "atty"
version = "0.2.14"
@ -70,6 +82,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
version = "0.12.3"
@ -134,6 +152,17 @@ dependencies = [
"opaque-debug 0.2.3",
]
[[package]]
name = "blake2b_simd"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
@ -321,6 +350,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation"
version = "0.7.0"
@ -527,6 +562,26 @@ dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "dirs"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
dependencies = [
"libc",
"redox_users",
"winapi 0.3.9",
]
[[package]]
name = "dtoa"
version = "0.4.6"
@ -1070,6 +1125,7 @@ version = "0.1.0"
dependencies = [
"clap",
"colored",
"dirs",
"env_logger",
"from-pest",
"lazy_static",
@ -1095,6 +1151,7 @@ dependencies = [
"snarkos-utilities",
"thiserror",
"toml",
"zip",
]
[[package]]
@ -1824,6 +1881,17 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_users"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
dependencies = [
"getrandom",
"redox_syscall",
"rust-argon2",
]
[[package]]
name = "regex"
version = "1.3.9"
@ -1866,7 +1934,7 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12427a5577082c24419c9c417db35cfeb65962efc7675bb6b0d5f1f9d315bfe6"
dependencies = [
"base64",
"base64 0.12.3",
"bytes",
"encoding_rs",
"futures-core",
@ -1896,6 +1964,18 @@ dependencies = [
"winreg",
]
[[package]]
name = "rust-argon2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
dependencies = [
"base64 0.11.0",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
]
[[package]]
name = "rocksdb"
version = "0.13.0"

View File

@ -31,6 +31,7 @@ snarkos-utilities = { git = "ssh://git@github.com/AleoHQ/snarkOS.git", package =
clap = { version = "2.33.3" }
colored = { version = "2.0" }
dirs = { version = "3.0.1" }
env_logger = { version = "0.7" }
from-pest = { version = "0.3.1" }
lazy_static = { version = "1.4.0" }
@ -44,6 +45,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
toml = { version = "0.5" }
thiserror = { version = "1.0" }
zip = { version = "0.5" }
[dev-dependencies]
rusty-hook = { version = "0.11.2" }

View File

@ -1,57 +1,146 @@
//
// Usage:
//
// leo add -a author -p package_name -v version
// leo add -a author -p package_name
//
use crate::{
cli::*,
cli::CLI,
cli_types::*,
commands::BuildCommand,
errors::{CLIError, RunError},
credentials::*,
errors::{AddError::*, CLIError::AddError},
};
use leo_package::{
imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME},
root::Manifest,
source::{MAIN_FILE_NAME, SOURCE_DIRECTORY_NAME},
};
use clap::ArgMatches;
use std::{convert::TryFrom, env::current_dir};
use std::{
collections::HashMap,
convert::TryFrom,
env::current_dir,
fs::{create_dir_all, File},
io::{Read, Write},
};
pub const ADD_URL: &str = "api/package/fetch";
#[derive(Debug)]
pub struct AddCommand;
impl CLI for AddCommand {
type Options = ();
// Format: author, package_name, version
type Options = (Option<String>, Option<String>, Option<String>);
type Output = ();
const ABOUT: AboutType = "Install a package from the package manager (*)";
const ABOUT: AboutType = "Install a package from the package manager";
const ARGUMENTS: &'static [ArgumentType] = &[];
const FLAGS: &'static [FlagType] = &[];
const NAME: NameType = "add";
const OPTIONS: &'static [OptionType] = &[];
const OPTIONS: &'static [OptionType] = &[
// (argument, conflicts, possible_values, requires)
("[author] -a --author=<author> 'Specify a package author'", &[], &[], &[
"package_name",
]),
(
"[package_name] -p --package_name=<package_name> 'Specify a package name'",
&[],
&[],
&["author"],
),
(
"[version] -v --version=[version] 'Specify a package version'",
&[],
&[],
&["author", "package_name"],
),
];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
fn output(options: Self::Options) -> Result<Self::Output, CLIError> {
let path = current_dir()?;
match BuildCommand::output(options)? {
Some((_program, _checksum_differs)) => {
// Get the package name
let _package_name = Manifest::try_from(&path)?.get_package_name();
log::info!("Unimplemented - `leo add`");
Ok(())
}
None => {
let mut main_file_path = path.clone();
main_file_path.push(SOURCE_DIRECTORY_NAME);
main_file_path.push(MAIN_FILE_NAME);
Err(CLIError::RunError(RunError::MainFileDoesNotExist(
main_file_path.into_os_string(),
)))
}
fn parse(arguments: &clap::ArgMatches) -> Result<Self::Options, crate::errors::CLIError> {
// TODO update to new package manager API without an author field
if arguments.is_present("author") && arguments.is_present("package_name") {
return Ok((
arguments.value_of("author").map(|s| s.to_string()),
arguments.value_of("package_name").map(|s| s.to_string()),
arguments.value_of("version").map(|s| s.to_string()),
));
} else {
return Ok((None, None, None));
}
}
fn output(options: Self::Options) -> Result<Self::Output, crate::errors::CLIError> {
let token = read_token()?;
let path = current_dir()?;
// Enforce that the current directory is a leo package
Manifest::try_from(&path)?;
let (response, package_name) = match options {
(Some(author), Some(package_name), version) => {
let client = reqwest::blocking::Client::new();
let url = format!("{}{}", PACKAGE_MANAGER_URL, ADD_URL);
let mut json = HashMap::new();
json.insert("author", author);
json.insert("package_name", package_name.clone());
if let Some(version) = version {
json.insert("version", version);
}
match client.post(&url).bearer_auth(token).json(&json).send() {
Ok(response) => (response, package_name),
//Cannot connect to the server
Err(_error) => {
return Err(AddError(ConnectionUnavailable(
"Could not connect to the package manager".into(),
)));
}
}
}
_ => return Err(AddError(MissingAuthorOrPackageName)),
};
let mut path = current_dir()?;
ImportsDirectory::create(&path)?;
path.push(IMPORTS_DIRECTORY_NAME);
path.push(package_name);
create_dir_all(&path)?;
let bytes = response.bytes()?;
let reader = std::io::Cursor::new(bytes);
let mut zip_arhive = match zip::ZipArchive::new(reader) {
Ok(zip) => zip,
Err(error) => return Err(AddError(ZipError(error.to_string().into()))),
};
for i in 0..zip_arhive.len() {
let file = match zip_arhive.by_index(i) {
Ok(file) => file,
Err(error) => return Err(AddError(ZipError(error.to_string().into()))),
};
let file_name = file.name();
let mut file_path = path.clone();
file_path.push(file_name);
if file_name.ends_with("/") {
create_dir_all(file_path)?;
} else {
if let Some(parent_directory) = path.parent() {
create_dir_all(parent_directory)?;
}
File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::<Vec<u8>>())?;
}
}
log::info!("Successfully added a package");
Ok(())
}
}

View File

@ -3,47 +3,25 @@
//
// leo login <token>
// leo login -u username -p password
// leo login // not yet implemented
//
use crate::{cli::CLI, cli_types::*, errors::LoginError};
use lazy_static::lazy_static;
use std::{
collections::HashMap,
fs::{create_dir, File},
io,
io::prelude::*,
path::Path,
use crate::{
cli::CLI,
cli_types::*,
credentials::*,
errors::{
CLIError::LoginError,
LoginError::{CannotGetToken, NoConnectionFound, NoCredentialsProvided, WrongLoginOrPassword},
},
};
const PACKAGE_MANAGER_URL: &str = "https://apm-backend-dev.herokuapp.com/";
const LOGIN_URL: &str = "api/account/login";
use std::collections::HashMap;
const LEO_CREDENTIALS_DIR: &str = ".leo";
const LEO_CREDENTIALS_FILE: &str = "credentials";
lazy_static! {
static ref LEO_CREDENTIALS_PATH: String = format!("{}/{}", LEO_CREDENTIALS_DIR, LEO_CREDENTIALS_FILE);
}
pub const LOGIN_URL: &str = "api/account/authenticate";
#[derive(Debug)]
pub struct LoginCommand;
impl LoginCommand {
fn write_token(token: &str) -> Result<(), io::Error> {
let mut credentials = File::create(LEO_CREDENTIALS_PATH.as_str())?;
credentials.write_all(&token.as_bytes())?;
Ok(())
}
pub fn read_token() -> Result<String, io::Error> {
let mut credentials = File::open(LEO_CREDENTIALS_PATH.as_str())?;
let mut buf = String::new();
credentials.read_to_string(&mut buf)?;
Ok(buf)
}
}
impl CLI for LoginCommand {
// Format: token, username, password
type Options = (Option<String>, Option<String>, Option<String>);
@ -79,10 +57,7 @@ impl CLI for LoginCommand {
match arguments.value_of("NAME") {
Some(name) => Ok((Some(name.to_string()), None, None)),
None => {
// TODO implement JWT
Ok((None, None, None))
}
None => Ok((None, None, None)),
}
}
@ -105,40 +80,33 @@ impl CLI for LoginCommand {
Ok(json) => json,
Err(_error) => {
log::error!("Wrong login or password");
return Err(LoginError::WrongLoginOrPassword("Wrong login or password".into()).into());
return Err(WrongLoginOrPassword("Wrong login or password".into()).into());
}
},
//Cannot connect to the server
Err(_error) => {
return Err(
LoginError::NoConnectionFound("Could not connect to the package manager".into()).into(),
);
return Err(LoginError(NoConnectionFound(
"Could not connect to the package manager".into(),
)));
}
};
match response.get("token") {
Some(token) => Some(token.clone()),
None => {
return Err(LoginError::CannotGetToken("No token was provided in the response".into()).into());
return Err(CannotGetToken("No token was provided in the response".into()).into());
}
}
}
// Login using JWT
(_, _, _) => {
// TODO JWT
None
}
// Login using stored JWT credentials.
// TODO (raychu86) Package manager re-authentication from token
(_, _, _) => Some(read_token()?),
};
match token {
Some(token) => {
// Create Leo credentials directory if it not exists
if !Path::new(LEO_CREDENTIALS_DIR).exists() {
create_dir(LEO_CREDENTIALS_DIR)?;
}
LoginCommand::write_token(token.as_str())?;
write_token(token.as_str())?;
log::info!("Login successful.");
@ -147,7 +115,7 @@ impl CLI for LoginCommand {
_ => {
log::error!("Failed to login. Please run `leo login -h` for help.");
Err(LoginError::NoCredentialsProvided.into())
Err(NoCredentialsProvided.into())
}
}
}

View File

@ -1,3 +1,6 @@
pub mod add;
pub use self::add::*;
pub mod build;
pub use self::build::*;
@ -13,9 +16,6 @@ pub use self::init::*;
pub mod lint;
pub use self::lint::*;
pub mod add;
pub use self::add::*;
pub mod login;
pub use self::login::*;

View File

@ -2,6 +2,7 @@ use crate::{
cli::*,
cli_types::*,
commands::LoginCommand,
credentials::{read_token, PACKAGE_MANAGER_URL},
errors::{
commands::PublishError::{ConnectionUnavalaible, PackageNotPublished},
CLIError,
@ -21,7 +22,6 @@ use reqwest::{
use serde::Deserialize;
use std::{convert::TryFrom, env::current_dir};
const PACKAGE_MANAGER_URL: &str = "https://apm-backend-dev.herokuapp.com/";
const PUBLISH_URL: &str = "api/package/publish";
#[derive(Deserialize)]
@ -81,7 +81,7 @@ impl CLI for PublishCommand {
let client = Client::new();
// Get token to make an authorized request
let token = match LoginCommand::read_token() {
let token = match read_token() {
Ok(token) => token,
// If not logged in, then try logging in using JWT.

43
leo/credentials.rs Normal file
View File

@ -0,0 +1,43 @@
use dirs::home_dir;
use lazy_static::lazy_static;
use std::{
fs::{create_dir_all, File},
io,
io::prelude::*,
path::{Path, PathBuf},
};
pub const PACKAGE_MANAGER_URL: &str = "https://apm-backend-dev.herokuapp.com/";
pub const LEO_CREDENTIALS_FILE: &str = "credentials";
lazy_static! {
pub static ref LEO_CREDENTIALS_DIR: PathBuf = {
let mut path = home_dir().expect("Invalid home directory");
path.push(".leo");
path
};
pub static ref LEO_CREDENTIALS_PATH: PathBuf = {
let mut path = LEO_CREDENTIALS_DIR.to_path_buf();
path.push(LEO_CREDENTIALS_FILE);
path
};
}
pub fn write_token(token: &str) -> Result<(), io::Error> {
// Create Leo credentials directory if it not exists
if !Path::new(&LEO_CREDENTIALS_DIR.to_path_buf()).exists() {
create_dir_all(&LEO_CREDENTIALS_DIR.to_path_buf())?;
}
let mut credentials = File::create(&LEO_CREDENTIALS_PATH.to_path_buf())?;
credentials.write_all(&token.as_bytes())?;
Ok(())
}
pub fn read_token() -> Result<String, io::Error> {
let mut credentials = File::open(&LEO_CREDENTIALS_PATH.to_path_buf())?;
let mut buf = String::new();
credentials.read_to_string(&mut buf)?;
Ok(buf)
}

View File

@ -3,6 +3,9 @@ use leo_package::errors::*;
#[derive(Debug, Error)]
pub enum CLIError {
#[error("{}", _0)]
AddError(AddError),
#[error("{}", _0)]
BuildError(BuildError),
@ -24,6 +27,9 @@ pub enum CLIError {
#[error("{}", _0)]
InitError(InitError),
#[error("{}", _0)]
ImportsDirectoryError(ImportsDirectoryError),
#[error("{}", _0)]
InputDirectoryError(InputsDirectoryError),
@ -90,6 +96,13 @@ impl From<BuildError> for CLIError {
}
}
impl From<AddError> for CLIError {
fn from(error: AddError) -> Self {
log::error!("{}\n", error);
CLIError::AddError(error)
}
}
impl From<ChecksumFileError> for CLIError {
fn from(error: ChecksumFileError) -> Self {
log::error!("{}\n", error);
@ -118,6 +131,13 @@ impl From<InitError> for CLIError {
}
}
impl From<ImportsDirectoryError> for CLIError {
fn from(error: ImportsDirectoryError) -> Self {
log::error!("{}\n", error);
CLIError::ImportsDirectoryError(error)
}
}
impl From<InputsDirectoryError> for CLIError {
fn from(error: InputsDirectoryError) -> Self {
log::error!("{}\n", error);
@ -244,6 +264,13 @@ impl From<leo_input::errors::InputParserError> for CLIError {
}
}
impl From<reqwest::Error> for CLIError {
fn from(error: reqwest::Error) -> Self {
log::error!("{}\n", error);
CLIError::Crate("rewquest", format!("{}", error))
}
}
impl From<snarkos_errors::algorithms::snark::SNARKError> for CLIError {
fn from(error: snarkos_errors::algorithms::snark::SNARKError) -> Self {
log::error!("{}\n", error);

View File

@ -0,0 +1,13 @@
use std::ffi::OsString;
#[derive(Debug, Error)]
pub enum AddError {
#[error("connection unavailable {:?}", _0)]
ConnectionUnavailable(OsString),
#[error("missing author or package name")]
MissingAuthorOrPackageName,
#[error("{:?}", _0)]
ZipError(OsString),
}

View File

@ -1,3 +1,6 @@
pub mod add;
pub use self::add::*;
pub mod build;
pub use self::build::*;

View File

@ -1,10 +1,11 @@
#[macro_use]
extern crate thiserror;
#[cfg_attr(tarpaulin, skip)]
pub mod cli;
pub mod cli_types;
pub mod commands;
#[cfg_attr(tarpaulin, skip)]
pub mod credentials;
pub mod errors;
pub mod logger;
pub mod synthesizer;