golem_cli/
command.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::command::api::ApiSubcommand;
16use crate::command::app::AppSubcommand;
17use crate::command::cloud::CloudSubcommand;
18use crate::command::component::ComponentSubcommand;
19use crate::command::plugin::PluginSubcommand;
20use crate::command::profile::ProfileSubcommand;
21use crate::command::worker::WorkerSubcommand;
22use crate::config::{BuildProfileName, ProfileName};
23use crate::log::LogColorize;
24use crate::model::{Format, WorkerName};
25use crate::{command_name, version};
26use anyhow::{anyhow, bail, Context as AnyhowContext};
27use chrono::{DateTime, Utc};
28use clap::error::{ContextKind, ContextValue, ErrorKind};
29use clap::{self, Command, CommandFactory, Subcommand};
30use clap::{Args, Parser};
31use clap_verbosity_flag::{ErrorLevel, LogLevel};
32use golem_client::model::ScanCursor;
33use lenient_bool::LenientBool;
34use std::collections::{BTreeSet, HashMap};
35use std::ffi::OsString;
36use std::path::PathBuf;
37use uuid::Uuid;
38
39#[cfg(feature = "server-commands")]
40use crate::command::server::ServerSubcommand;
41use crate::command::shared_args::ComponentOptionalComponentName;
42use crate::error::ShowClapHelpTarget;
43
44/// Golem Command Line Interface
45#[derive(Debug, Parser)]
46#[command(bin_name = command_name(), display_name = command_name(), long_version = version())]
47pub struct GolemCliCommand {
48    #[command(flatten)]
49    pub global_flags: GolemCliGlobalFlags,
50
51    #[clap(subcommand)]
52    pub subcommand: GolemCliSubcommand,
53}
54
55// NOTE: inlined from clap-verbosity-flag, so we can override display order,
56//       check for possible changes when upgrading clap-verbosity-flag
57#[derive(clap::Args, Debug, Clone, Copy, Default)]
58#[command(about = None, long_about = None)]
59pub struct Verbosity<L: LogLevel = ErrorLevel> {
60    #[arg(
61        long,
62        short = 'v',
63        action = clap::ArgAction::Count,
64        global = true,
65        help = L::verbose_help(),
66        long_help = L::verbose_long_help(),
67        display_order = 201
68    )]
69    verbose: u8,
70
71    #[arg(
72        long,
73        short = 'q',
74        action = clap::ArgAction::Count,
75        global = true,
76        help = L::quiet_help(),
77        long_help = L::quiet_long_help(),
78        conflicts_with = "verbose",
79        display_order = 202
80    )]
81    quiet: u8,
82
83    #[arg(skip)]
84    phantom: std::marker::PhantomData<L>,
85}
86
87impl Verbosity {
88    pub fn as_clap_verbosity_flag(self) -> clap_verbosity_flag::Verbosity {
89        clap_verbosity_flag::Verbosity::new(self.verbose, self.quiet)
90    }
91}
92
93#[derive(Debug, Clone, Default, Args)]
94pub struct GolemCliGlobalFlags {
95    /// Output format, defaults to text, unless specified by the selected profile
96    #[arg(long, short, global = true, display_order = 101)]
97    pub format: Option<Format>,
98
99    /// Select Golem profile by name
100    #[arg(long, short, global = true, conflicts_with_all = ["local", "cloud"], display_order = 102)]
101    pub profile: Option<ProfileName>,
102
103    /// Select builtin "local" profile, to use services provided by the "golem server" command
104    #[arg(long, short, global = true, conflicts_with_all = ["profile", "cloud"], display_order = 103
105    )]
106    pub local: bool,
107
108    /// Select builtin "cloud" profile to use Golem Cloud
109    #[arg(long, short, global = true, conflicts_with_all = ["profile", "local"], display_order = 104
110    )]
111    pub cloud: bool,
112
113    /// Custom path to the root application manifest (golem.yaml)
114    #[arg(long, short, global = true, display_order = 105)]
115    pub app_manifest_path: Option<PathBuf>,
116
117    /// Disable automatic searching for application manifests
118    #[arg(long, short = 'A', global = true, display_order = 106)]
119    pub disable_app_manifest_discovery: bool,
120
121    /// Select build profile
122    #[arg(long, short, global = true, display_order = 107)]
123    pub build_profile: Option<BuildProfileName>,
124
125    /// Custom path to the config directory (defaults to $HOME/.golem)
126    #[arg(long, global = true, display_order = 108)]
127    pub config_dir: Option<PathBuf>,
128
129    /// Automatically answer "yes" to any interactive confirm questions
130    #[arg(long, short, global = true, display_order = 109)]
131    pub yes: bool,
132
133    /// Disables filtering of potentially sensitive use values in text mode (e.g. component environment variable values)
134    #[arg(long, global = true, display_order = 110)]
135    pub show_sensitive: bool,
136
137    #[command(flatten)]
138    pub verbosity: Verbosity,
139
140    // The flags below can only be set through env vars, as they are mostly
141    // useful for testing, so we do not want to pollute the flag space with them
142    #[arg(skip)]
143    pub golem_rust_path: Option<PathBuf>,
144
145    #[arg(skip)]
146    pub golem_rust_version: Option<String>,
147
148    #[arg(skip)]
149    pub wasm_rpc_offline: bool,
150
151    #[arg(skip)]
152    pub http_batch_size: Option<u64>,
153
154    #[arg(skip)]
155    pub auth_token: Option<Uuid>,
156
157    #[arg(skip)]
158    pub custom_global_cloud_profile: Option<ProfileName>,
159
160    #[arg(skip)]
161    pub local_server_auto_start: bool,
162}
163
164impl GolemCliGlobalFlags {
165    pub fn with_env_overrides(mut self) -> GolemCliGlobalFlags {
166        if self.profile.is_none() {
167            if let Ok(profile) = std::env::var("GOLEM_PROFILE") {
168                self.profile = Some(profile.into());
169            }
170        }
171
172        if self.app_manifest_path.is_none() {
173            if let Ok(app_manifest_path) = std::env::var("GOLEM_APP_MANIFEST_PATH") {
174                self.app_manifest_path = Some(PathBuf::from(app_manifest_path));
175            }
176        }
177
178        if !self.disable_app_manifest_discovery {
179            if let Ok(disable) = std::env::var("GOLEM_DISABLE_APP_MANIFEST_DISCOVERY") {
180                self.disable_app_manifest_discovery = disable
181                    .parse::<LenientBool>()
182                    .map(|b| b.into())
183                    .unwrap_or_default()
184            }
185        }
186
187        if self.build_profile.is_none() {
188            if let Ok(build_profile) = std::env::var("GOLEM_BUILD_PROFILE") {
189                self.build_profile = Some(build_profile.into());
190            }
191        }
192
193        if let Ok(offline) = std::env::var("GOLEM_WASM_RPC_OFFLINE") {
194            self.wasm_rpc_offline = offline
195                .parse::<LenientBool>()
196                .map(|b| b.into())
197                .unwrap_or_default()
198        }
199
200        if self.golem_rust_path.is_none() {
201            if let Ok(wasm_rpc_path) = std::env::var("GOLEM_RUST_PATH") {
202                self.golem_rust_path = Some(PathBuf::from(wasm_rpc_path));
203            }
204        }
205
206        if self.golem_rust_version.is_none() {
207            if let Ok(version) = std::env::var("GOLEM_RUST_VERSION") {
208                self.golem_rust_version = Some(version);
209            }
210        }
211
212        if let Ok(batch_size) = std::env::var("GOLEM_HTTP_BATCH_SIZE") {
213            self.http_batch_size = Some(
214                batch_size
215                    .parse()
216                    .with_context(|| {
217                        format!("Failed to parse GOLEM_HTTP_BATCH_SIZE: {}", batch_size)
218                    })
219                    .unwrap(),
220            )
221        }
222
223        if let Ok(auth_token) = std::env::var("GOLEM_AUTH_TOKEN") {
224            self.auth_token = Some(
225                auth_token
226                    .parse()
227                    .context("Failed to parse GOLEM_AUTH_TOKEN, expected uuid")
228                    .unwrap(),
229            );
230        }
231
232        if let Ok(default_cloud_profile) = std::env::var("GOLEM_CUSTOM_GLOBAL_CLOUD_PROFILE") {
233            self.custom_global_cloud_profile = Some(default_cloud_profile.into());
234        }
235
236        if let Ok(auto_start) = std::env::var("GOLEM_LOCAL_SERVER_AUTO_START") {
237            self.local_server_auto_start = auto_start
238                .parse::<LenientBool>()
239                .map(|b| b.into())
240                .unwrap_or_default()
241        }
242
243        self
244    }
245
246    pub fn config_dir(&self) -> PathBuf {
247        self.config_dir
248            .clone()
249            .unwrap_or_else(|| dirs::home_dir().unwrap().join(".golem"))
250    }
251
252    pub fn verbosity(&self) -> clap_verbosity_flag::Verbosity {
253        self.verbosity.as_clap_verbosity_flag()
254    }
255}
256
257#[derive(Debug, Default, Parser)]
258#[command(ignore_errors = true)]
259pub struct GolemCliFallbackCommand {
260    #[command(flatten)]
261    pub global_flags: GolemCliGlobalFlags,
262
263    pub positional_args: Vec<String>,
264
265    #[arg(skip)]
266    pub parse_error: Option<clap::Error>,
267}
268
269impl GolemCliFallbackCommand {
270    fn try_parse_from<I, T>(args: I, with_env_overrides: bool) -> Self
271    where
272        I: IntoIterator<Item = T>,
273        T: Into<OsString> + Clone,
274    {
275        let args = args
276            .into_iter()
277            .map(|arg| arg.into())
278            .filter(|arg| arg != "-h" && arg != "--help")
279            .collect::<Vec<OsString>>();
280
281        let mut cmd = <Self as Parser>::try_parse_from(args).unwrap_or_else(|error| {
282            GolemCliFallbackCommand {
283                parse_error: Some(error),
284                ..Self::default()
285            }
286        });
287
288        if with_env_overrides {
289            cmd.global_flags = cmd.global_flags.with_env_overrides();
290        }
291
292        cmd
293    }
294}
295
296impl GolemCliCommand {
297    pub fn try_parse_from_lenient<I, T>(
298        iterator: I,
299        with_env_overrides: bool,
300    ) -> GolemCliCommandParseResult
301    where
302        I: IntoIterator<Item = T>,
303        T: Into<OsString> + Clone,
304    {
305        let args = iterator
306            .into_iter()
307            .map(|arg| arg.into())
308            .collect::<Vec<OsString>>();
309
310        match GolemCliCommand::try_parse_from(&args) {
311            Ok(mut command) => {
312                if with_env_overrides {
313                    command.global_flags = command.global_flags.with_env_overrides()
314                }
315                GolemCliCommandParseResult::FullMatch(command)
316            }
317            Err(error) => {
318                let fallback_command =
319                    GolemCliFallbackCommand::try_parse_from(&args, with_env_overrides);
320
321                let partial_match = match error.kind() {
322                    ErrorKind::DisplayHelp => {
323                        let positional_args = fallback_command
324                            .positional_args
325                            .iter()
326                            .map(|arg| arg.as_ref())
327                            .collect::<Vec<_>>();
328                        match positional_args.as_slice() {
329                            ["app"] => Some(GolemCliCommandPartialMatch::AppHelp),
330                            ["component"] => Some(GolemCliCommandPartialMatch::ComponentHelp),
331                            ["worker"] => Some(GolemCliCommandPartialMatch::WorkerHelp),
332                            _ => None,
333                        }
334                    }
335                    ErrorKind::MissingRequiredArgument => {
336                        error.context().find_map(|context| match context {
337                            (ContextKind::InvalidArg, ContextValue::Strings(args)) => {
338                                Self::match_invalid_arg(
339                                    &fallback_command.positional_args,
340                                    args,
341                                    &Self::invalid_arg_matchers(),
342                                )
343                            }
344                            _ => None,
345                        })
346                    }
347                    ErrorKind::MissingSubcommand
348                    | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => {
349                        let positional_args = fallback_command
350                            .positional_args
351                            .iter()
352                            .map(|arg| arg.as_ref())
353                            .collect::<Vec<_>>();
354                        match positional_args.as_slice() {
355                            ["app"] => Some(GolemCliCommandPartialMatch::AppMissingSubcommandHelp),
356                            ["component"] => {
357                                Some(GolemCliCommandPartialMatch::ComponentMissingSubcommandHelp)
358                            }
359                            _ => None,
360                        }
361                    }
362                    _ => None,
363                };
364
365                match partial_match {
366                    Some(partial_match) => GolemCliCommandParseResult::ErrorWithPartialMatch {
367                        error,
368                        fallback_command,
369                        partial_match,
370                    },
371                    None => GolemCliCommandParseResult::Error {
372                        error,
373                        fallback_command,
374                    },
375                }
376            }
377        }
378    }
379
380    fn invalid_arg_matchers() -> Vec<InvalidArgMatcher> {
381        vec![
382            InvalidArgMatcher {
383                subcommands: vec!["worker", "invoke"],
384                found_positional_args: vec![],
385                missing_positional_arg: "worker_name",
386                to_partial_match: |_| GolemCliCommandPartialMatch::WorkerInvokeMissingWorkerName,
387            },
388            InvalidArgMatcher {
389                subcommands: vec!["worker", "invoke"],
390                found_positional_args: vec!["worker_name"],
391                missing_positional_arg: "function_name",
392                to_partial_match: |args| {
393                    GolemCliCommandPartialMatch::WorkerInvokeMissingFunctionName {
394                        worker_name: args[0].clone().into(),
395                    }
396                },
397            },
398            InvalidArgMatcher {
399                subcommands: vec!["profile", "switch"],
400                missing_positional_arg: "profile_name",
401                found_positional_args: vec![],
402                to_partial_match: |_| GolemCliCommandPartialMatch::ProfileSwitchMissingProfileName,
403            },
404        ]
405    }
406
407    fn match_invalid_arg(
408        positional_args: &[String],
409        error_context_args: &[String],
410        matchers: &[InvalidArgMatcher],
411    ) -> Option<GolemCliCommandPartialMatch> {
412        let command = Self::command();
413
414        let positional_args = positional_args
415            .iter()
416            .map(|str| str.as_str())
417            .collect::<Vec<_>>();
418
419        for matcher in matchers {
420            if positional_args.len() < matcher.subcommands.len() {
421                continue;
422            }
423
424            let missing_arg_error_name =
425                format!("<{}>", matcher.missing_positional_arg.to_uppercase());
426            let missing_args_error_name = format!("{}...", missing_arg_error_name);
427            if !error_context_args.contains(&missing_arg_error_name)
428                && !error_context_args.contains(&missing_args_error_name)
429            {
430                continue;
431            }
432
433            if !positional_args.starts_with(&matcher.subcommands) {
434                continue;
435            }
436
437            let mut command = &command;
438            for subcommand in &matcher.subcommands {
439                command = command.find_subcommand(subcommand).unwrap();
440            }
441            let positional_arg_ids_to_idx = command
442                .get_arguments()
443                .filter(|arg| arg.is_positional())
444                .enumerate()
445                .map(|(idx, arg)| (arg.get_id().to_string(), idx))
446                .collect::<HashMap<_, _>>();
447
448            let mut found_args = Vec::<String>::with_capacity(matcher.found_positional_args.len());
449            for expected_arg_name in &matcher.found_positional_args {
450                let Some(idx) = positional_arg_ids_to_idx.get(*expected_arg_name) else {
451                    break;
452                };
453                let Some(arg_value) = positional_args.get(matcher.subcommands.len() + *idx) else {
454                    break;
455                };
456                found_args.push(arg_value.to_string());
457            }
458            if found_args.len() == matcher.found_positional_args.len() {
459                return Some((matcher.to_partial_match)(found_args));
460            }
461        }
462
463        None
464    }
465}
466
467#[derive(Debug)]
468struct InvalidArgMatcher {
469    pub subcommands: Vec<&'static str>,
470    pub found_positional_args: Vec<&'static str>,
471    pub missing_positional_arg: &'static str,
472    pub to_partial_match: fn(Vec<String>) -> GolemCliCommandPartialMatch,
473}
474
475#[allow(clippy::large_enum_variant)]
476pub enum GolemCliCommandParseResult {
477    FullMatch(GolemCliCommand),
478    ErrorWithPartialMatch {
479        error: clap::Error,
480        fallback_command: GolemCliFallbackCommand,
481        partial_match: GolemCliCommandPartialMatch,
482    },
483    Error {
484        error: clap::Error,
485        fallback_command: GolemCliFallbackCommand,
486    },
487}
488
489#[derive(Debug)]
490pub enum GolemCliCommandPartialMatch {
491    AppHelp,
492    AppMissingSubcommandHelp,
493    ComponentHelp,
494    ComponentMissingSubcommandHelp,
495    WorkerHelp,
496    WorkerInvokeMissingFunctionName { worker_name: WorkerName },
497    WorkerInvokeMissingWorkerName,
498    ProfileSwitchMissingProfileName,
499}
500
501#[derive(Debug, Subcommand)]
502pub enum GolemCliSubcommand {
503    #[command(alias = "application")]
504    /// Build, deploy application
505    App {
506        #[clap(subcommand)]
507        subcommand: AppSubcommand,
508    },
509    /// Build, deploy and manage components
510    Component {
511        #[clap(subcommand)]
512        subcommand: ComponentSubcommand,
513    },
514    /// Invoke and manage workers
515    Worker {
516        #[clap(subcommand)]
517        subcommand: WorkerSubcommand,
518    },
519    /// Manage API gateway objects
520    Api {
521        #[clap(subcommand)]
522        subcommand: ApiSubcommand,
523    },
524    /// Manage plugins
525    Plugin {
526        #[clap(subcommand)]
527        subcommand: PluginSubcommand,
528    },
529    /// Manage global CLI profiles
530    Profile {
531        #[clap(subcommand)]
532        subcommand: ProfileSubcommand,
533    },
534    /// Run and manage the local Golem server
535    #[cfg(feature = "server-commands")]
536    Server {
537        #[clap(subcommand)]
538        subcommand: ServerSubcommand,
539    },
540    /// Manage Golem Cloud accounts and projects
541    Cloud {
542        #[clap(subcommand)]
543        subcommand: CloudSubcommand,
544    },
545    /// Start Rib REPL for a selected component
546    Repl {
547        #[command(flatten)]
548        component_name: ComponentOptionalComponentName,
549        /// Optional component version to use, defaults to latest component version
550        version: Option<u64>,
551    },
552    /// Generate shell completion
553    Completion {
554        /// Selects shell
555        shell: clap_complete::Shell,
556    },
557}
558
559pub mod shared_args {
560    use crate::cloud::AccountId;
561    use crate::model::app::AppBuildStep;
562    use crate::model::{
563        ComponentName, ProjectName, ProjectReference, WorkerName, WorkerUpdateMode,
564    };
565    use clap::Args;
566    use golem_templates::model::GuestLanguage;
567
568    pub type ComponentTemplateName = String;
569    pub type NewWorkerArgument = String;
570    pub type WorkerFunctionArgument = String;
571    pub type WorkerFunctionName = String;
572
573    #[derive(Debug, Args)]
574    pub struct ComponentMandatoryComponentName {
575        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
576        /// Optional component name, if not specified component is selected based on the current directory.
577        /// Accepted formats:
578        ///   - <COMPONENT>
579        ///   - <PROJECT>/<COMPONENT>
580        ///   - <ACCOUNT>/<PROJECT>/<COMPONENT>
581        #[arg(verbatim_doc_comment)]
582        pub component_name: ComponentName,
583    }
584
585    #[derive(Debug, Args)]
586    pub struct ComponentOptionalComponentName {
587        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
588        /// Optional component name, if not specified component is selected based on the current directory.
589        /// Accepted formats:
590        ///   - <COMPONENT>
591        ///   - <PROJECT>/<COMPONENT>
592        ///   - <ACCOUNT>/<PROJECT>/<COMPONENT>
593        #[arg(verbatim_doc_comment)]
594        pub component_name: Option<ComponentName>,
595    }
596
597    #[derive(Debug, Args)]
598    pub struct ComponentOptionalComponentNames {
599        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
600        /// Optional component names, if not specified components are selected based on the current directory
601        /// Accepted formats:
602        ///   - <COMPONENT>
603        ///   - <PROJECT>/<COMPONENT>
604        ///   - <ACCOUNT>/<PROJECT>/<COMPONENT>
605        #[arg(verbatim_doc_comment)]
606        pub component_name: Vec<ComponentName>,
607    }
608
609    #[derive(Debug, Args)]
610    pub struct AppOptionalComponentNames {
611        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
612        /// Optional component names, if not specified all components are selected.
613        /// Accepted formats:
614        ///   - <COMPONENT>
615        ///   - <PROJECT>/<COMPONENT>
616        ///   - <ACCOUNT>/<PROJECT>/<COMPONENT>
617        pub component_name: Vec<ComponentName>,
618    }
619
620    #[derive(Debug, Args)]
621    pub struct LanguageArg {
622        #[clap(long, short)]
623        pub language: GuestLanguage,
624    }
625
626    #[derive(Debug, Args)]
627    pub struct ForceBuildArg {
628        /// When set to true will skip modification time based up-to-date checks, defaults to false
629        #[clap(long, default_value = "false")]
630        pub force_build: bool,
631    }
632
633    #[derive(Debug, Args)]
634    pub struct BuildArgs {
635        /// Select specific build step(s)
636        #[clap(long, short)]
637        pub step: Vec<AppBuildStep>,
638        #[command(flatten)]
639        pub force_build: ForceBuildArg,
640    }
641
642    #[derive(Debug, Args)]
643    pub struct WorkerNameArg {
644        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
645        /// Worker name, accepted formats:
646        ///   - <WORKER>
647        ///   - <COMPONENT>/<WORKER>
648        ///   - <PROJECT>/<COMPONENT>/<WORKER>
649        ///   - <ACCOUNT>/<PROJECT>/<COMPONENT>/<WORKER>
650        #[arg(verbatim_doc_comment)]
651        pub worker_name: WorkerName,
652    }
653
654    #[derive(Debug, Args)]
655    pub struct StreamArgs {
656        /// Hide log levels in stream output
657        #[clap(long, short = 'L')]
658        pub stream_no_log_level: bool,
659        /// Hide timestamp in stream output
660        #[clap(long, short = 'T')]
661        pub stream_no_timestamp: bool,
662    }
663
664    #[derive(Debug, Args)]
665    pub struct UpdateOrRedeployArgs {
666        /// Update existing workers with auto or manual update mode
667        #[clap(long, value_name = "UPDATE_MODE", short, conflicts_with_all = ["redeploy_workers", "redeploy_all"], num_args = 0..=1
668        )]
669        pub update_workers: Option<WorkerUpdateMode>,
670        /// Delete and recreate existing workers
671        #[clap(long, conflicts_with_all = ["update_workers"])]
672        pub redeploy_workers: bool,
673        /// Delete and recreate HTTP API definitions and deployment
674        #[clap(long, conflicts_with_all = ["redeploy_all"])]
675        pub redeploy_http_api: bool,
676        /// Delete and recreate components and HTTP APIs
677        #[clap(long, short, conflicts_with_all = ["update_workers", "redeploy_workers", "redeploy_http_api"]
678        )]
679        pub redeploy_all: bool,
680    }
681
682    impl UpdateOrRedeployArgs {
683        pub fn none() -> Self {
684            UpdateOrRedeployArgs {
685                update_workers: None,
686                redeploy_workers: false,
687                redeploy_http_api: false,
688                redeploy_all: false,
689            }
690        }
691
692        pub fn redeploy_workers(&self, profile_args: &UpdateOrRedeployArgs) -> bool {
693            profile_args.redeploy_all
694                || profile_args.redeploy_workers
695                || self.redeploy_all
696                || self.redeploy_workers
697        }
698
699        pub fn redeploy_http_api(&self, profile_args: &UpdateOrRedeployArgs) -> bool {
700            profile_args.redeploy_all
701                || profile_args.redeploy_http_api
702                || self.redeploy_all
703                || self.redeploy_http_api
704        }
705    }
706
707    #[derive(Debug, Args)]
708    pub struct ProjectArg {
709        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
710        /// Project, accepted formats:
711        ///   - <PROJECT_NAME>
712        ///   - <ACCOUNT_EMAIL>/<PROJECT_NAME>
713        #[arg(verbatim_doc_comment)]
714        pub project: ProjectReference,
715    }
716
717    #[derive(Debug, Args)]
718    pub struct ProjectOptionalFlagArg {
719        // DO NOT ADD EMPTY LINES TO THE DOC COMMENT
720        /// Project, accepted formats:
721        ///   - <PROJECT_NAME>
722        ///   - <ACCOUNT_EMAIL>/<PROJECT_NAME>
723        #[arg(verbatim_doc_comment, long)]
724        pub project: Option<ProjectReference>,
725    }
726
727    #[derive(Debug, Args)]
728    pub struct AccountIdOptionalArg {
729        /// Account ID
730        #[arg(long)]
731        pub account_id: Option<AccountId>,
732    }
733
734    #[derive(clap::Args, Debug, Clone)]
735    pub struct PluginScopeArgs {
736        /// Global scope (plugin available for all components)
737        #[arg(long, conflicts_with_all=["account", "project", "component"])]
738        pub global: bool,
739        /// Account id, optionally specifies the account id for the project name
740        #[arg(long, conflicts_with_all = ["global"])]
741        pub account: Option<String>,
742        /// Project name; Required when component name is used. Without a given component, it defines a project scope.
743        #[arg(long, conflicts_with_all = ["global"])]
744        pub project: Option<ProjectName>,
745        /// Component scope given by the component's name (plugin only available for this component)
746        #[arg(long, conflicts_with_all=["global"])]
747        pub component: Option<ComponentName>,
748    }
749
750    impl PluginScopeArgs {
751        pub fn is_global(&self) -> bool {
752            self.global
753                || (self.account.is_none() && self.project.is_none() && self.component.is_none())
754        }
755    }
756}
757
758pub mod app {
759    use crate::command::shared_args::{
760        AppOptionalComponentNames, BuildArgs, ForceBuildArg, UpdateOrRedeployArgs,
761    };
762    use crate::model::WorkerUpdateMode;
763    use clap::Subcommand;
764    use golem_templates::model::GuestLanguage;
765
766    #[derive(Debug, Subcommand)]
767    pub enum AppSubcommand {
768        /// Create new application
769        New {
770            /// Application folder name where the new application should be created
771            application_name: Option<String>,
772            /// Languages that the application should support
773            language: Vec<GuestLanguage>,
774        },
775        /// Build all or selected components in the application
776        Build {
777            #[command(flatten)]
778            component_name: AppOptionalComponentNames,
779            #[command(flatten)]
780            build: BuildArgs,
781        },
782        /// Deploy all or selected components and HTTP APIs in the application, includes building
783        Deploy {
784            #[command(flatten)]
785            component_name: AppOptionalComponentNames,
786            #[command(flatten)]
787            force_build: ForceBuildArg,
788            #[command(flatten)]
789            update_or_redeploy: UpdateOrRedeployArgs,
790        },
791        /// Clean all components in the application or by selection
792        Clean {
793            #[command(flatten)]
794            component_name: AppOptionalComponentNames,
795        },
796        /// Try to automatically update all existing workers of the application to the latest version
797        UpdateWorkers {
798            #[command(flatten)]
799            component_name: AppOptionalComponentNames,
800            /// Update mode - auto or manual, defaults to "auto"
801            #[arg(long, short, default_value = "auto")]
802            update_mode: WorkerUpdateMode,
803        },
804        /// Redeploy all workers of the application using the latest version
805        RedeployWorkers {
806            #[command(flatten)]
807            component_name: AppOptionalComponentNames,
808        },
809        /// Diagnose possible tooling problems
810        Diagnose {
811            #[command(flatten)]
812            component_name: AppOptionalComponentNames,
813        },
814        /// Run custom command
815        #[clap(external_subcommand)]
816        CustomCommand(Vec<String>),
817    }
818}
819
820pub mod component {
821    use crate::command::component::plugin::ComponentPluginSubcommand;
822    use crate::command::shared_args::{
823        BuildArgs, ComponentOptionalComponentName, ComponentOptionalComponentNames,
824        ComponentTemplateName, ForceBuildArg, UpdateOrRedeployArgs,
825    };
826    use crate::model::app::DependencyType;
827    use crate::model::{ComponentName, WorkerUpdateMode};
828    use clap::Subcommand;
829    use golem_templates::model::PackageName;
830    use std::path::PathBuf;
831    use url::Url;
832
833    #[derive(Debug, Subcommand)]
834    pub enum ComponentSubcommand {
835        /// Create new component in the current application
836        New {
837            /// Template to be used for the new component
838            component_template: Option<ComponentTemplateName>,
839            /// Name of the new component package in 'package:name' form
840            component_name: Option<PackageName>,
841        },
842        /// List or search component templates
843        Templates {
844            /// Optional filter for language or template name
845            filter: Option<String>,
846        },
847        /// Build component(s) based on the current directory or by selection
848        Build {
849            #[command(flatten)]
850            component_name: ComponentOptionalComponentNames,
851            #[command(flatten)]
852            build: BuildArgs,
853        },
854        /// Deploy component(s) and dependent HTTP APIs based on the current directory or by selection
855        Deploy {
856            #[command(flatten)]
857            component_name: ComponentOptionalComponentNames,
858            #[command(flatten)]
859            force_build: ForceBuildArg,
860            #[command(flatten)]
861            update_or_redeploy: UpdateOrRedeployArgs,
862        },
863        /// Clean component(s) based on the current directory or by selection
864        Clean {
865            #[command(flatten)]
866            component_name: ComponentOptionalComponentNames,
867        },
868        /// Add or update a component dependency
869        AddDependency {
870            /// The name of the component to which the dependency should be added
871            #[arg(long)]
872            component_name: Option<ComponentName>,
873            /// The name of the component that will be used as the target component
874            #[arg(long, conflicts_with_all = ["target_component_path", "target_component_url"])]
875            target_component_name: Option<ComponentName>,
876            /// The path to the local component WASM that will be used as the target
877            #[arg(long, conflicts_with_all = ["target_component_name", "target_component_url"])]
878            target_component_path: Option<PathBuf>,
879            /// The URL to the remote component WASM that will be used as the target
880            #[arg(long, conflicts_with_all = ["target_component_name", "target_component_path"])]
881            target_component_url: Option<Url>,
882            /// The type of the dependency, defaults to wasm-rpc
883            #[arg(long)]
884            dependency_type: Option<DependencyType>,
885        },
886        /// List deployed component versions' metadata
887        List {
888            #[command(flatten)]
889            component_name: ComponentOptionalComponentName,
890        },
891        /// Get latest or selected version of deployed component metadata
892        Get {
893            #[command(flatten)]
894            component_name: ComponentOptionalComponentName,
895            /// Optional component version to get
896            version: Option<u64>,
897        },
898        /// Try to automatically update all existing workers of the selected component to the latest version
899        UpdateWorkers {
900            #[command(flatten)]
901            component_name: ComponentOptionalComponentName,
902            /// Update mode - auto or manual, defaults to "auto"
903            #[arg(long, short, default_value_t = WorkerUpdateMode::Automatic)]
904            update_mode: WorkerUpdateMode,
905        },
906        /// Redeploy all workers of the selected component using the latest version
907        RedeployWorkers {
908            #[command(flatten)]
909            component_name: ComponentOptionalComponentName,
910        },
911        /// Manage component plugin installations
912        Plugin {
913            #[command(subcommand)]
914            subcommand: ComponentPluginSubcommand,
915        },
916        /// Diagnose possible tooling problems
917        Diagnose {
918            #[command(flatten)]
919            component_name: ComponentOptionalComponentNames,
920        },
921    }
922
923    pub mod plugin {
924        use crate::command::parse_key_val;
925        use crate::command::shared_args::ComponentOptionalComponentName;
926        use clap::Subcommand;
927        use golem_common::base_model::PluginInstallationId;
928
929        #[derive(Debug, Subcommand)]
930        pub enum ComponentPluginSubcommand {
931            /// Install a plugin for selected component
932            Install {
933                #[command(flatten)]
934                component_name: ComponentOptionalComponentName,
935                /// The plugin to install
936                #[arg(long)]
937                plugin_name: String,
938                /// The version of the plugin to install
939                #[arg(long)]
940                plugin_version: String,
941                /// Priority of the plugin - largest priority is applied first
942                #[arg(long)]
943                priority: i32,
944                /// List of parameters (key-value pairs) passed to the plugin
945                #[arg(long, value_parser = parse_key_val, value_name = "KEY=VAL")]
946                param: Vec<(String, String)>,
947            },
948            /// Get the installed plugins of the component
949            Get {
950                #[command(flatten)]
951                component_name: ComponentOptionalComponentName,
952                /// The version of the component
953                version: Option<u64>,
954            },
955            /// Update component plugin
956            Update {
957                /// The component to update the plugin for
958                #[command(flatten)]
959                component_name: ComponentOptionalComponentName,
960                /// Installation id of the plugin to update
961                #[arg(long)]
962                installation_id: PluginInstallationId,
963                /// Updated priority of the plugin - largest priority is applied first
964                #[arg(long)]
965                priority: i32,
966                /// Updated list of parameters (key-value pairs) passed to the plugin
967                #[arg(long, value_parser = parse_key_val, value_name = "KEY=VAL")]
968                param: Vec<(String, String)>,
969            },
970            /// Uninstall a plugin for selected component
971            Uninstall {
972                /// The component to uninstall the plugin from
973                #[command(flatten)]
974                component_name: ComponentOptionalComponentName,
975                /// Installation id of the plugin to uninstall
976                #[arg(long)]
977                installation_id: PluginInstallationId,
978            },
979        }
980    }
981}
982
983pub mod worker {
984    use crate::command::parse_cursor;
985    use crate::command::parse_key_val;
986    use crate::command::shared_args::{
987        ComponentOptionalComponentName, NewWorkerArgument, StreamArgs, WorkerFunctionArgument,
988        WorkerFunctionName, WorkerNameArg,
989    };
990    use crate::model::{IdempotencyKey, WorkerUpdateMode};
991    use clap::Subcommand;
992    use golem_client::model::ScanCursor;
993
994    #[derive(Debug, Subcommand)]
995    pub enum WorkerSubcommand {
996        /// Create new worker
997        New {
998            #[command(flatten)]
999            worker_name: WorkerNameArg,
1000            /// Worker arguments
1001            arguments: Vec<NewWorkerArgument>,
1002            /// Worker environment variables
1003            #[arg(short, long, value_parser = parse_key_val, value_name = "ENV=VAL")]
1004            env: Vec<(String, String)>,
1005        },
1006        // TODO: json args
1007        /// Invoke (or enqueue invocation for) worker
1008        Invoke {
1009            #[command(flatten)]
1010            worker_name: WorkerNameArg,
1011            /// Worker function name to invoke
1012            function_name: WorkerFunctionName,
1013            /// Worker function arguments in WAVE format
1014            arguments: Vec<WorkerFunctionArgument>,
1015            /// Enqueue invocation, and do not wait for it
1016            #[clap(long, short)]
1017            enqueue: bool,
1018            /// Set idempotency key for the call, use "-" for auto generated key
1019            #[clap(long, short)]
1020            idempotency_key: Option<IdempotencyKey>,
1021            #[clap(long, short)]
1022            /// Connect to the worker before invoke (the worker must already exist)
1023            /// and live stream its standard output, error and log channels
1024            stream: bool,
1025            #[command(flatten)]
1026            stream_args: StreamArgs,
1027        },
1028        /// Get worker metadata
1029        Get {
1030            #[command(flatten)]
1031            worker_name: WorkerNameArg,
1032        },
1033        /// Deletes a worker
1034        Delete {
1035            #[command(flatten)]
1036            worker_name: WorkerNameArg,
1037        },
1038        /// List worker metadata
1039        List {
1040            #[command(flatten)]
1041            component_name: ComponentOptionalComponentName,
1042            /// Filter for worker metadata in form of `property op value`.
1043            ///
1044            /// Filter examples: `name = worker-name`, `version >= 0`, `status = Running`, `env.var1 = value`.
1045            /// Can be used multiple times (AND condition is applied between them)
1046            #[arg(long)]
1047            filter: Vec<String>,
1048            /// Cursor position, if not provided, starts from the beginning.
1049            ///
1050            /// Cursor can be used to get the next page of results, use the cursor returned
1051            /// in the previous response.
1052            /// The cursor has the format 'layer/position' where both layer and position are numbers.
1053            #[arg(long, short, value_parser = parse_cursor)]
1054            scan_cursor: Option<ScanCursor>,
1055            /// The maximum the number of returned workers, returns all values is not specified.
1056            /// When multiple component is selected, then the limit it is applied separately
1057            #[arg(long, short)]
1058            max_count: Option<u64>,
1059            /// When set to true it queries for most up-to-date status for each worker, default is false
1060            #[arg(long, default_value_t = false)]
1061            precise: bool,
1062        },
1063        /// Connect to a worker and live stream its standard output, error and log channels
1064        Stream {
1065            #[command(flatten)]
1066            worker_name: WorkerNameArg,
1067            #[command(flatten)]
1068            stream_args: StreamArgs,
1069        },
1070        /// Updates a worker
1071        Update {
1072            #[command(flatten)]
1073            worker_name: WorkerNameArg,
1074            /// Update mode - auto or manual (default is auto)
1075            mode: Option<WorkerUpdateMode>,
1076            /// The new version of the updated worker (default is the latest version)
1077            target_version: Option<u64>,
1078        },
1079        /// Interrupts a running worker
1080        Interrupt {
1081            #[command(flatten)]
1082            worker_name: WorkerNameArg,
1083        },
1084        /// Resume an interrupted worker
1085        Resume {
1086            #[command(flatten)]
1087            worker_name: WorkerNameArg,
1088        },
1089        /// Simulates a crash on a worker for testing purposes.
1090        ///
1091        /// The worker starts recovering and resuming immediately.
1092        SimulateCrash {
1093            #[command(flatten)]
1094            worker_name: WorkerNameArg,
1095        },
1096        /// Queries and dumps a worker's full oplog
1097        Oplog {
1098            #[command(flatten)]
1099            worker_name: WorkerNameArg,
1100            /// Index of the first oplog entry to get. If missing, the whole oplog is returned
1101            #[arg(long, conflicts_with = "query")]
1102            from: Option<u64>,
1103            /// Lucene query to look for oplog entries. If missing, the whole oplog is returned
1104            #[arg(long, conflicts_with = "from")]
1105            query: Option<String>,
1106        },
1107        /// Reverts a worker by undoing its last recorded operations
1108        Revert {
1109            #[command(flatten)]
1110            worker_name: WorkerNameArg,
1111            /// Revert by oplog index
1112            #[arg(long, conflicts_with = "number_of_invocations")]
1113            last_oplog_index: Option<u64>,
1114            /// Revert by number of invocations
1115            #[arg(long, conflicts_with = "last_oplog_index")]
1116            number_of_invocations: Option<u64>,
1117        },
1118        /// Cancels an enqueued invocation if it has not started yet
1119        CancelInvocation {
1120            #[command(flatten)]
1121            worker_name: WorkerNameArg,
1122            /// Idempotency key of the invocation to be cancelled
1123            idempotency_key: IdempotencyKey,
1124        },
1125    }
1126}
1127
1128pub mod api {
1129    use crate::command::api::cloud::ApiCloudSubcommand;
1130    use crate::command::api::definition::ApiDefinitionSubcommand;
1131    use crate::command::api::deployment::ApiDeploymentSubcommand;
1132    use crate::command::api::security_scheme::ApiSecuritySchemeSubcommand;
1133    use crate::command::shared_args::UpdateOrRedeployArgs;
1134    use clap::Subcommand;
1135
1136    #[derive(Debug, Subcommand)]
1137    pub enum ApiSubcommand {
1138        /// Deploy API Definitions and Deployments
1139        Deploy {
1140            #[command(flatten)]
1141            update_or_redeploy: UpdateOrRedeployArgs,
1142        },
1143        /// Manage API definitions
1144        Definition {
1145            #[clap(subcommand)]
1146            subcommand: ApiDefinitionSubcommand,
1147        },
1148        /// Manage API deployments
1149        Deployment {
1150            #[clap(subcommand)]
1151            subcommand: ApiDeploymentSubcommand,
1152        },
1153        /// Manage API Security Schemes
1154        SecurityScheme {
1155            #[clap(subcommand)]
1156            subcommand: ApiSecuritySchemeSubcommand,
1157        },
1158        /// Manage API Cloud Domains and Certificates
1159        Cloud {
1160            #[clap(subcommand)]
1161            subcommand: ApiCloudSubcommand,
1162        },
1163    }
1164
1165    pub mod definition {
1166        use crate::command::shared_args::{ProjectOptionalFlagArg, UpdateOrRedeployArgs};
1167        use crate::model::api::{ApiDefinitionId, ApiDefinitionVersion};
1168        use crate::model::app::HttpApiDefinitionName;
1169        use clap::Subcommand;
1170
1171        #[derive(Debug, Subcommand)]
1172        pub enum ApiDefinitionSubcommand {
1173            /// Deploy API Definitions and required components
1174            Deploy {
1175                /// API definition to deploy, if not specified, all definitions are deployed
1176                http_api_definition_name: Option<HttpApiDefinitionName>,
1177                #[command(flatten)]
1178                update_or_redeploy: UpdateOrRedeployArgs,
1179            },
1180            /// Retrieves metadata about an existing API definition
1181            Get {
1182                #[command(flatten)]
1183                project: ProjectOptionalFlagArg,
1184                /// API definition id
1185                #[arg(short, long)]
1186                id: ApiDefinitionId,
1187                /// Version of the api definition
1188                #[arg(long)]
1189                version: ApiDefinitionVersion,
1190            },
1191            /// Lists all API definitions
1192            List {
1193                #[command(flatten)]
1194                project: ProjectOptionalFlagArg,
1195                /// API definition id to get all versions. Optional.
1196                #[arg(short, long)]
1197                id: Option<ApiDefinitionId>,
1198            },
1199            /// Deletes an existing API definition
1200            Delete {
1201                #[command(flatten)]
1202                project: ProjectOptionalFlagArg,
1203                /// API definition id
1204                #[arg(short, long)]
1205                id: ApiDefinitionId,
1206                /// Version of the api definition
1207                #[arg(long)]
1208                version: ApiDefinitionVersion,
1209            },
1210        }
1211    }
1212
1213    pub mod deployment {
1214        use crate::command::shared_args::{ProjectOptionalFlagArg, UpdateOrRedeployArgs};
1215        use crate::model::api::ApiDefinitionId;
1216        use clap::Subcommand;
1217
1218        #[derive(Debug, Subcommand)]
1219        pub enum ApiDeploymentSubcommand {
1220            /// Deploy API Deployments
1221            Deploy {
1222                /// Host or site to deploy, if not defined, all deployments will be deployed
1223                host_or_site: Option<String>,
1224                #[command(flatten)]
1225                update_or_redeploy: UpdateOrRedeployArgs,
1226            },
1227            /// Get API deployment
1228            Get {
1229                #[command(flatten)]
1230                project: ProjectOptionalFlagArg,
1231                /// Deployment site
1232                #[arg(value_name = "subdomain.host")]
1233                site: String,
1234            },
1235            /// List API deployment for API definition
1236            List {
1237                #[command(flatten)]
1238                project: ProjectOptionalFlagArg,
1239                /// API definition id
1240                definition: Option<ApiDefinitionId>,
1241            },
1242            /// Delete api deployment
1243            Delete {
1244                #[command(flatten)]
1245                project: ProjectOptionalFlagArg,
1246                /// Deployment site
1247                #[arg(value_name = "subdomain.host")]
1248                site: String,
1249            },
1250        }
1251    }
1252
1253    pub mod security_scheme {
1254        use crate::command::shared_args::ProjectOptionalFlagArg;
1255        use crate::model::api::IdentityProviderType;
1256        use clap::Subcommand;
1257
1258        #[derive(Debug, Subcommand)]
1259        pub enum ApiSecuritySchemeSubcommand {
1260            /// Create API Security Scheme
1261            Create {
1262                #[command(flatten)]
1263                project: ProjectOptionalFlagArg,
1264                /// Security Scheme ID
1265                security_scheme_id: String,
1266                /// Security Scheme provider (Google, Facebook, Gitlab, Microsoft)
1267                #[arg(long)]
1268                provider_type: IdentityProviderType,
1269                /// Security Scheme client ID
1270                #[arg(long)]
1271                client_id: String,
1272                /// Security Scheme client secret
1273                #[arg(long)]
1274                client_secret: String,
1275                #[arg(long)]
1276                /// Security Scheme Scopes, can be defined multiple times
1277                scope: Vec<String>,
1278                #[arg(long)]
1279                /// Security Scheme redirect URL
1280                redirect_url: String,
1281            },
1282
1283            /// Get API security
1284            Get {
1285                #[command(flatten)]
1286                project: ProjectOptionalFlagArg,
1287                /// Security Scheme ID
1288                security_scheme_id: String,
1289            },
1290        }
1291    }
1292
1293    pub mod cloud {
1294        use crate::command::api::cloud::certificate::ApiCertificateSubcommand;
1295        use crate::command::api::cloud::domain::ApiDomainSubcommand;
1296        use clap::Subcommand;
1297
1298        #[derive(Debug, Subcommand)]
1299        pub enum ApiCloudSubcommand {
1300            /// Manage Cloud API Domains
1301            Domain {
1302                #[clap(subcommand)]
1303                subcommand: ApiDomainSubcommand,
1304            },
1305            /// Manage Cloud API Certificates
1306            Certificate {
1307                #[clap(subcommand)]
1308                subcommand: ApiCertificateSubcommand,
1309            },
1310        }
1311
1312        pub mod domain {
1313            use crate::command::shared_args::ProjectArg;
1314            use clap::Subcommand;
1315
1316            #[derive(Debug, Subcommand)]
1317            pub enum ApiDomainSubcommand {
1318                /// Retrieves metadata about an existing domain
1319                Get {
1320                    #[clap(flatten)]
1321                    project: ProjectArg,
1322                },
1323                /// Add new domain
1324                New {
1325                    #[clap(flatten)]
1326                    project: ProjectArg,
1327                    /// Domain name
1328                    domain_name: String,
1329                },
1330                /// Delete an existing domain
1331                Delete {
1332                    #[clap(flatten)]
1333                    project: ProjectArg,
1334                    /// Domain name
1335                    domain_name: String,
1336                },
1337            }
1338        }
1339
1340        pub mod certificate {
1341            use crate::command::shared_args::ProjectArg;
1342            use crate::model::PathBufOrStdin;
1343            use clap::Subcommand;
1344            use uuid::Uuid;
1345
1346            #[derive(Debug, Subcommand)]
1347            pub enum ApiCertificateSubcommand {
1348                /// Retrieves metadata about an existing certificate
1349                Get {
1350                    #[clap(flatten)]
1351                    project: ProjectArg,
1352                    /// Certificate ID
1353                    certificate_id: Option<Uuid>,
1354                },
1355                /// Create new certificate
1356                New {
1357                    #[clap(flatten)]
1358                    project: ProjectArg,
1359                    /// Domain name
1360                    #[arg(short, long)]
1361                    domain_name: String,
1362                    /// Certificate
1363                    #[arg(long, value_hint = clap::ValueHint::FilePath)]
1364                    certificate_body: PathBufOrStdin,
1365                    /// Certificate private key
1366                    #[arg(long, value_hint = clap::ValueHint::FilePath)]
1367                    certificate_private_key: PathBufOrStdin,
1368                },
1369                /// Delete an existing certificate
1370                #[command()]
1371                Delete {
1372                    #[clap(flatten)]
1373                    project: ProjectArg,
1374                    /// Certificate ID
1375                    certificate_id: Uuid,
1376                },
1377            }
1378        }
1379    }
1380}
1381
1382pub mod plugin {
1383    use crate::command::shared_args::PluginScopeArgs;
1384    use crate::model::PathBufOrStdin;
1385    use clap::Subcommand;
1386
1387    #[derive(Debug, Subcommand)]
1388    pub enum PluginSubcommand {
1389        /// List component for the select scope
1390        List {
1391            /// The scope to list components from
1392            #[command(flatten)]
1393            scope: PluginScopeArgs,
1394        },
1395        /// Get information about a registered plugin
1396        Get {
1397            /// Plugin name
1398            plugin_name: String,
1399            /// Plugin version
1400            version: String,
1401        },
1402        /// Register a new plugin
1403        Register {
1404            #[command(flatten)]
1405            scope: PluginScopeArgs,
1406            /// Path to the plugin manifest JSON or '-' to use STDIN
1407            manifest: PathBufOrStdin,
1408        },
1409        /// Unregister a plugin
1410        Unregister {
1411            /// Plugin name
1412            plugin_name: String,
1413            /// Plugin version
1414            version: String,
1415        },
1416    }
1417}
1418
1419pub mod profile {
1420    use crate::command::profile::config::ProfileConfigSubcommand;
1421    use crate::config::{ProfileKind, ProfileName};
1422    use crate::model::Format;
1423    use clap::Subcommand;
1424    use url::Url;
1425
1426    #[allow(clippy::large_enum_variant)]
1427    #[derive(Debug, Subcommand)]
1428    pub enum ProfileSubcommand {
1429        /// Create new global profile, call without <PROFILE_NAME> for interactive setup
1430        New {
1431            /// Profile kind
1432            profile_kind: ProfileKind,
1433            /// Name of the newly created profile
1434            name: Option<ProfileName>,
1435            /// Switch to the profile after creation
1436            #[arg(long)]
1437            set_active: bool,
1438            /// URL of Golem Component service
1439            #[arg(long)]
1440            component_url: Option<Url>,
1441            /// URL of Golem Worker service, if not provided defaults to component-url
1442            #[arg(long)]
1443            worker_url: Option<Url>,
1444            /// URL of Golem Cloud service, if not provided defaults to component-url
1445            #[arg(long)]
1446            cloud_url: Option<Url>,
1447            /// Default output format
1448            #[arg(long, default_value_t = Format::Text)]
1449            default_format: Format,
1450            /// Accept invalid certificates.
1451            ///
1452            /// Disables certificate validation.
1453            /// Warning! Any certificate will be trusted for use.
1454            /// This includes expired certificates.
1455            /// This introduces significant vulnerabilities, and should only be used as a last resort.
1456            #[arg(long, hide = true)]
1457            allow_insecure: bool,
1458        },
1459        /// List global profiles
1460        List,
1461        /// Set the active global default profile
1462        Switch {
1463            /// Profile name to switch to
1464            profile_name: ProfileName,
1465        },
1466        /// Show global profile details
1467        Get {
1468            /// Name of profile to show, shows active profile if not specified.
1469            profile_name: Option<ProfileName>,
1470        },
1471        /// Remove global profile
1472        Delete {
1473            /// Profile name to delete
1474            profile_name: ProfileName,
1475        },
1476        /// Configure global profile
1477        Config {
1478            /// Profile name
1479            profile_name: ProfileName,
1480            #[command(subcommand)]
1481            subcommand: ProfileConfigSubcommand,
1482        },
1483    }
1484
1485    pub mod config {
1486        use crate::model::Format;
1487        use clap::Subcommand;
1488
1489        #[derive(Debug, Subcommand)]
1490        pub enum ProfileConfigSubcommand {
1491            /// Set default output format for the requested profile
1492            SetFormat {
1493                /// CLI output format
1494                format: Format,
1495            },
1496        }
1497    }
1498}
1499
1500pub mod cloud {
1501    use crate::command::cloud::account::AccountSubcommand;
1502    use crate::command::cloud::project::ProjectSubcommand;
1503    use crate::command::cloud::token::TokenSubcommand;
1504    use clap::Subcommand;
1505
1506    #[derive(Debug, Subcommand)]
1507    pub enum CloudSubcommand {
1508        /// Manage Cloud Projects
1509        Project {
1510            #[clap(subcommand)]
1511            subcommand: ProjectSubcommand,
1512        },
1513        /// Manage Cloud Account
1514        Account {
1515            #[clap(subcommand)]
1516            subcommand: AccountSubcommand,
1517        },
1518        /// Manage Cloud Tokens
1519        Token {
1520            #[clap(subcommand)]
1521            subcommand: TokenSubcommand,
1522        },
1523    }
1524
1525    pub mod token {
1526        use crate::command::parse_instant;
1527        use crate::model::TokenId;
1528        use chrono::{DateTime, Utc};
1529        use clap::Subcommand;
1530
1531        #[derive(Debug, Subcommand)]
1532        pub enum TokenSubcommand {
1533            /// List tokens
1534            List,
1535            /// Create new token
1536            New {
1537                /// Expiration date of the generated token
1538                #[arg(long, value_parser = parse_instant, default_value = "2100-01-01T00:00:00Z")]
1539                expires_at: DateTime<Utc>,
1540            },
1541            /// Delete an existing token
1542            Delete {
1543                /// Token ID
1544                token_id: TokenId,
1545            },
1546        }
1547    }
1548
1549    pub mod account {
1550        use crate::command::cloud::account::grant::GrantSubcommand;
1551        use crate::command::shared_args::AccountIdOptionalArg;
1552        use clap::Subcommand;
1553
1554        #[derive(Debug, Subcommand)]
1555        pub enum AccountSubcommand {
1556            /// Get information about the account
1557            Get {
1558                #[command(flatten)]
1559                account_id: AccountIdOptionalArg,
1560            },
1561            /// Update some information about the account
1562            Update {
1563                #[command(flatten)]
1564                account_id: AccountIdOptionalArg,
1565                /// Set the account's name
1566                account_name: Option<String>,
1567                /// Set the account's email address
1568                account_email: Option<String>,
1569            },
1570            /// Add a new account
1571            New {
1572                /// The new account's name
1573                account_name: String,
1574                /// The new account's email address
1575                account_email: String,
1576            },
1577            /// Delete the account
1578            Delete {
1579                #[command(flatten)]
1580                account_id: AccountIdOptionalArg,
1581            },
1582            /// Manage the account roles
1583            Grant {
1584                #[command(subcommand)]
1585                subcommand: GrantSubcommand,
1586            },
1587        }
1588
1589        pub mod grant {
1590            use crate::command::shared_args::AccountIdOptionalArg;
1591            use crate::model::Role;
1592            use clap::Subcommand;
1593
1594            #[derive(Subcommand, Debug)]
1595            pub enum GrantSubcommand {
1596                /// Get the roles granted to the account
1597                Get {
1598                    #[command(flatten)]
1599                    account_id: AccountIdOptionalArg,
1600                },
1601                /// Grant a new role to the account
1602                New {
1603                    #[command(flatten)]
1604                    account_id: AccountIdOptionalArg,
1605                    /// The role to be granted
1606                    role: Role,
1607                },
1608                /// Remove a role from the account
1609                Delete {
1610                    #[command(flatten)]
1611                    account_id: AccountIdOptionalArg,
1612                    /// The role to be deleted
1613                    role: Role,
1614                },
1615            }
1616        }
1617    }
1618
1619    pub mod project {
1620
1621        use crate::command::cloud::project::plugin::ProjectPluginSubcommand;
1622        use crate::command::cloud::project::policy::PolicySubcommand;
1623        use crate::model::{ProjectAction, ProjectName, ProjectPolicyId, ProjectReference};
1624        use clap::Subcommand;
1625
1626        #[derive(clap::Args, Debug)]
1627        #[group(required = true, multiple = false)]
1628        pub struct ProjectActionsOrPolicyId {
1629            /// The sharing policy's identifier. If not provided, use `--action` instead
1630            #[arg(long, required = true, group = "project_actions_or_policy")]
1631            pub policy_id: Option<ProjectPolicyId>,
1632            /// A list of actions to be granted to the recipient account. If not provided, use `--policy-id` instead
1633            #[arg(long, required = true, group = "project_actions_or_policy")]
1634            pub action: Option<Vec<ProjectAction>>,
1635        }
1636
1637        #[derive(Debug, Subcommand)]
1638        pub enum ProjectSubcommand {
1639            /// Create new project
1640            New {
1641                /// The new project's name
1642                project_name: ProjectName,
1643                /// The new project's description
1644                #[arg(short, long)]
1645                description: Option<String>,
1646            },
1647            /// Lists existing projects
1648            List {
1649                /// Optionally filter projects by name
1650                project_name: Option<ProjectName>,
1651            },
1652            /// Gets the default project which is used when no explicit project is specified
1653            GetDefault,
1654            /// Share a project with another account
1655            Grant {
1656                /// The project to be shared
1657                project_reference: ProjectReference,
1658                /// Email of the user account the project will be shared with
1659                recipient_email: String,
1660                #[command(flatten)]
1661                project_actions_or_policy_id: ProjectActionsOrPolicyId,
1662            },
1663            /// Manage project policies
1664            Policy {
1665                #[command(subcommand)]
1666                subcommand: PolicySubcommand,
1667            },
1668            /// Manage project plugins
1669            Plugin {
1670                #[command(subcommand)]
1671                subcommand: ProjectPluginSubcommand,
1672            },
1673        }
1674
1675        pub mod policy {
1676            use crate::model::{ProjectAction, ProjectPolicyId};
1677            use clap::Subcommand;
1678
1679            #[derive(Subcommand, Debug)]
1680            pub enum PolicySubcommand {
1681                /// Creates a new project sharing policy
1682                New {
1683                    /// Name of the policy
1684                    policy_name: String,
1685                    /// List of actions allowed by the policy
1686                    actions: Vec<ProjectAction>,
1687                },
1688                /// Gets the existing project sharing policies
1689                #[command()]
1690                Get {
1691                    /// Project policy ID
1692                    policy_id: ProjectPolicyId,
1693                },
1694            }
1695        }
1696
1697        pub mod plugin {
1698            use crate::command::parse_key_val;
1699            use crate::command::shared_args::ProjectArg;
1700            use clap::Subcommand;
1701            use golem_common::base_model::PluginInstallationId;
1702
1703            #[derive(Debug, Subcommand)]
1704            pub enum ProjectPluginSubcommand {
1705                /// Install a plugin for a project
1706                Install {
1707                    #[clap(flatten)]
1708                    project: ProjectArg,
1709                    /// The plugin to install
1710                    #[arg(long)]
1711                    plugin_name: String,
1712                    /// The version of the plugin to install
1713                    #[arg(long)]
1714                    plugin_version: String,
1715                    /// Priority of the plugin - largest priority is applied first
1716                    #[arg(long)]
1717                    priority: i32,
1718                    /// List of parameters (key-value pairs) passed to the plugin
1719                    #[arg(long, value_parser = parse_key_val, value_name = "KEY=VAL")]
1720                    param: Vec<(String, String)>,
1721                },
1722                /// Get the installed plugins for the project
1723                Get {
1724                    #[clap(flatten)]
1725                    project: ProjectArg,
1726                    /* TODO: Missing from HTTP API
1727                    /// The version of the component
1728                    version: Option<u64>,
1729                    */
1730                },
1731                /// Update project plugin
1732                Update {
1733                    #[clap(flatten)]
1734                    project: ProjectArg,
1735                    /// Installation id of the plugin to update
1736                    plugin_installation_id: PluginInstallationId,
1737                    /// Updated priority of the plugin - largest priority is applied first
1738                    #[arg(long)]
1739                    priority: i32,
1740                    /// Updated list of parameters (key-value pairs) passed to the plugin
1741                    #[arg(long, value_parser = parse_key_val, value_name = "KEY=VAL")]
1742                    param: Vec<(String, String)>,
1743                },
1744                /// Uninstall a plugin for selected component
1745                Uninstall {
1746                    #[clap(flatten)]
1747                    project: ProjectArg,
1748                    /// Installation id of the plugin to uninstall
1749                    plugin_installation_id: PluginInstallationId,
1750                },
1751            }
1752        }
1753    }
1754}
1755
1756pub mod server {
1757    use clap::{Args, Subcommand};
1758    use std::path::PathBuf;
1759
1760    #[derive(Debug, Args, Default)]
1761    pub struct RunArgs {
1762        /// Address to serve the main API on, defaults to 0.0.0.0
1763        #[clap(long)]
1764        pub router_addr: Option<String>,
1765
1766        /// Port to serve the main API on, defaults to 9881
1767        #[clap(long)]
1768        pub router_port: Option<u16>,
1769
1770        /// Port to serve custom requests on, defaults to 9006
1771        #[clap(long)]
1772        pub custom_request_port: Option<u16>,
1773
1774        /// Directory to store data in. Defaults to $XDG_STATE_HOME/golem
1775        #[clap(long)]
1776        pub data_dir: Option<PathBuf>,
1777
1778        /// Clean the data directory before starting
1779        #[clap(long)]
1780        pub clean: bool,
1781    }
1782
1783    impl RunArgs {
1784        pub fn router_addr(&self) -> &str {
1785            self.router_addr.as_deref().unwrap_or("0.0.0.0")
1786        }
1787
1788        pub fn router_port(&self) -> u16 {
1789            self.router_port.unwrap_or(9881)
1790        }
1791
1792        pub fn custom_request_port(&self) -> u16 {
1793            self.custom_request_port.unwrap_or(9006)
1794        }
1795    }
1796
1797    #[derive(Debug, Subcommand)]
1798    pub enum ServerSubcommand {
1799        /// Run golem server for local development
1800        Run {
1801            #[clap(flatten)]
1802            args: RunArgs,
1803        },
1804        /// Clean the local server data directory
1805        Clean,
1806    }
1807}
1808
1809pub fn builtin_app_subcommands() -> BTreeSet<String> {
1810    GolemCliCommand::command()
1811        .find_subcommand("app")
1812        .unwrap()
1813        .get_subcommands()
1814        .map(|subcommand| subcommand.get_name().to_string())
1815        .collect()
1816}
1817
1818fn help_target_to_subcommand_names(target: ShowClapHelpTarget) -> Vec<&'static str> {
1819    match target {
1820        ShowClapHelpTarget::AppNew => {
1821            vec!["app", "new"]
1822        }
1823        ShowClapHelpTarget::ComponentNew => {
1824            vec!["component", "new"]
1825        }
1826        ShowClapHelpTarget::ComponentAddDependency => {
1827            vec!["component", "add-dependency"]
1828        }
1829    }
1830}
1831
1832pub fn help_target_to_command(target: ShowClapHelpTarget) -> Command {
1833    let command = GolemCliCommand::command();
1834    let mut command = &command;
1835
1836    for subcommand in help_target_to_subcommand_names(target) {
1837        command = command.find_subcommand(subcommand).unwrap();
1838    }
1839
1840    command.clone()
1841}
1842
1843fn parse_key_val(key_and_val: &str) -> anyhow::Result<(String, String)> {
1844    let pos = key_and_val.find('=').ok_or_else(|| {
1845        anyhow!(
1846            "invalid KEY=VALUE: no `=` found in `{}`",
1847            key_and_val.log_color_error_highlight()
1848        )
1849    })?;
1850    Ok((
1851        key_and_val[..pos].to_string(),
1852        key_and_val[pos + 1..].to_string(),
1853    ))
1854}
1855
1856// TODO: better error context and messages
1857fn parse_cursor(cursor: &str) -> anyhow::Result<ScanCursor> {
1858    let parts = cursor.split('/').collect::<Vec<_>>();
1859
1860    if parts.len() != 2 {
1861        bail!("Invalid cursor format: {}", cursor);
1862    }
1863
1864    Ok(ScanCursor {
1865        layer: parts[0].parse()?,
1866        cursor: parts[1].parse()?,
1867    })
1868}
1869
1870fn parse_instant(
1871    s: &str,
1872) -> Result<DateTime<Utc>, Box<dyn std::error::Error + Send + Sync + 'static>> {
1873    match s.parse::<DateTime<Utc>>() {
1874        Ok(dt) => Ok(dt),
1875        Err(err) => Err(err.into()),
1876    }
1877}
1878
1879#[cfg(test)]
1880mod test {
1881    use crate::command::{
1882        builtin_app_subcommands, help_target_to_subcommand_names, GolemCliCommand,
1883    };
1884    use crate::error::ShowClapHelpTarget;
1885    use assert2::assert;
1886    use clap::builder::StyledStr;
1887    use clap::{Command, CommandFactory};
1888    use itertools::Itertools;
1889    use std::collections::{BTreeMap, BTreeSet};
1890    use strum::IntoEnumIterator;
1891    use test_r::test;
1892
1893    #[test]
1894    fn command_debug_assert() {
1895        GolemCliCommand::command().debug_assert();
1896    }
1897
1898    #[test]
1899    fn all_commands_and_args_has_doc() {
1900        fn collect_docs(
1901            path: &mut Vec<String>,
1902            doc_by_cmd_path: &mut BTreeMap<String, Option<StyledStr>>,
1903            command: &Command,
1904        ) {
1905            path.push(command.get_name().to_string());
1906            let key = path.iter().join(" ");
1907
1908            doc_by_cmd_path.insert(key.clone(), command.get_about().cloned());
1909
1910            for arg in command.get_arguments() {
1911                doc_by_cmd_path.insert(
1912                    if arg.is_positional() {
1913                        format!("{} |{}|", key, arg.get_id().to_string().to_uppercase())
1914                    } else {
1915                        format!("{} --{}", key, arg.get_id())
1916                    },
1917                    arg.get_help().cloned(),
1918                );
1919            }
1920
1921            for subcommand in command.get_subcommands() {
1922                collect_docs(path, doc_by_cmd_path, subcommand);
1923            }
1924
1925            path.pop();
1926        }
1927
1928        let mut path = vec![];
1929        let mut doc_by_cmd_path = BTreeMap::new();
1930        collect_docs(&mut path, &mut doc_by_cmd_path, &GolemCliCommand::command());
1931
1932        let elems_without_about = doc_by_cmd_path
1933            .into_iter()
1934            .filter_map(|(path, about)| about.is_none().then_some(path))
1935            .collect::<Vec<_>>();
1936
1937        assert!(
1938            elems_without_about.is_empty(),
1939            "\n{}",
1940            elems_without_about.join("\n")
1941        );
1942    }
1943
1944    #[test]
1945    fn invalid_arg_matchers_are_using_valid_commands_and_args_names() {
1946        fn collect_positional_args(
1947            path: &mut Vec<String>,
1948            positional_args_by_cmd: &mut BTreeMap<String, BTreeSet<String>>,
1949            command: &Command,
1950        ) {
1951            path.push(command.get_name().to_string());
1952            let key = path.iter().join(" ");
1953
1954            positional_args_by_cmd.insert(
1955                key,
1956                command
1957                    .get_arguments()
1958                    .filter(|arg| arg.is_positional())
1959                    .map(|arg| arg.get_id().to_string())
1960                    .collect(),
1961            );
1962
1963            for subcommand in command.get_subcommands() {
1964                collect_positional_args(path, positional_args_by_cmd, subcommand);
1965            }
1966
1967            path.pop();
1968        }
1969
1970        let mut path = vec![];
1971        let mut positional_args_by_cmd = BTreeMap::new();
1972
1973        collect_positional_args(
1974            &mut path,
1975            &mut positional_args_by_cmd,
1976            &GolemCliCommand::command(),
1977        );
1978
1979        let bad_matchers = GolemCliCommand::invalid_arg_matchers()
1980            .into_iter()
1981            .filter_map(|matcher| {
1982                let cmd_path = format!("golem-cli {}", matcher.subcommands.iter().join(" "));
1983
1984                let Some(args) = positional_args_by_cmd.get(&cmd_path) else {
1985                    return Some(("command not found".to_string(), matcher));
1986                };
1987
1988                let missing_arg = [matcher.missing_positional_arg];
1989
1990                let bad_args = matcher
1991                    .found_positional_args
1992                    .iter()
1993                    .chain(&missing_arg)
1994                    .filter(|&&arg| !args.contains(arg))
1995                    .collect::<Vec<_>>();
1996
1997                if !bad_args.is_empty() {
1998                    return Some((
1999                        format!("args not found: {}", bad_args.into_iter().join(", ")),
2000                        matcher,
2001                    ));
2002                }
2003
2004                None
2005            })
2006            .collect::<Vec<_>>();
2007
2008        assert!(
2009            bad_matchers.is_empty(),
2010            "\n{}",
2011            bad_matchers
2012                .into_iter()
2013                .map(|(error, matcher)| format!("error: {}\nmatcher: {:?}\n", error, matcher))
2014                .join("\n")
2015        )
2016    }
2017
2018    #[test]
2019    fn no_overlapping_flags() {
2020        fn collect_flags(
2021            path: &mut Vec<String>,
2022            flags_by_cmd_path: &mut BTreeMap<String, Vec<String>>,
2023            global_flags: &mut Vec<String>,
2024            command: &Command,
2025        ) {
2026            path.push(command.get_name().to_string());
2027            let key = path.iter().join(" ");
2028
2029            let mut cmd_flag_names = Vec::<String>::new();
2030            for arg in command.get_arguments() {
2031                let mut arg_flag_names = Vec::<String>::new();
2032                if arg.is_positional() {
2033                    continue;
2034                }
2035
2036                arg_flag_names.extend(
2037                    arg.get_long_and_visible_aliases()
2038                        .into_iter()
2039                        .flatten()
2040                        .map(|s| s.to_string()),
2041                );
2042                arg_flag_names.extend(
2043                    arg.get_short_and_visible_aliases()
2044                        .into_iter()
2045                        .flatten()
2046                        .map(|s| s.to_string()),
2047                );
2048
2049                if arg.is_global_set() {
2050                    global_flags.extend(arg_flag_names);
2051                } else {
2052                    cmd_flag_names.extend(arg_flag_names);
2053                }
2054            }
2055
2056            flags_by_cmd_path.insert(key, cmd_flag_names);
2057
2058            for subcommand in command.get_subcommands() {
2059                collect_flags(path, flags_by_cmd_path, global_flags, subcommand);
2060            }
2061
2062            path.pop();
2063        }
2064
2065        let mut path = vec![];
2066        let mut flags_by_cmd_path = BTreeMap::<String, Vec<String>>::new();
2067        let mut global_flags = Vec::<String>::new();
2068        collect_flags(
2069            &mut path,
2070            &mut flags_by_cmd_path,
2071            &mut global_flags,
2072            &GolemCliCommand::command(),
2073        );
2074
2075        let commands_with_conflicting_flags = flags_by_cmd_path
2076            .into_iter()
2077            .map(|(path, flags)| {
2078                (
2079                    path,
2080                    flags
2081                        .into_iter()
2082                        .chain(global_flags.iter().cloned())
2083                        .counts()
2084                        .into_iter()
2085                        .filter(|(_, count)| *count > 1)
2086                        .collect::<Vec<_>>(),
2087                )
2088            })
2089            .filter(|(_, flags)| !flags.is_empty())
2090            .collect::<Vec<_>>();
2091
2092        assert!(
2093            commands_with_conflicting_flags.is_empty(),
2094            "\n{}",
2095            commands_with_conflicting_flags
2096                .iter()
2097                .map(|e| format!("{:?}", e))
2098                .join("\n")
2099        );
2100    }
2101
2102    #[test]
2103    fn builtin_app_subcommands_no_panic() {
2104        println!("{:?}", builtin_app_subcommands())
2105    }
2106
2107    #[test]
2108    fn help_targets_to_subcommands_uses_valid_subcommands() {
2109        for target in ShowClapHelpTarget::iter() {
2110            let command = GolemCliCommand::command();
2111            let mut command = &command;
2112            let subcommands = help_target_to_subcommand_names(target);
2113            for subcommand in &subcommands {
2114                match command.find_subcommand(subcommand) {
2115                    Some(subcommand) => command = subcommand,
2116                    None => {
2117                        panic!(
2118                            "Invalid help target: {}, {:?}, {}",
2119                            target, subcommands, subcommand
2120                        );
2121                    }
2122                }
2123            }
2124        }
2125    }
2126}