From 4ce42f136b98aa42143ae34d5680abf909ef746a Mon Sep 17 00:00:00 2001 From: Firelight Flagboy Date: Mon, 2 Dec 2024 16:18:36 +0100 Subject: [PATCH] Factorize the initialization of a client in the CLI --- .../commands/device/export_recovery_device.rs | 20 ++----- cli/src/commands/invite/cancel.rs | 40 ++++--------- cli/src/commands/invite/device.rs | 54 +++++++----------- cli/src/commands/invite/greet.rs | 47 ++++++---------- cli/src/commands/invite/list.rs | 16 ++---- cli/src/commands/invite/shared_recovery.rs | 18 ++---- cli/src/commands/invite/user.rs | 56 +++++++------------ cli/src/lib.rs | 2 +- cli/src/{macro_opts.rs => macros.rs} | 24 ++++++++ 9 files changed, 105 insertions(+), 172 deletions(-) rename cli/src/{macro_opts.rs => macros.rs} (90%) diff --git a/cli/src/commands/device/export_recovery_device.rs b/cli/src/commands/device/export_recovery_device.rs index ee673121c8c..3e8281bfac6 100644 --- a/cli/src/commands/device/export_recovery_device.rs +++ b/cli/src/commands/device/export_recovery_device.rs @@ -14,23 +14,13 @@ crate::clap_parser_with_shared_opts_builder!( } ); -pub async fn main(args: Args) -> anyhow::Result<()> { - let Args { - output, - device, - config_dir, - password_stdin, - } = args; - log::trace!( - "Exporting recovery device at {} (confdir={}, device={})", - output.display(), - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); +crate::build_main_with_client!(export_recovery_device); - let mut handle = start_spinner("Saving recovery device file".into()); +pub async fn export_recovery_device(args: Args, client: &StartedClient) -> anyhow::Result<()> { + let Args { output, .. } = args; + log::trace!("Exporting recovery device at {}", output.display()); - let client = load_client(&config_dir, device.clone(), password_stdin).await?; + let mut handle = start_spinner("Saving recovery device file".into()); let now = DateTime::now(); let (passphrase, data) = client diff --git a/cli/src/commands/invite/cancel.rs b/cli/src/commands/invite/cancel.rs index 2e33f7a8e0b..4feb03db077 100644 --- a/cli/src/commands/invite/cancel.rs +++ b/cli/src/commands/invite/cancel.rs @@ -1,9 +1,7 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -use libparsec::{ - authenticated_cmds::latest::invite_cancel::{self, InviteCancelRep}, - InvitationToken, -}; +use anyhow::Context; +use libparsec::InvitationToken; use crate::utils::*; @@ -16,32 +14,18 @@ crate::clap_parser_with_shared_opts_builder!( } ); -pub async fn main(args: Args) -> anyhow::Result<()> { - let Args { - token, - device, - config_dir, - password_stdin, - } = args; - log::trace!( - "Cancelling invitation (confdir={}, device={})", - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); - - let (cmds, _) = load_cmds(&config_dir, device, password_stdin).await?; - let mut handle = start_spinner("Deleting invitation".into()); +crate::build_main_with_client!(invite_cancel); + +pub async fn invite_cancel(args: Args, client: &StartedClient) -> anyhow::Result<()> { + let Args { token, .. } = args; + log::trace!("Cancelling invitation"); - let rep = cmds.send(invite_cancel::Req { token }).await?; + let mut handle = start_spinner("Deleting invitation".into()); - match rep { - InviteCancelRep::Ok => (), - rep => { - return Err(anyhow::anyhow!( - "Server error while cancelling invitation: {rep:?}" - )); - } - }; + client + .cancel_invitation(token) + .await + .context("Server refused to cancel invitation")?; handle.stop_with_message("Invitation deleted".into()); diff --git a/cli/src/commands/invite/device.rs b/cli/src/commands/invite/device.rs index ca62c3f070f..95244ea759f 100644 --- a/cli/src/commands/invite/device.rs +++ b/cli/src/commands/invite/device.rs @@ -1,9 +1,7 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -use libparsec::{ - authenticated_cmds::latest::invite_new_device::{self, InviteNewDeviceRep}, - InvitationType, ParsecInvitationAddr, -}; +use anyhow::Context; +use libparsec::{InvitationType, ParsecInvitationAddr}; use crate::utils::*; @@ -12,39 +10,25 @@ crate::clap_parser_with_shared_opts_builder!( pub struct Args {} ); -pub async fn main(args: Args) -> anyhow::Result<()> { - let Args { - device, - config_dir, - password_stdin, - } = args; - log::trace!( - "Inviting a device (confdir={}, device={})", - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); - - let (cmds, device) = load_cmds(&config_dir, device, password_stdin).await?; +crate::build_main_with_client!(invite_device); + +pub async fn invite_device(_args: Args, client: &StartedClient) -> anyhow::Result<()> { + log::trace!("Inviting a device"); + let mut handle = start_spinner("Creating device invitation".into()); - let rep = cmds - .send(invite_new_device::Req { send_email: false }) - .await?; - - let url = match rep { - InviteNewDeviceRep::Ok { token, .. } => ParsecInvitationAddr::new( - device.organization_addr.clone(), - device.organization_id().clone(), - InvitationType::Device, - token, - ) - .to_url(), - rep => { - return Err(anyhow::anyhow!( - "Server refused to create device invitation: {rep:?}" - )); - } - }; + let (token, _email_sent_status) = client + .new_device_invitation(false) + .await + .context("Server refused to create device invitation")?; + + let url = ParsecInvitationAddr::new( + client.organization_addr(), + client.organization_id().clone(), + InvitationType::Device, + token, + ) + .to_url(); handle.stop_with_message(format!("Invitation URL: {YELLOW}{url}{RESET}")); diff --git a/cli/src/commands/invite/greet.rs b/cli/src/commands/invite/greet.rs index d9af5c0cd2d..d0ca70f27b2 100644 --- a/cli/src/commands/invite/greet.rs +++ b/cli/src/commands/invite/greet.rs @@ -1,17 +1,17 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -use std::sync::Arc; - +use anyhow::Context; use libparsec::{ - authenticated_cmds::latest::invite_list::{self, InviteListItem}, + authenticated_cmds::latest::invite_list::InviteListItem, internal::{ DeviceGreetInProgress1Ctx, DeviceGreetInProgress2Ctx, DeviceGreetInProgress3Ctx, - DeviceGreetInProgress4Ctx, DeviceGreetInitialCtx, EventBus, UserGreetInProgress1Ctx, + DeviceGreetInProgress4Ctx, DeviceGreetInitialCtx, UserGreetInProgress1Ctx, UserGreetInProgress2Ctx, UserGreetInProgress3Ctx, UserGreetInProgress4Ctx, UserGreetInitialCtx, }, - AuthenticatedCmds, InvitationToken, + InvitationToken, }; +use libparsec_client::Client; use crate::utils::*; @@ -24,26 +24,17 @@ crate::clap_parser_with_shared_opts_builder!( } ); -pub async fn main(args: Args) -> anyhow::Result<()> { - let Args { - token, - device, - config_dir, - password_stdin, - } = args; - log::trace!( - "Greeting invitation (confdir={}, device={})", - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); +crate::build_main_with_client!(device_greet); - let (cmds, device) = load_cmds(&config_dir, device, password_stdin).await?; +pub async fn device_greet(args: Args, client: &StartedClient) -> anyhow::Result<()> { + let Args { token, .. } = args; + log::trace!("Greeting invitation"); - let invitation = step0(&cmds, token).await?; + let invitation = step0(client, token).await?; match invitation { - InviteListItem::User { .. } => { - let ctx = UserGreetInitialCtx::new(device, Arc::new(cmds), EventBus::default(), token); + InviteListItem::User { token, .. } => { + let ctx = client.start_user_invitation_greet(token); let ctx = step1_user(ctx).await?; let ctx = step2_user(ctx).await?; @@ -51,9 +42,8 @@ pub async fn main(args: Args) -> anyhow::Result<()> { let ctx = step4_user(ctx).await?; step5_user(ctx).await } - InviteListItem::Device { .. } => { - let ctx = - DeviceGreetInitialCtx::new(device, Arc::new(cmds), EventBus::default(), token); + InviteListItem::Device { token, .. } => { + let ctx = client.start_device_invitation_greet(token); let ctx = step1_device(ctx).await?; let ctx = step2_device(ctx).await?; @@ -70,17 +60,12 @@ pub async fn main(args: Args) -> anyhow::Result<()> { /// Step 0: retrieve info async fn step0( - cmds: &AuthenticatedCmds, + client: &Client, invitation_token: InvitationToken, ) -> anyhow::Result { let mut handle = start_spinner("Retrieving invitation info".into()); - let rep = cmds.send(invite_list::Req).await?; - - let invitations = match rep { - invite_list::InviteListRep::Ok { invitations } => invitations, - rep => return Err(anyhow::anyhow!("Server error: {rep:?}")), - }; + let invitations = client.list_invitations().await.context("Server error")?; let invitation = match invitations.into_iter().find(|invitation| match invitation { InviteListItem::User { token, .. } if *token == invitation_token => true, diff --git a/cli/src/commands/invite/list.rs b/cli/src/commands/invite/list.rs index d5a63175d11..73436df0c35 100644 --- a/cli/src/commands/invite/list.rs +++ b/cli/src/commands/invite/list.rs @@ -9,19 +9,11 @@ crate::clap_parser_with_shared_opts_builder!( pub struct Args {} ); -pub async fn main(args: Args) -> anyhow::Result<()> { - let Args { - device, - config_dir, - password_stdin, - } = args; - log::trace!( - "Listing invitations (confdir={}, device={})", - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); +crate::build_main_with_client!(list_invite); + +pub async fn list_invite(_args: Args, client: &StartedClient) -> anyhow::Result<()> { + log::trace!("Listing invitations"); - let client = load_client(&config_dir, device, password_stdin).await?; let mut handle = start_spinner("Listing invitations".into()); let invitations = client.list_invitations().await?; diff --git a/cli/src/commands/invite/shared_recovery.rs b/cli/src/commands/invite/shared_recovery.rs index b0fdf419ccd..b707144b353 100644 --- a/cli/src/commands/invite/shared_recovery.rs +++ b/cli/src/commands/invite/shared_recovery.rs @@ -16,21 +16,13 @@ crate::clap_parser_with_shared_opts_builder!( } ); -pub async fn main(args: Args) -> anyhow::Result<()> { +crate::build_main_with_client!(invite_shared_recovery); + +pub async fn invite_shared_recovery(args: Args, client: &StartedClient) -> anyhow::Result<()> { let Args { - email, - send_email, - device, - config_dir, - password_stdin, + email, send_email, .. } = args; - log::trace!( - "Inviting an user to perform a shared recovery (confdir={}, device={})", - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); - - let client = load_client(&config_dir, device, password_stdin).await?; + log::trace!("Inviting an user to perform a shared recovery"); { let _spinner = start_spinner("Poll server for new certificates".into()); diff --git a/cli/src/commands/invite/user.rs b/cli/src/commands/invite/user.rs index 613f8bbbf51..50ea5c99283 100644 --- a/cli/src/commands/invite/user.rs +++ b/cli/src/commands/invite/user.rs @@ -1,9 +1,7 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -use libparsec::{ - authenticated_cmds::latest::invite_new_user::{self, InviteNewUserRep}, - InvitationType, ParsecInvitationAddr, -}; +use anyhow::Context; +use libparsec::{InvitationType, ParsecInvitationAddr}; use crate::utils::*; @@ -19,44 +17,28 @@ crate::clap_parser_with_shared_opts_builder!( } ); -pub async fn main(args: Args) -> anyhow::Result<()> { +crate::build_main_with_client!(invite_user); + +pub async fn invite_user(args: Args, client: &StartedClient) -> anyhow::Result<()> { let Args { - email, - send_email, - device, - config_dir, - password_stdin, + email, send_email, .. } = args; - log::trace!( - "Inviting an user (confdir={}, device={})", - config_dir.display(), - device.as_deref().unwrap_or("N/A") - ); + log::trace!("Inviting an user"); - let (cmds, device) = load_cmds(&config_dir, device, password_stdin).await?; let mut handle = start_spinner("Creating user invitation".into()); - let rep = cmds - .send(invite_new_user::Req { - claimer_email: email, - send_email, - }) - .await?; - - let url = match rep { - InviteNewUserRep::Ok { token, .. } => ParsecInvitationAddr::new( - device.organization_addr.clone(), - device.organization_id().clone(), - InvitationType::Device, - token, - ) - .to_url(), - rep => { - return Err(anyhow::anyhow!( - "Server refused to create user invitation: {rep:?}" - )); - } - }; + let (token, _sent_email_status) = client + .new_user_invitation(email, send_email) + .await + .context("Server refused to create user invitation")?; + + let url = ParsecInvitationAddr::new( + client.organization_addr().clone(), + client.organization_id().clone(), + InvitationType::Device, + token, + ) + .to_url(); handle.stop_with_message(format!("Invitation URL: {YELLOW}{url}{RESET}")); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 217a77d0099..ce79b79f97e 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,5 +1,5 @@ pub mod commands; -pub mod macro_opts; +pub mod macros; #[cfg(test)] #[path = "../tests/integration/mod.rs"] diff --git a/cli/src/macro_opts.rs b/cli/src/macros.rs similarity index 90% rename from cli/src/macro_opts.rs rename to cli/src/macros.rs index 362fd383af9..1300a71e0e0 100644 --- a/cli/src/macro_opts.rs +++ b/cli/src/macros.rs @@ -1,3 +1,27 @@ +/// This macro builds a main function that takes the `Args` struct defined in the same module. +/// +/// Once called, it will initialize a client using the provided arguments and then call the sub-task and cleanup the client ressource before returning. +#[macro_export] +macro_rules! build_main_with_client { + ($base:ident) => { + pub async fn main(args: Args) -> anyhow::Result<()> { + log::trace!( + "Loading client from {dir} using device {device}", + dir = args.config_dir.display(), + device = args.device.as_deref().unwrap_or("N/A") + ); + let client = + load_client(&args.config_dir, args.device.clone(), args.password_stdin).await?; + + let res = $base(args, client.as_ref()).await; + + client.stop().await; + + res + } + }; +} + /// This macros builds a Clap argument parser by combining the provided structure /// with common CLI options. ///