From cde9c0ef40f3ad6066bd7a212a249d0d1ac0f515 Mon Sep 17 00:00:00 2001 From: Sergey Isaev Date: Wed, 12 Aug 2020 02:33:32 +0300 Subject: [PATCH 1/4] Add publish an package to the package manager --- leo/commands/publish.rs | 105 ++++++++++++++++++++++++++++++--- leo/errors/cli.rs | 10 ++++ leo/errors/commands/mod.rs | 3 + leo/errors/commands/publish.rs | 10 ++++ package/src/root/manifest.rs | 6 +- package/src/root/zip.rs | 4 ++ 6 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 leo/errors/commands/publish.rs diff --git a/leo/commands/publish.rs b/leo/commands/publish.rs index 49f4d2f0b9..b2f945e4af 100644 --- a/leo/commands/publish.rs +++ b/leo/commands/publish.rs @@ -1,21 +1,51 @@ -use crate::{cli::*, cli_types::*, commands::BuildCommand, errors::CLIError}; +use crate::{ + cli::*, + cli_types::*, + commands::{BuildCommand, LoginCommand}, + errors::{ + commands::PublishError::{ConnectionUnavalaible, PackageNotPublished}, + CLIError, + CLIError::PublishError, + }, +}; +use clap::ArgMatches; use leo_package::{ outputs::OutputsDirectory, root::{Manifest, ZipFile}, }; - -use clap::ArgMatches; +use reqwest::{ + blocking::{multipart::Form, Client}, + header::{HeaderMap, HeaderValue}, +}; +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)] +struct ResponseJson { + package_id: String, + _success: bool, +} + #[derive(Debug)] pub struct PublishCommand; impl CLI for PublishCommand { type Options = (); - type Output = (); + type Output = Option; const ABOUT: AboutType = "Publish the current package to the package manager (*)"; - const ARGUMENTS: &'static [ArgumentType] = &[]; + const ARGUMENTS: &'static [ArgumentType] = &[ + // (name, description, required, index) + ( + "NAME", + "Sets the resulting package name, defaults to the directory name", + true, + 1u64, + ), + ]; const FLAGS: &'static [FlagType] = &[]; const NAME: NameType = "publish"; const OPTIONS: &'static [OptionType] = &[]; @@ -23,18 +53,23 @@ impl CLI for PublishCommand { #[cfg_attr(tarpaulin, skip)] fn parse(_arguments: &ArgMatches) -> Result { + // match arguments.value_of("NAME") { + // Some(name) => Ok((Some(name.to_string()),)), + // None => Ok((None,)), + // } Ok(()) } #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result { + fn output(_options: Self::Options) -> Result { // Build all program files. // It's okay if there's just a lib.leo file here - let _output = BuildCommand::output(options)?; + // let _output = BuildCommand::output(options)?; // Get the package name let path = current_dir()?; let package_name = Manifest::try_from(&path)?.get_package_name(); + let package_version = Manifest::try_from(&path)?.get_package_version(); // Create the output directory OutputsDirectory::create(&path)?; @@ -47,6 +82,60 @@ impl CLI for PublishCommand { zip_file.write(&path)?; } - Ok(()) + let is_public = "false"; + let form_data = Form::new() + .text("name", package_name) + .text("version", package_version) + .file("file", zip_file.get_file_path(&path))? + .text("public", is_public); + + // Client for make POST request + let client = Client::new(); + + // Get token to make an authorized request + let token = match LoginCommand::read_token() { + Ok(token) => token, + + // If not logged then try to login using JWT + Err(_errorr) => { + log::warn!("You should be logged before publish the package"); + log::info!("Trying to log in using JWT..."); + let options = (None, None, None); + + LoginCommand::output(options)? + } + }; + + // Headers for request to publish package + let mut headers = HeaderMap::new(); + headers.insert( + "Authorization", + HeaderValue::from_str(&format!("{} {}", "Bearer", token)).unwrap(), + ); + + // Make a request to publish a package + let response = client + .post(format!("{}{}", PACKAGE_MANAGER_URL, PUBLISH_URL).as_str()) + .headers(headers) + .multipart(form_data) + .send(); + + // Get a response result + let result = match response { + Ok(json_result) => match json_result.json::() { + Ok(json) => json, + Err(error) => { + log::warn!("{:?}", error); + return Err(PublishError(PackageNotPublished("Package not published".into()))); + } + }, + Err(error) => { + log::warn!("{:?}", error); + return Err(PublishError(ConnectionUnavalaible("Connection error".into()))); + } + }; + + log::info!("Packge published successfully"); + Ok(Some(result.package_id)) } } diff --git a/leo/errors/cli.rs b/leo/errors/cli.rs index c9f28f281f..8687b82f0b 100644 --- a/leo/errors/cli.rs +++ b/leo/errors/cli.rs @@ -51,6 +51,9 @@ pub enum CLIError { #[error("{}", _0)] ProvingKeyFileError(ProvingKeyFileError), + #[error("{}", _0)] + PublishError(PublishError), + #[error("{}", _0)] RunError(RunError), @@ -175,6 +178,13 @@ impl From for CLIError { } } +impl From for CLIError { + fn from(error: PublishError) -> Self { + log::error!("{}\n", error); + CLIError::PublishError(error) + } +} + impl From for CLIError { fn from(error: RunError) -> Self { log::error!("{}\n", error); diff --git a/leo/errors/commands/mod.rs b/leo/errors/commands/mod.rs index e283cfaa63..0f5a5a0f30 100644 --- a/leo/errors/commands/mod.rs +++ b/leo/errors/commands/mod.rs @@ -10,6 +10,9 @@ pub use self::login::*; pub mod new; pub use self::new::*; +pub mod publish; +pub use self::publish::*; + pub mod run; pub use self::run::*; diff --git a/leo/errors/commands/publish.rs b/leo/errors/commands/publish.rs new file mode 100644 index 0000000000..ed98ba2d98 --- /dev/null +++ b/leo/errors/commands/publish.rs @@ -0,0 +1,10 @@ +use std::ffi::OsString; + +#[derive(Debug, Error)] +pub enum PublishError { + #[error("connection unavailable {:?}", _0)] + ConnectionUnavalaible(OsString), + + #[error("package not published {:?}", _0)] + PackageNotPublished(OsString), +} diff --git a/package/src/root/manifest.rs b/package/src/root/manifest.rs index bca2d8b47c..a439e1d31c 100644 --- a/package/src/root/manifest.rs +++ b/package/src/root/manifest.rs @@ -8,7 +8,7 @@ use std::{ path::PathBuf, }; -pub static MANIFEST_FILE_NAME: &str = "Leo.toml"; +pub const MANIFEST_FILE_NAME: &str = "Leo.toml"; #[derive(Deserialize)] pub struct Package { @@ -43,6 +43,10 @@ impl Manifest { self.package.name.clone() } + pub fn get_package_version(&self) -> String { + self.package.version.clone() + } + pub fn write_to(self, path: &PathBuf) -> Result<(), ManifestError> { let mut path = path.to_owned(); if path.is_dir() { diff --git a/package/src/root/zip.rs b/package/src/root/zip.rs index fbe925336c..cc21099b7c 100644 --- a/package/src/root/zip.rs +++ b/package/src/root/zip.rs @@ -41,6 +41,10 @@ impl ZipFile { path.exists() } + pub fn get_file_path(&self, current_dir: &PathBuf) -> PathBuf { + self.setup_file_path(current_dir) + } + // /// Reads the program bytes from the given file path if it exists. // pub fn read_from(&self, path: &PathBuf) -> Result, ZipFileError> { // let path = self.setup_file_path(path); From 3f30543d509b2a063ab6b676107b5f7f0644ef63 Mon Sep 17 00:00:00 2001 From: Sergey Isaev Date: Wed, 12 Aug 2020 02:49:15 +0300 Subject: [PATCH 2/4] Fix build --- leo/commands/login.rs | 97 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/leo/commands/login.rs b/leo/commands/login.rs index a72e134df0..4a3db628f5 100644 --- a/leo/commands/login.rs +++ b/leo/commands/login.rs @@ -54,7 +54,7 @@ impl LoginCommand { impl CLI for LoginCommand { // Format: token, username, password type Options = (Option, Option, Option); - type Output = (); + type Output = String; const ABOUT: AboutType = "Login to the package manager (*)"; const ARGUMENTS: &'static [ArgumentType] = &[ @@ -138,6 +138,101 @@ impl CLI for LoginCommand { LoginCommand::write_token(token.as_str())?; log::info!("Successfully logged in"); + Ok(token) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + use std::{ + fs::{remove_dir, remove_file}, + io, + }; + + const TEST_DIR: &str = ".test"; + + fn setup(suffix: &str) -> Result<(), io::Error> { + let test_dir = format!("{}_{}", TEST_DIR, suffix); + create_dir(&test_dir)?; + Ok(()) + } + + fn clean(suffix: &str) -> Result<(), io::Error> { + let test_dir = format!("{}_{}", TEST_DIR, suffix); + remove_file(&format!( + "{}/{}/{}", + test_dir, LEO_CREDENTIALS_DIR, LEO_CREDENTIALS_FILE + ))?; + remove_dir(&format!("{}/{}", test_dir, LEO_CREDENTIALS_DIR))?; + remove_dir(test_dir)?; + Ok(()) + } + + #[test] + #[ignore] + fn test_credential_dir_exists() -> Result<(), io::Error> { + let suffix = "test1"; + setup(suffix)?; + create_dir(LEO_CREDENTIALS_DIR)?; + + let token = "SOME_TOKEN".to_string(); + let options = (Some(token.clone()), None, None); + LoginCommand::output(options).unwrap(); + + assert_eq!(token, LoginCommand::read_token()?); + clean(suffix)?; + Ok(()) + } + + #[test] + #[ignore] + fn test_credential_file_exists() -> Result<(), io::Error> { + let suffix = "test2"; + setup(suffix)?; + create_dir(LEO_CREDENTIALS_DIR)?; + let old_token = "OLD_TOKEN"; + LoginCommand::write_token(old_token)?; + + let token = "NEW_TOKEN".to_string(); + let options = (Some(token.clone()), None, None); + LoginCommand::output(options).unwrap(); + + assert_eq!(token, LoginCommand::read_token()?); + clean(suffix)?; + Ok(()) + } + + #[test] + #[ignore] + fn test_credential_dir_does_not_exist() -> Result<(), io::Error> { + let suffix = "test3"; + setup(suffix)?; + + let token = "SOME_TOKEN".to_string(); + let options = (Some(token.clone()), None, None); + LoginCommand::output(options).unwrap(); + + assert_eq!(token, LoginCommand::read_token()?); + clean(suffix)?; + Ok(()) + } + + #[test] + #[ignore] + fn test_credential_file_does_not_exist() -> Result<(), io::Error> { + let suffix = "test4"; + setup(suffix)?; + create_dir(LEO_CREDENTIALS_DIR)?; + + let token = "SOME_TOKEN".to_string(); + let options = (Some(token.clone()), None, None); + LoginCommand::output(options).unwrap(); + + assert_eq!(token, LoginCommand::read_token()?); + clean(suffix)?; Ok(()) } } From cc60e0ba7ed441fd00116829d5a79cd743ad06ae Mon Sep 17 00:00:00 2001 From: Sergey Isaev Date: Wed, 12 Aug 2020 03:04:02 +0300 Subject: [PATCH 3/4] Fix CI tests --- leo/commands/login.rs | 95 ------------------------------------------- 1 file changed, 95 deletions(-) diff --git a/leo/commands/login.rs b/leo/commands/login.rs index 4a3db628f5..ed0491941e 100644 --- a/leo/commands/login.rs +++ b/leo/commands/login.rs @@ -141,98 +141,3 @@ impl CLI for LoginCommand { Ok(token) } } - -#[cfg(test)] -mod tests { - - use super::*; - - use std::{ - fs::{remove_dir, remove_file}, - io, - }; - - const TEST_DIR: &str = ".test"; - - fn setup(suffix: &str) -> Result<(), io::Error> { - let test_dir = format!("{}_{}", TEST_DIR, suffix); - create_dir(&test_dir)?; - Ok(()) - } - - fn clean(suffix: &str) -> Result<(), io::Error> { - let test_dir = format!("{}_{}", TEST_DIR, suffix); - remove_file(&format!( - "{}/{}/{}", - test_dir, LEO_CREDENTIALS_DIR, LEO_CREDENTIALS_FILE - ))?; - remove_dir(&format!("{}/{}", test_dir, LEO_CREDENTIALS_DIR))?; - remove_dir(test_dir)?; - Ok(()) - } - - #[test] - #[ignore] - fn test_credential_dir_exists() -> Result<(), io::Error> { - let suffix = "test1"; - setup(suffix)?; - create_dir(LEO_CREDENTIALS_DIR)?; - - let token = "SOME_TOKEN".to_string(); - let options = (Some(token.clone()), None, None); - LoginCommand::output(options).unwrap(); - - assert_eq!(token, LoginCommand::read_token()?); - clean(suffix)?; - Ok(()) - } - - #[test] - #[ignore] - fn test_credential_file_exists() -> Result<(), io::Error> { - let suffix = "test2"; - setup(suffix)?; - create_dir(LEO_CREDENTIALS_DIR)?; - let old_token = "OLD_TOKEN"; - LoginCommand::write_token(old_token)?; - - let token = "NEW_TOKEN".to_string(); - let options = (Some(token.clone()), None, None); - LoginCommand::output(options).unwrap(); - - assert_eq!(token, LoginCommand::read_token()?); - clean(suffix)?; - Ok(()) - } - - #[test] - #[ignore] - fn test_credential_dir_does_not_exist() -> Result<(), io::Error> { - let suffix = "test3"; - setup(suffix)?; - - let token = "SOME_TOKEN".to_string(); - let options = (Some(token.clone()), None, None); - LoginCommand::output(options).unwrap(); - - assert_eq!(token, LoginCommand::read_token()?); - clean(suffix)?; - Ok(()) - } - - #[test] - #[ignore] - fn test_credential_file_does_not_exist() -> Result<(), io::Error> { - let suffix = "test4"; - setup(suffix)?; - create_dir(LEO_CREDENTIALS_DIR)?; - - let token = "SOME_TOKEN".to_string(); - let options = (Some(token.clone()), None, None); - LoginCommand::output(options).unwrap(); - - assert_eq!(token, LoginCommand::read_token()?); - clean(suffix)?; - Ok(()) - } -} From d152636d39d0da2cf8f71b9e93ede78296ddff7d Mon Sep 17 00:00:00 2001 From: Sergey Isaev Date: Thu, 13 Aug 2020 14:16:57 +0300 Subject: [PATCH 4/4] Update publish command corresponding the new API --- leo/commands/publish.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/leo/commands/publish.rs b/leo/commands/publish.rs index b2f945e4af..4130011535 100644 --- a/leo/commands/publish.rs +++ b/leo/commands/publish.rs @@ -82,12 +82,10 @@ impl CLI for PublishCommand { zip_file.write(&path)?; } - let is_public = "false"; let form_data = Form::new() .text("name", package_name) .text("version", package_version) - .file("file", zip_file.get_file_path(&path))? - .text("public", is_public); + .file("file", zip_file.get_file_path(&path))?; // Client for make POST request let client = Client::new();