From b9bc9f501c72835cf58fc242e22299936af4644b Mon Sep 17 00:00:00 2001 From: Simon Popugaev Date: Tue, 4 Jun 2024 15:12:43 +0300 Subject: [PATCH] unify commands --- golem-cli/src/cloud/command.rs | 24 +- golem-cli/src/cloud/factory.rs | 22 +- golem-cli/src/cloud/model.rs | 11 +- golem-cli/src/cloud/service/project.rs | 15 + golem-cli/src/cloud_main.rs | 8 +- golem-cli/src/command.rs | 41 ++ .../src/{cloud => }/command/api_definition.rs | 13 +- .../src/{cloud => }/command/api_deployment.rs | 17 +- .../src/{cloud => }/command/component.rs | 22 +- golem-cli/src/{cloud => }/command/worker.rs | 64 +-- golem-cli/src/factory.rs | 24 +- golem-cli/src/lib.rs | 1 + golem-cli/src/main.rs | 21 +- golem-cli/src/oss/command.rs | 24 +- golem-cli/src/oss/command/api_definition.rs | 106 ----- golem-cli/src/oss/command/api_deployment.rs | 85 ---- golem-cli/src/oss/command/component.rs | 94 ----- golem-cli/src/oss/command/worker.rs | 378 ------------------ golem-cli/src/oss/factory.rs | 14 +- golem-cli/src/oss/model.rs | 2 +- golem-cli/src/service.rs | 1 + golem-cli/src/service/project.rs | 51 +++ 22 files changed, 268 insertions(+), 770 deletions(-) create mode 100644 golem-cli/src/command.rs rename golem-cli/src/{cloud => }/command/api_definition.rs (93%) rename golem-cli/src/{cloud => }/command/api_deployment.rs (85%) rename golem-cli/src/{cloud => }/command/component.rs (86%) rename golem-cli/src/{cloud => }/command/worker.rs (88%) delete mode 100644 golem-cli/src/oss/command/api_definition.rs delete mode 100644 golem-cli/src/oss/command/api_deployment.rs delete mode 100644 golem-cli/src/oss/command/component.rs delete mode 100644 golem-cli/src/oss/command/worker.rs create mode 100644 golem-cli/src/service/project.rs diff --git a/golem-cli/src/cloud/command.rs b/golem-cli/src/cloud/command.rs index 471ec1496..0ebd976e3 100644 --- a/golem-cli/src/cloud/command.rs +++ b/golem-cli/src/cloud/command.rs @@ -13,33 +13,31 @@ // limitations under the License. use crate::cloud::command::account::AccountSubcommand; -use crate::cloud::command::api_definition::ApiDefinitionSubcommand; -use crate::cloud::command::api_deployment::ApiDeploymentSubcommand; use crate::cloud::command::certificate::CertificateSubcommand; use crate::cloud::command::domain::DomainSubcommand; use crate::cloud::command::policy::ProjectPolicySubcommand; use crate::cloud::command::project::ProjectSubcommand; use crate::cloud::command::token::TokenSubcommand; -use crate::cloud::command::worker::WorkerSubcommand; -use crate::cloud::model::{AccountId, ProjectAction, ProjectPolicyId, ProjectRef}; +use crate::cloud::model::{ + AccountId, CloudComponentIdOrName, ProjectAction, ProjectPolicyId, ProjectRef, +}; +use crate::command::api_definition::ApiDefinitionSubcommand; +use crate::command::api_deployment::ApiDeploymentSubcommand; +use crate::command::component::ComponentSubCommand; +use crate::command::worker::WorkerSubcommand; use crate::model::Format; use clap::{Parser, Subcommand}; use clap_verbosity_flag::Verbosity; -use component::ComponentSubCommand; use golem_examples::model::{ExampleName, GuestLanguage, GuestLanguageTier, PackageName}; use std::path::PathBuf; use uuid::Uuid; pub mod account; -pub mod api_definition; -pub mod api_deployment; pub mod certificate; -pub mod component; pub mod domain; pub mod policy; pub mod project; pub mod token; -pub mod worker; #[derive(Subcommand, Debug)] #[command()] @@ -48,14 +46,14 @@ pub enum CloudCommand { #[command()] Component { #[command(subcommand)] - subcommand: ComponentSubCommand, + subcommand: ComponentSubCommand, }, /// Manage Golem workers #[command()] Worker { #[command(subcommand)] - subcommand: WorkerSubcommand, + subcommand: WorkerSubcommand, }, /// Manage accounts @@ -157,14 +155,14 @@ pub enum CloudCommand { #[command()] ApiDefinition { #[command(subcommand)] - subcommand: ApiDefinitionSubcommand, + subcommand: ApiDefinitionSubcommand, }, /// Manage Golem api deployments #[command()] ApiDeployment { #[command(subcommand)] - subcommand: ApiDeploymentSubcommand, + subcommand: ApiDeploymentSubcommand, }, #[command()] diff --git a/golem-cli/src/cloud/factory.rs b/golem-cli/src/cloud/factory.rs index 45fe9c841..831e888ae 100644 --- a/golem-cli/src/cloud/factory.rs +++ b/golem-cli/src/cloud/factory.rs @@ -32,17 +32,18 @@ use crate::cloud::clients::project_grant::{ProjectGrantClient, ProjectGrantClien use crate::cloud::clients::token::{TokenClient, TokenClientLive}; use crate::cloud::clients::worker::WorkerClientLive; use crate::cloud::clients::CloudAuthentication; -use crate::cloud::model::ProjectId; +use crate::cloud::model::{ProjectId, ProjectRef}; use crate::cloud::service::account::{AccountService, AccountServiceLive}; use crate::cloud::service::certificate::{CertificateService, CertificateServiceLive}; use crate::cloud::service::domain::{DomainService, DomainServiceLive}; use crate::cloud::service::grant::{GrantService, GrantServiceLive}; use crate::cloud::service::policy::{ProjectPolicyService, ProjectPolicyServiceLive}; -use crate::cloud::service::project::{ProjectService, ProjectServiceLive}; +use crate::cloud::service::project::{CloudProjectResolver, ProjectService, ProjectServiceLive}; use crate::cloud::service::project_grant::{ProjectGrantService, ProjectGrantServiceLive}; use crate::cloud::service::token::{TokenService, TokenServiceLive}; use crate::factory::{FactoryWithAuth, ServiceFactory}; use crate::model::GolemError; +use crate::service::project::ProjectResolver; use golem_cloud_client::{Context, Security}; use url::Url; @@ -121,7 +122,7 @@ impl CloudServiceFactory { pub fn project_service( &self, auth: &CloudAuthentication, - ) -> Result, GolemError> { + ) -> Result, GolemError> { Ok(Box::new(ProjectServiceLive { account_id: auth.account_id(), client: self.project_client(auth)?, @@ -277,18 +278,31 @@ impl CloudServiceFactory { impl ServiceFactory for CloudServiceFactory { type SecurityContext = CloudAuthentication; + type ProjectRef = ProjectRef; type ProjectContext = ProjectId; fn with_auth( &self, auth: &Self::SecurityContext, - ) -> FactoryWithAuth { + ) -> FactoryWithAuth { FactoryWithAuth { auth: auth.clone(), factory: Box::new(self.clone()), } } + fn project_resolver( + &self, + auth: &Self::SecurityContext, + ) -> Result< + Box + Send + Sync>, + GolemError, + > { + Ok(Box::new(CloudProjectResolver { + service: self.project_service(auth)?, + })) + } + fn component_client( &self, auth: &Self::SecurityContext, diff --git a/golem-cli/src/cloud/model.rs b/golem-cli/src/cloud/model.rs index b3c94f5e7..610a051c1 100644 --- a/golem-cli/src/cloud/model.rs +++ b/golem-cli/src/cloud/model.rs @@ -14,7 +14,7 @@ pub mod text; -use crate::model::{ComponentId, ComponentIdOrName, ComponentName}; +use crate::model::{ComponentId, ComponentName}; use clap::{ArgMatches, Error, FromArgMatches}; use derive_more::{Display, FromStr, Into}; use serde::{Deserialize, Serialize}; @@ -221,15 +221,6 @@ pub enum CloudComponentIdOrName { Name(ComponentName, ProjectRef), } -impl CloudComponentIdOrName { - pub fn split(self) -> (ComponentIdOrName, Option) { - match self { - CloudComponentIdOrName::Id(id) => (ComponentIdOrName::Id(id), None), - CloudComponentIdOrName::Name(name, p) => (ComponentIdOrName::Name(name), Some(p)), - } - } -} - #[derive(Copy, Clone, PartialEq, Eq, Debug, EnumIter, Serialize, Deserialize)] pub enum Role { Admin, diff --git a/golem-cli/src/cloud/service/project.rs b/golem-cli/src/cloud/service/project.rs index fccc5d4a8..29a7f12ec 100644 --- a/golem-cli/src/cloud/service/project.rs +++ b/golem-cli/src/cloud/service/project.rs @@ -15,6 +15,7 @@ use crate::cloud::clients::project::ProjectClient; use crate::cloud::model::{AccountId, ProjectId, ProjectRef}; use crate::model::{GolemError, GolemResult}; +use crate::service::project::ProjectResolver; use async_trait::async_trait; use golem_cloud_client::model::Project; use indoc::formatdoc; @@ -117,3 +118,17 @@ impl ProjectService for ProjectServiceLive { } } } + +pub struct CloudProjectResolver { + pub service: Box, +} + +#[async_trait] +impl ProjectResolver for CloudProjectResolver { + async fn resolve_id_or_default( + &self, + project_ref: ProjectRef, + ) -> Result { + self.service.resolve_id_or_default(project_ref).await + } +} diff --git a/golem-cli/src/cloud_main.rs b/golem-cli/src/cloud_main.rs index 5c8300e9b..3e34bdb5e 100644 --- a/golem-cli/src/cloud_main.rs +++ b/golem-cli/src/cloud_main.rs @@ -95,7 +95,7 @@ async fn async_main(cmd: GolemCloudCommand) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box { + fn split(self) -> (ComponentIdOrName, Option); +} + +impl ComponentRefSplit for ComponentIdOrName { + fn split(self) -> (ComponentIdOrName, Option) { + (self, None) + } +} + +impl ComponentRefSplit for CloudComponentIdOrName { + fn split(self) -> (ComponentIdOrName, Option) { + match self { + CloudComponentIdOrName::Id(id) => (ComponentIdOrName::Id(id), None), + CloudComponentIdOrName::Name(name, p) => (ComponentIdOrName::Name(name), Some(p)), + } + } +} diff --git a/golem-cli/src/cloud/command/api_definition.rs b/golem-cli/src/command/api_definition.rs similarity index 93% rename from golem-cli/src/cloud/command/api_definition.rs rename to golem-cli/src/command/api_definition.rs index b9d6d5a6d..2e7907b45 100644 --- a/golem-cli/src/cloud/command/api_definition.rs +++ b/golem-cli/src/command/api_definition.rs @@ -12,17 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::cloud::model::{ProjectId, ProjectRef}; -use crate::cloud::service::project::ProjectService; use crate::model::{ ApiDefinitionId, ApiDefinitionVersion, GolemError, GolemResult, PathBufOrStdin, }; use crate::service::api_definition::ApiDefinitionService; +use crate::service::project::ProjectResolver; use clap::Subcommand; #[derive(Subcommand, Debug)] #[command()] -pub enum ApiDefinitionSubcommand { +pub enum ApiDefinitionSubcommand { /// Lists all api definitions #[command()] List { @@ -110,11 +109,11 @@ pub enum ApiDefinitionSubcommand { }, } -impl ApiDefinitionSubcommand { - pub async fn handle( +impl ApiDefinitionSubcommand { + pub async fn handle( self, - service: &(dyn ApiDefinitionService + Send + Sync), - projects: &(dyn ProjectService + Send + Sync), + service: &(dyn ApiDefinitionService + Send + Sync), + projects: &(dyn ProjectResolver + Send + Sync), ) -> Result { match self { ApiDefinitionSubcommand::Get { diff --git a/golem-cli/src/cloud/command/api_deployment.rs b/golem-cli/src/command/api_deployment.rs similarity index 85% rename from golem-cli/src/cloud/command/api_deployment.rs rename to golem-cli/src/command/api_deployment.rs index b3b05c107..54463824d 100644 --- a/golem-cli/src/cloud/command/api_deployment.rs +++ b/golem-cli/src/command/api_deployment.rs @@ -12,15 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::cloud::model::{ProjectId, ProjectRef}; -use crate::cloud::service::project::ProjectService; use crate::model::{ApiDefinitionId, ApiDefinitionVersion, GolemError, GolemResult}; use crate::service::api_deployment::ApiDeploymentService; +use crate::service::project::ProjectResolver; use clap::Subcommand; #[derive(Subcommand, Debug)] #[command()] -pub enum ApiDeploymentSubcommand { +pub enum ApiDeploymentSubcommand { /// Create or update deployment #[command()] Deploy { @@ -40,7 +39,7 @@ pub enum ApiDeploymentSubcommand { host: String, #[arg(short, long)] - subdomain: String, // TODO: unify cloud with OSS + subdomain: Option, }, /// Get api deployment @@ -72,11 +71,11 @@ pub enum ApiDeploymentSubcommand { }, } -impl ApiDeploymentSubcommand { - pub async fn handle( +impl ApiDeploymentSubcommand { + pub async fn handle( self, - service: &(dyn ApiDeploymentService + Send + Sync), - projects: &(dyn ProjectService + Send + Sync), + service: &(dyn ApiDeploymentService + Send + Sync), + projects: &(dyn ProjectResolver + Send + Sync), ) -> Result { match self { ApiDeploymentSubcommand::Deploy { @@ -88,7 +87,7 @@ impl ApiDeploymentSubcommand { } => { let project_id = projects.resolve_id_or_default(project_ref).await?; service - .deploy(id, version, host, Some(subdomain), &project_id) + .deploy(id, version, host, subdomain, &project_id) .await } ApiDeploymentSubcommand::Get { site } => service.get(site).await, diff --git a/golem-cli/src/cloud/command/component.rs b/golem-cli/src/command/component.rs similarity index 86% rename from golem-cli/src/cloud/command/component.rs rename to golem-cli/src/command/component.rs index 5523e051a..7cbd7418c 100644 --- a/golem-cli/src/cloud/command/component.rs +++ b/golem-cli/src/command/component.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::cloud::model::{CloudComponentIdOrName, ProjectId, ProjectRef}; -use crate::cloud::service::project::ProjectService; +use crate::command::ComponentRefSplit; use crate::model::{ComponentName, GolemError, GolemResult, PathBufOrStdin}; use crate::service::component::ComponentService; +use crate::service::project::ProjectResolver; use clap::Subcommand; #[derive(Subcommand, Debug)] #[command()] -pub enum ComponentSubCommand { +pub enum ComponentSubCommand { /// Creates a new component with a given name by uploading the component WASM #[command()] Add { @@ -42,7 +42,7 @@ pub enum ComponentSubCommand { Update { /// The component name or identifier to update #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// The WASM file to be used as a new version of the Golem component #[arg(value_name = "component-file", value_hint = clap::ValueHint::FilePath)] @@ -65,7 +65,7 @@ pub enum ComponentSubCommand { Get { /// The Golem component id or name #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// The version of the component #[arg(short = 't', long)] @@ -73,11 +73,15 @@ pub enum ComponentSubCommand { }, } -impl ComponentSubCommand { - pub async fn handle( +impl< + ProjectRef: clap::Args + Send + Sync + 'static, + ComponentRef: ComponentRefSplit + clap::Args, + > ComponentSubCommand +{ + pub async fn handle( self, - service: &(dyn ComponentService + Send + Sync), - projects: &(dyn ProjectService + Send + Sync), + service: &(dyn ComponentService + Send + Sync), + projects: &(dyn ProjectResolver + Send + Sync), ) -> Result { match self { ComponentSubCommand::Add { diff --git a/golem-cli/src/cloud/command/worker.rs b/golem-cli/src/command/worker.rs similarity index 88% rename from golem-cli/src/cloud/command/worker.rs rename to golem-cli/src/command/worker.rs index ce3ebd286..b05fe2ed7 100644 --- a/golem-cli/src/cloud/command/worker.rs +++ b/golem-cli/src/command/worker.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::cloud::model::{CloudComponentIdOrName, ProjectId}; -use crate::cloud::service::project::ProjectService; +use crate::command::ComponentRefSplit; use clap::builder::ValueParser; use clap::Subcommand; use golem_client::model::ScanCursor; @@ -22,17 +21,18 @@ use crate::model::{ Format, GolemError, GolemResult, IdempotencyKey, JsonValueParser, WorkerName, WorkerUpdateMode, }; use crate::parse_key_val; +use crate::service::project::ProjectResolver; use crate::service::worker::WorkerService; #[derive(Subcommand, Debug)] #[command()] -pub enum WorkerSubcommand { +pub enum WorkerSubcommand { /// Creates a new idle worker #[command()] Add { /// The Golem component to use for the worker, identified by either its name or its component ID #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the newly created worker #[arg(short, long)] @@ -56,7 +56,7 @@ pub enum WorkerSubcommand { InvokeAndAwait { /// The Golem component the worker to be invoked belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -95,7 +95,7 @@ pub enum WorkerSubcommand { Invoke { /// The Golem component the worker to be invoked belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -130,7 +130,7 @@ pub enum WorkerSubcommand { Connect { /// The Golem component the worker to be connected to belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -142,7 +142,7 @@ pub enum WorkerSubcommand { Interrupt { /// The Golem component the worker to be interrupted belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -156,7 +156,7 @@ pub enum WorkerSubcommand { SimulatedCrash { /// The Golem component the worker to be crashed belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -168,7 +168,7 @@ pub enum WorkerSubcommand { Delete { /// The Golem component the worker to be deleted belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -180,7 +180,7 @@ pub enum WorkerSubcommand { Get { /// The Golem component the worker to be retrieved belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker #[arg(short, long)] @@ -191,7 +191,7 @@ pub enum WorkerSubcommand { List { /// The Golem component the workers to be retrieved belongs to #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Filter for worker metadata in form of `property op value`. /// @@ -202,9 +202,10 @@ pub enum WorkerSubcommand { /// Position where to start listing, if not provided, starts from the beginning /// - /// It is used to get the next page of results. To get next page, use the cursor returned in the response - #[arg(short = 'P', long)] - cursor: Option, + /// It is used to get the next page of results. To get next page, use the cursor returned in the response. + /// The cursor has the format 'layer/position' where both layer and position are numbers. + #[arg(short = 'P', long, value_parser = parse_cursor)] + cursor: Option, /// Count of listed values, if count is not provided, returns all values #[arg(short = 'n', long)] @@ -219,7 +220,7 @@ pub enum WorkerSubcommand { Update { /// The Golem component of the worker, identified by either its name or its component ID #[command(flatten)] - component_id_or_name: CloudComponentIdOrName, + component_id_or_name: ComponentRef, /// Name of the worker to update #[arg(short, long)] @@ -235,13 +236,16 @@ pub enum WorkerSubcommand { }, } -impl WorkerSubcommand { - pub async fn handle( +impl WorkerSubcommand { + pub async fn handle( self, format: Format, - service: &(dyn WorkerService + Send + Sync), - projects: &(dyn ProjectService + Send + Sync), - ) -> Result { + service: &(dyn WorkerService + Send + Sync), + projects: &(dyn ProjectResolver + Send + Sync), + ) -> Result + where + ComponentRef: ComponentRefSplit, + { match self { WorkerSubcommand::Add { component_id_or_name, @@ -367,10 +371,7 @@ impl WorkerSubcommand { component_id_or_name, filter, count, - cursor.map(|c| ScanCursor { - cursor: c, - layer: 0, - }), // TODO: unify cloud with OSS + cursor, precise, project_id, ) @@ -397,3 +398,16 @@ impl WorkerSubcommand { } } } + +fn parse_cursor(s: &str) -> Result> { + let parts = s.split('/').collect::>(); + + if parts.len() != 2 { + return Err(format!("Invalid cursor format: {}", s).into()); + } + + Ok(ScanCursor { + layer: parts[0].parse()?, + cursor: parts[1].parse()?, + }) +} diff --git a/golem-cli/src/factory.rs b/golem-cli/src/factory.rs index 21e7b330a..c68f436c4 100644 --- a/golem-cli/src/factory.rs +++ b/golem-cli/src/factory.rs @@ -21,6 +21,7 @@ use crate::model::GolemError; use crate::service::api_definition::{ApiDefinitionService, ApiDefinitionServiceLive}; use crate::service::api_deployment::{ApiDeploymentService, ApiDeploymentServiceLive}; use crate::service::component::{ComponentService, ComponentServiceLive}; +use crate::service::project::ProjectResolver; use crate::service::version::{VersionService, VersionServiceLive}; use crate::service::worker::{ ComponentServiceBuilder, WorkerClientBuilder, WorkerService, WorkerServiceLive, @@ -29,12 +30,20 @@ use std::fmt::Display; pub trait ServiceFactory { type SecurityContext: Clone + Send + Sync + 'static; + type ProjectRef: Send + Sync + 'static; type ProjectContext: Display + Send + Sync + 'static; fn with_auth( &self, auth: &Self::SecurityContext, - ) -> FactoryWithAuth; + ) -> FactoryWithAuth; + fn project_resolver( + &self, + auth: &Self::SecurityContext, + ) -> Result< + Box + Send + Sync>, + GolemError, + >; fn component_client( &self, auth: &Self::SecurityContext, @@ -126,25 +135,28 @@ pub trait ServiceFactory { } pub struct FactoryWithAuth< + PR: Send + Sync + 'static, PC: Send + Sync + 'static, SecurityContext: Clone + Send + Sync + 'static, > { pub auth: SecurityContext, pub factory: Box< - dyn ServiceFactory + Send + Sync, + dyn ServiceFactory + + Send + + Sync, >, } -impl WorkerClientBuilder - for FactoryWithAuth +impl WorkerClientBuilder + for FactoryWithAuth { fn build(&self) -> Result, GolemError> { self.factory.worker_client(&self.auth) } } -impl ComponentServiceBuilder - for FactoryWithAuth +impl ComponentServiceBuilder + for FactoryWithAuth { fn build( &self, diff --git a/golem-cli/src/lib.rs b/golem-cli/src/lib.rs index 54a8a0767..899b62606 100644 --- a/golem-cli/src/lib.rs +++ b/golem-cli/src/lib.rs @@ -14,6 +14,7 @@ pub mod clients; pub mod cloud; +pub mod command; pub mod examples; pub mod factory; pub mod model; diff --git a/golem-cli/src/main.rs b/golem-cli/src/main.rs index f7d997272..33af24b96 100644 --- a/golem-cli/src/main.rs +++ b/golem-cli/src/main.rs @@ -88,12 +88,19 @@ async fn async_main(cmd: GolemOssCommand) -> Result<(), Box { subcommand - .handle(factory.component_service(ctx)?.as_ref()) + .handle( + factory.component_service(ctx)?.as_ref(), + factory.project_resolver(ctx)?.as_ref(), + ) .await } OssCommand::Worker { subcommand } => { subcommand - .handle(cmd.format, factory.worker_service(ctx)?.as_ref()) + .handle( + cmd.format, + factory.worker_service(ctx)?.as_ref(), + factory.project_resolver(ctx)?.as_ref(), + ) .await } OssCommand::New { @@ -108,12 +115,18 @@ async fn async_main(cmd: GolemOssCommand) -> Result<(), Box handle_stubgen(subcommand).await, OssCommand::ApiDefinition { subcommand } => { subcommand - .handle(factory.api_definition_service(ctx)?.as_ref()) + .handle( + factory.api_definition_service(ctx)?.as_ref(), + factory.project_resolver(ctx)?.as_ref(), + ) .await } OssCommand::ApiDeployment { subcommand } => { subcommand - .handle(factory.api_deployment_service(ctx)?.as_ref()) + .handle( + factory.api_deployment_service(ctx)?.as_ref(), + factory.project_resolver(ctx)?.as_ref(), + ) .await } }; diff --git a/golem-cli/src/oss/command.rs b/golem-cli/src/oss/command.rs index 03c1c0940..26fbc0cac 100644 --- a/golem-cli/src/oss/command.rs +++ b/golem-cli/src/oss/command.rs @@ -12,20 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::model::Format; -use crate::oss::command::api_definition::ApiDefinitionSubcommand; -use crate::oss::command::api_deployment::ApiDeploymentSubcommand; -use crate::oss::command::component::ComponentSubCommand; -use crate::oss::command::worker::WorkerSubcommand; +use crate::command::api_definition::ApiDefinitionSubcommand; +use crate::command::api_deployment::ApiDeploymentSubcommand; +use crate::command::component::ComponentSubCommand; +use crate::command::worker::WorkerSubcommand; +use crate::model::{ComponentIdOrName, Format}; +use crate::oss::model::OssContext; use clap::{Parser, Subcommand}; use clap_verbosity_flag::Verbosity; use golem_examples::model::{ExampleName, GuestLanguage, GuestLanguageTier, PackageName}; -pub mod api_definition; -pub mod api_deployment; -pub mod component; -pub mod worker; - #[derive(Subcommand, Debug)] #[command()] pub enum OssCommand { @@ -33,14 +29,14 @@ pub enum OssCommand { #[command()] Component { #[command(subcommand)] - subcommand: ComponentSubCommand, + subcommand: ComponentSubCommand, }, /// Manage Golem workers #[command()] Worker { #[command(subcommand)] - subcommand: WorkerSubcommand, + subcommand: WorkerSubcommand, }, /// Create a new Golem component from built-in examples @@ -82,14 +78,14 @@ pub enum OssCommand { #[command()] ApiDefinition { #[command(subcommand)] - subcommand: ApiDefinitionSubcommand, + subcommand: ApiDefinitionSubcommand, }, /// Manage Golem api deployments #[command()] ApiDeployment { #[command(subcommand)] - subcommand: ApiDeploymentSubcommand, + subcommand: ApiDeploymentSubcommand, }, } diff --git a/golem-cli/src/oss/command/api_definition.rs b/golem-cli/src/oss/command/api_definition.rs deleted file mode 100644 index 04e4d89b0..000000000 --- a/golem-cli/src/oss/command/api_definition.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2024 Golem Cloud -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::model::{ - ApiDefinitionId, ApiDefinitionVersion, GolemError, GolemResult, PathBufOrStdin, -}; -use crate::oss::model::OssContext; -use crate::service::api_definition::ApiDefinitionService; -use clap::Subcommand; - -#[derive(Subcommand, Debug)] -#[command()] -pub enum ApiDefinitionSubcommand { - /// Lists all api definitions - #[command()] - List { - /// Api definition id to get all versions. Optional. - #[arg(short, long)] - id: Option, - }, - - /// Creates an api definition - /// - /// Golem API definition file format expected - #[command()] - Add { - /// The Golem API definition file - #[arg(value_hint = clap::ValueHint::FilePath)] - definition: PathBufOrStdin, // TODO: validate exists - }, - - /// Updates an api definition - /// - /// Golem API definition file format expected - #[command()] - Update { - /// The Golem API definition file - #[arg(value_hint = clap::ValueHint::FilePath)] - definition: PathBufOrStdin, // TODO: validate exists - }, - - /// Import OpenAPI file as api definition - #[command()] - Import { - /// The OpenAPI json or yaml file to be used as the api definition - /// - /// Json format expected unless file name ends up in `.yaml` - #[arg(value_hint = clap::ValueHint::FilePath)] - definition: PathBufOrStdin, // TODO: validate exists - }, - - /// Retrieves metadata about an existing api definition - #[command()] - Get { - /// Api definition id - #[arg(short, long)] - id: ApiDefinitionId, - - /// Version of the api definition - #[arg(short = 'V', long)] - version: ApiDefinitionVersion, - }, - - /// Deletes an existing api definition - #[command()] - Delete { - /// Api definition id - #[arg(short, long)] - id: ApiDefinitionId, - - /// Version of the api definition - #[arg(short = 'V', long)] - version: ApiDefinitionVersion, - }, -} - -impl ApiDefinitionSubcommand { - pub async fn handle( - self, - service: &(dyn ApiDefinitionService + Send + Sync), - ) -> Result { - let ctx = &OssContext::EMPTY; - - match self { - ApiDefinitionSubcommand::Get { id, version } => service.get(id, version, ctx).await, - ApiDefinitionSubcommand::Add { definition } => service.add(definition, ctx).await, - ApiDefinitionSubcommand::Update { definition } => service.update(definition, ctx).await, - ApiDefinitionSubcommand::Import { definition } => service.import(definition, ctx).await, - ApiDefinitionSubcommand::List { id } => service.list(id, ctx).await, - ApiDefinitionSubcommand::Delete { id, version } => { - service.delete(id, version, ctx).await - } - } - } -} diff --git a/golem-cli/src/oss/command/api_deployment.rs b/golem-cli/src/oss/command/api_deployment.rs deleted file mode 100644 index 7f253ba73..000000000 --- a/golem-cli/src/oss/command/api_deployment.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2024 Golem Cloud -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::model::{ApiDefinitionId, ApiDefinitionVersion, GolemError, GolemResult}; -use crate::oss::model::OssContext; -use crate::service::api_deployment::ApiDeploymentService; -use clap::Subcommand; - -#[derive(Subcommand, Debug)] -#[command()] -pub enum ApiDeploymentSubcommand { - /// Create or update deployment - #[command()] - Deploy { - /// Api definition id - #[arg(short, long)] - id: ApiDefinitionId, - - /// Api definition version - #[arg(short = 'V', long)] - version: ApiDefinitionVersion, - - #[arg(short = 'H', long)] - host: String, - - #[arg(short, long)] - subdomain: Option, - }, - - /// Get api deployment - #[command()] - Get { - /// Deployment site - #[arg(value_name = "subdomain.host")] - site: String, - }, - - /// List api deployment for api definition - #[command()] - List { - /// Api definition id - #[arg(short, long)] - id: ApiDefinitionId, - }, - - /// Delete api deployment - #[command()] - Delete { - /// Deployment site - #[arg(value_name = "subdomain.host")] - site: String, - }, -} - -impl ApiDeploymentSubcommand { - pub async fn handle( - self, - service: &(dyn ApiDeploymentService + Send + Sync), - ) -> Result { - let ctx = &OssContext::EMPTY; - - match self { - ApiDeploymentSubcommand::Deploy { - id, - version, - host, - subdomain, - } => service.deploy(id, version, host, subdomain, ctx).await, - ApiDeploymentSubcommand::Get { site } => service.get(site).await, - ApiDeploymentSubcommand::List { id } => service.list(id, ctx).await, - ApiDeploymentSubcommand::Delete { site } => service.delete(site).await, - } - } -} diff --git a/golem-cli/src/oss/command/component.rs b/golem-cli/src/oss/command/component.rs deleted file mode 100644 index 1e5f60720..000000000 --- a/golem-cli/src/oss/command/component.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2024 Golem Cloud -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::model::{ComponentIdOrName, ComponentName, GolemError, GolemResult, PathBufOrStdin}; -use crate::oss::model::OssContext; -use crate::service::component::ComponentService; -use clap::Subcommand; - -#[derive(Subcommand, Debug)] -#[command()] -pub enum ComponentSubCommand { - /// Creates a new component with a given name by uploading the component WASM - #[command()] - Add { - /// Name of the newly created component - #[arg(short, long)] - component_name: ComponentName, - - /// The WASM file to be used as a Golem component - #[arg(value_name = "component-file", value_hint = clap::ValueHint::FilePath)] - component_file: PathBufOrStdin, // TODO: validate exists - }, - - /// Updates an existing component by uploading a new version of its WASM - #[command()] - Update { - /// The component name or identifier to update - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// The WASM file to be used as a new version of the Golem component - #[arg(value_name = "component-file", value_hint = clap::ValueHint::FilePath)] - component_file: PathBufOrStdin, // TODO: validate exists - }, - - /// Lists the existing components - #[command()] - List { - /// Optionally look for only components matching a given name - #[arg(short, long)] - component_name: Option, - }, - /// Get component - #[command()] - Get { - /// The Golem component id or name - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// The version of the component - #[arg(short = 't', long)] - version: Option, - }, -} - -impl ComponentSubCommand { - pub async fn handle( - self, - service: &(dyn ComponentService + Send + Sync), - ) -> Result { - match self { - ComponentSubCommand::Add { - component_name, - component_file, - } => service.add(component_name, component_file, None).await, - ComponentSubCommand::Update { - component_id_or_name, - component_file, - } => { - service - .update(component_id_or_name, component_file, None) - .await - } - ComponentSubCommand::List { component_name } => { - service.list(component_name, None).await - } - ComponentSubCommand::Get { - component_id_or_name, - version, - } => service.get(component_id_or_name, version, None).await, - } - } -} diff --git a/golem-cli/src/oss/command/worker.rs b/golem-cli/src/oss/command/worker.rs deleted file mode 100644 index 847ffff2b..000000000 --- a/golem-cli/src/oss/command/worker.rs +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2024 Golem Cloud -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use clap::builder::ValueParser; -use clap::Subcommand; -use golem_client::model::ScanCursor; - -use crate::model::{ - ComponentIdOrName, Format, GolemError, GolemResult, IdempotencyKey, JsonValueParser, - WorkerName, WorkerUpdateMode, -}; -use crate::oss::model::OssContext; -use crate::parse_key_val; -use crate::service::worker::WorkerService; - -#[derive(Subcommand, Debug)] -#[command()] -pub enum WorkerSubcommand { - /// Creates a new idle worker - #[command()] - Add { - /// The Golem component to use for the worker, identified by either its name or its component ID - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the newly created worker - #[arg(short, long)] - worker_name: WorkerName, - - /// List of environment variables (key-value pairs) passed to the worker - #[arg(short, long, value_parser = parse_key_val, value_name = "ENV=VAL")] - env: Vec<(String, String)>, - - /// List of command line arguments passed to the worker - #[arg(value_name = "args")] - args: Vec, - }, - - /// Generates an idempotency key for achieving at-most-one invocation when doing retries - #[command()] - IdempotencyKey {}, - - /// Invokes a worker and waits for its completion - #[command()] - InvokeAndAwait { - /// The Golem component the worker to be invoked belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - - /// A pre-generated idempotency key - #[arg(short = 'k', long)] - idempotency_key: Option, - - /// Name of the function to be invoked - #[arg(short, long)] - function: String, - - /// JSON array representing the parameters to be passed to the function - #[arg(short = 'j', long, value_name = "json", value_parser = ValueParser::new(JsonValueParser), conflicts_with = "wave")] - parameters: Option, - - /// Function parameter in WAVE format - /// - /// You can specify this argument multiple times for multiple parameters. - #[arg( - short = 'p', - long = "param", - value_name = "wave", - conflicts_with = "parameters" - )] - wave: Vec, - - /// Enables the STDIO cal;ing convention, passing the parameters through stdin instead of a typed exported interface - #[arg(short = 's', long, default_value_t = false)] - use_stdio: bool, - }, - - /// Triggers a function invocation on a worker without waiting for its completion - #[command()] - Invoke { - /// The Golem component the worker to be invoked belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - - /// A pre-generated idempotency key - #[arg(short = 'k', long)] - idempotency_key: Option, - - /// Name of the function to be invoked - #[arg(short, long)] - function: String, - - /// JSON array representing the parameters to be passed to the function - #[arg(short = 'j', long, value_name = "json", value_parser = ValueParser::new(JsonValueParser), conflicts_with = "wave")] - parameters: Option, - - /// Function parameter in WAVE format - /// - /// You can specify this argument multiple times for multiple parameters. - #[arg( - short = 'p', - long = "param", - value_name = "wave", - conflicts_with = "parameters" - )] - wave: Vec, - }, - - /// Connect to a worker and live stream its standard output, error and log channels - #[command()] - Connect { - /// The Golem component the worker to be connected to belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - }, - - /// Interrupts a running worker - #[command()] - Interrupt { - /// The Golem component the worker to be interrupted belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - }, - - /// Simulates a crash on a worker for testing purposes. - /// - /// The worker starts recovering and resuming immediately. - #[command()] - SimulatedCrash { - /// The Golem component the worker to be crashed belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - }, - - /// Deletes a worker - #[command()] - Delete { - /// The Golem component the worker to be deleted belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - }, - - /// Retrieves metadata about an existing worker - #[command()] - Get { - /// The Golem component the worker to be retrieved belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker - #[arg(short, long)] - worker_name: WorkerName, - }, - /// Retrieves metadata about an existing workers in a component - #[command()] - List { - /// The Golem component the workers to be retrieved belongs to - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Filter for worker metadata in form of `property op value`. - /// - /// Filter examples: `name = worker-name`, `version >= 0`, `status = Running`, `env.var1 = value`. - /// Can be used multiple times (AND condition is applied between them) - #[arg(short, long)] - filter: Option>, - - /// Position where to start listing, if not provided, starts from the beginning - /// - /// It is used to get the next page of results. To get next page, use the cursor returned in the response. - /// The cursor has the format 'layer/position' where both layer and position are numbers. - #[arg(short = 'P', long, value_parser = parse_cursor)] - cursor: Option, - - /// Count of listed values, if count is not provided, returns all values - #[arg(short = 'n', long)] - count: Option, - - /// Precision in relation to worker status, if true, calculate the most up-to-date status for each worker, default is false - #[arg(short, long)] - precise: Option, - }, - /// Updates a worker - #[command()] - Update { - /// The Golem component of the worker, identified by either its name or its component ID - #[command(flatten)] - component_id_or_name: ComponentIdOrName, - - /// Name of the worker to update - #[arg(short, long)] - worker_name: WorkerName, - - /// Update mode - auto or manual - #[arg(short, long)] - mode: WorkerUpdateMode, - - /// The new version of the updated worker - #[arg(short = 't', long)] - target_version: u64, - }, -} - -impl WorkerSubcommand { - pub async fn handle( - self, - format: Format, - service: &(dyn WorkerService + Send + Sync), - ) -> Result { - match self { - WorkerSubcommand::Add { - component_id_or_name, - worker_name, - env, - args, - } => { - service - .add(component_id_or_name, worker_name, env, args, None) - .await - } - WorkerSubcommand::IdempotencyKey {} => service.idempotency_key().await, - WorkerSubcommand::InvokeAndAwait { - component_id_or_name, - worker_name, - idempotency_key, - function, - parameters, - wave, - use_stdio, - } => { - service - .invoke_and_await( - format, - component_id_or_name, - worker_name, - idempotency_key, - function, - parameters, - wave, - use_stdio, - None, - ) - .await - } - WorkerSubcommand::Invoke { - component_id_or_name, - worker_name, - idempotency_key, - function, - parameters, - wave, - } => { - service - .invoke( - component_id_or_name, - worker_name, - idempotency_key, - function, - parameters, - wave, - None, - ) - .await - } - WorkerSubcommand::Connect { - component_id_or_name, - worker_name, - } => { - service - .connect(component_id_or_name, worker_name, None) - .await - } - WorkerSubcommand::Interrupt { - component_id_or_name, - worker_name, - } => { - service - .interrupt(component_id_or_name, worker_name, None) - .await - } - WorkerSubcommand::SimulatedCrash { - component_id_or_name, - worker_name, - } => { - service - .simulated_crash(component_id_or_name, worker_name, None) - .await - } - WorkerSubcommand::Delete { - component_id_or_name, - worker_name, - } => { - service - .delete(component_id_or_name, worker_name, None) - .await - } - WorkerSubcommand::Get { - component_id_or_name, - worker_name, - } => service.get(component_id_or_name, worker_name, None).await, - WorkerSubcommand::List { - component_id_or_name, - filter, - count, - cursor, - precise, - } => { - service - .list(component_id_or_name, filter, count, cursor, precise, None) - .await - } - WorkerSubcommand::Update { - component_id_or_name, - worker_name, - target_version, - mode, - } => { - service - .update( - component_id_or_name, - worker_name, - target_version, - mode, - None, - ) - .await - } - } - } -} - -fn parse_cursor(s: &str) -> Result> { - let parts = s.split('/').collect::>(); - - if parts.len() != 2 { - return Err(format!("Invalid cursor format: {}", s).into()); - } - - Ok(ScanCursor { - layer: parts[0].parse()?, - cursor: parts[1].parse()?, - }) -} diff --git a/golem-cli/src/oss/factory.rs b/golem-cli/src/oss/factory.rs index 5c0e6d41c..2a520fb47 100644 --- a/golem-cli/src/oss/factory.rs +++ b/golem-cli/src/oss/factory.rs @@ -25,6 +25,7 @@ use crate::oss::clients::component::ComponentClientLive; use crate::oss::clients::health_check::HealthCheckClientLive; use crate::oss::clients::worker::WorkerClientLive; use crate::oss::model::OssContext; +use crate::service::project::{ProjectResolver, ProjectResolverOss}; use golem_client::Context; use url::Url; @@ -61,18 +62,29 @@ impl OssServiceFactory { impl ServiceFactory for OssServiceFactory { type SecurityContext = OssContext; + type ProjectRef = OssContext; type ProjectContext = OssContext; fn with_auth( &self, auth: &Self::SecurityContext, - ) -> FactoryWithAuth { + ) -> FactoryWithAuth { FactoryWithAuth { auth: *auth, factory: Box::new(self.clone()), } } + fn project_resolver( + &self, + _auth: &Self::SecurityContext, + ) -> Result< + Box + Send + Sync>, + GolemError, + > { + Ok(Box::new(ProjectResolverOss::DUMMY)) + } + fn component_client( &self, _auth: &Self::SecurityContext, diff --git a/golem-cli/src/oss/model.rs b/golem-cli/src/oss/model.rs index 5f80d7327..6db1cd566 100644 --- a/golem-cli/src/oss/model.rs +++ b/golem-cli/src/oss/model.rs @@ -14,7 +14,7 @@ use std::fmt::{Display, Formatter}; -#[derive(Debug, Clone, Copy)] +#[derive(clap::Args, Debug, Clone, Copy)] pub struct OssContext {} impl OssContext { diff --git a/golem-cli/src/service.rs b/golem-cli/src/service.rs index 2f01d4c0b..9c027b5e4 100644 --- a/golem-cli/src/service.rs +++ b/golem-cli/src/service.rs @@ -15,5 +15,6 @@ pub mod api_definition; pub mod api_deployment; pub mod component; +pub mod project; pub mod version; pub mod worker; diff --git a/golem-cli/src/service/project.rs b/golem-cli/src/service/project.rs new file mode 100644 index 000000000..e1e10e163 --- /dev/null +++ b/golem-cli/src/service/project.rs @@ -0,0 +1,51 @@ +// Copyright 2024 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::model::GolemError; +use crate::oss::model::OssContext; +use async_trait::async_trait; + +#[async_trait] +pub trait ProjectResolver { + async fn resolve_id_or_default( + &self, + project_ref: ProjectRef, + ) -> Result; + + async fn resolve_id_or_default_opt( + &self, + project_ref: Option, + ) -> Result, GolemError> { + match project_ref { + None => Ok(None), + Some(project_ref) => Ok(Some(self.resolve_id_or_default(project_ref).await?)), + } + } +} + +pub struct ProjectResolverOss {} + +impl ProjectResolverOss { + pub const DUMMY: ProjectResolverOss = ProjectResolverOss {}; +} + +#[async_trait] +impl ProjectResolver for ProjectResolverOss { + async fn resolve_id_or_default( + &self, + _project_ref: OssContext, + ) -> Result { + Ok(OssContext::EMPTY) + } +}