wash_cli/ctl/
mod.rs

1use clap::Subcommand;
2
3pub use output::*;
4use wash_lib::cli::{
5    get::{GetClaimsCommand, GetHostInventoriesCommand, GetHostsCommand},
6    link::LinkCommand,
7    scale::ScaleCommand,
8    start::StartCommand,
9    stop::StopCommand,
10    update::UpdateCommand,
11};
12
13mod output;
14
15#[derive(Debug, Clone, Subcommand)]
16pub enum CtlCliCommand {
17    /// Retrieves information about the lattice
18    #[clap(name = "get", subcommand)]
19    Get(CtlGetCommand),
20
21    /// Link an component and a provider
22    #[clap(name = "link", alias = "links", subcommand)]
23    Link(LinkCommand),
24
25    /// Start an component or a provider
26    #[clap(name = "start", subcommand)]
27    Start(StartCommand),
28
29    /// Stop an component, provider, or host
30    #[clap(name = "stop", subcommand)]
31    Stop(StopCommand),
32
33    /// Update an component running in a host to a new component
34    #[clap(name = "update", subcommand)]
35    Update(UpdateCommand),
36
37    #[clap(name = "scale", subcommand)]
38    Scale(ScaleCommand),
39}
40
41#[derive(Debug, Clone, Subcommand)]
42pub enum CtlGetCommand {
43    /// Query lattice for running hosts
44    #[clap(name = "hosts")]
45    Hosts(GetHostsCommand),
46
47    /// Query a single host for its inventory of labels, components and providers
48    #[clap(name = "inventory")]
49    HostInventories(GetHostInventoriesCommand),
50
51    /// Query lattice for its claims cache
52    #[clap(name = "claims")]
53    Claims(GetClaimsCommand),
54}
55
56#[cfg(test)]
57mod test {
58    use clap::Parser;
59
60    use wash_lib::cli::{
61        get::GetHostsCommand,
62        scale::ScaleComponentCommand,
63        stop::{StopComponentCommand, StopProviderCommand},
64        update::UpdateComponentCommand,
65    };
66
67    use super::*;
68
69    #[derive(Parser)]
70    struct Cmd {
71        #[clap(subcommand)]
72        command: CtlCliCommand,
73    }
74
75    const CTL_HOST: &str = "127.0.0.1";
76    const CTL_PORT: &str = "4222";
77    const DEFAULT_LATTICE: &str = "default";
78    const JS_DOMAIN: &str = "custom-domain";
79
80    const COMPONENT_ID: &str = "MDPDJEYIAK6MACO67PRFGOSSLODBISK4SCEYDY3HEOY4P5CVJN6UCWUK";
81    const PROVIDER_ID: &str = "VBKTSBG2WKP6RJWLQ5O7RDVIIB4LMW6U5R67A7QMIDBZDGZWYTUE3TSI";
82    const HOST_ID: &str = "NCE7YHGI42RWEKBRDJZWXBEJJCFNE5YIWYMSTLGHQBEGFY55BKJ3EG3G";
83
84    #[test]
85    /// Enumerates multiple options of the `ctl` command to ensure API doesn't
86    /// change between versions. This test will fail if any subcommand of `wash ctl`
87    /// changes syntax, ordering of required elements, or flags.
88    fn test_ctl_comprehensive() -> anyhow::Result<()> {
89        let stop_component_all: Cmd = Parser::try_parse_from([
90            "ctl",
91            "stop",
92            "component",
93            "--lattice",
94            DEFAULT_LATTICE,
95            "--ctl-host",
96            CTL_HOST,
97            "--ctl-port",
98            CTL_PORT,
99            "--timeout-ms",
100            "2001",
101            "--host-id",
102            HOST_ID,
103            COMPONENT_ID,
104        ])?;
105        match stop_component_all.command {
106            CtlCliCommand::Stop(StopCommand::Component(StopComponentCommand {
107                opts,
108                host_id,
109                component_id,
110                skip_wait,
111            })) => {
112                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
113                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
114                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
115                assert_eq!(opts.timeout_ms, 2001);
116                assert_eq!(host_id, Some(HOST_ID.to_string()));
117                assert_eq!(component_id, COMPONENT_ID);
118                assert!(!skip_wait);
119            }
120            cmd => panic!("ctl stop component constructed incorrect command {cmd:?}"),
121        }
122        let stop_component_minimal: Cmd =
123            Parser::try_parse_from(["ctl", "stop", "component", "foobar"])?;
124        match stop_component_minimal.command {
125            CtlCliCommand::Stop(StopCommand::Component(StopComponentCommand {
126                host_id,
127                component_id,
128                ..
129            })) => {
130                assert_eq!(host_id, None);
131                assert_eq!(component_id, "foobar");
132            }
133            cmd => panic!("ctl stop component constructed incorrect command {cmd:?}"),
134        }
135        let stop_provider_all: Cmd = Parser::try_parse_from([
136            "ctl",
137            "stop",
138            "provider",
139            "--lattice",
140            DEFAULT_LATTICE,
141            "--ctl-host",
142            CTL_HOST,
143            "--ctl-port",
144            CTL_PORT,
145            "--timeout-ms",
146            "2001",
147            "--host-id",
148            HOST_ID,
149            PROVIDER_ID,
150        ])?;
151        match stop_provider_all.command {
152            CtlCliCommand::Stop(StopCommand::Provider(StopProviderCommand {
153                opts,
154                host_id,
155                provider_id,
156                skip_wait,
157            })) => {
158                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
159                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
160                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
161                assert_eq!(opts.timeout_ms, 2001);
162                assert_eq!(host_id, Some(HOST_ID.to_string()));
163                assert_eq!(provider_id, PROVIDER_ID);
164                assert!(!skip_wait);
165            }
166            cmd => panic!("ctl stop component constructed incorrect command {cmd:?}"),
167        }
168        let stop_provider_minimal: Cmd =
169            Parser::try_parse_from(["ctl", "stop", "provider", "foobar"])?;
170        match stop_provider_minimal.command {
171            CtlCliCommand::Stop(StopCommand::Provider(StopProviderCommand {
172                host_id,
173                provider_id,
174                ..
175            })) => {
176                assert_eq!(host_id, None);
177                assert_eq!(provider_id, "foobar");
178            }
179            cmd => panic!("ctl stop component constructed incorrect command {cmd:?}"),
180        }
181        let get_hosts_all: Cmd = Parser::try_parse_from([
182            "ctl",
183            "get",
184            "hosts",
185            "--lattice",
186            DEFAULT_LATTICE,
187            "--ctl-host",
188            CTL_HOST,
189            "--ctl-port",
190            CTL_PORT,
191            "--timeout-ms",
192            "2001",
193        ])?;
194        match get_hosts_all.command {
195            CtlCliCommand::Get(CtlGetCommand::Hosts(GetHostsCommand { opts })) => {
196                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
197                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
198                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
199                assert_eq!(opts.timeout_ms, 2001);
200            }
201            cmd => panic!("ctl get hosts constructed incorrect command {cmd:?}"),
202        }
203        let get_host_inventory_all: Cmd = Parser::try_parse_from([
204            "ctl",
205            "get",
206            "inventory",
207            "--lattice",
208            DEFAULT_LATTICE,
209            "--ctl-host",
210            CTL_HOST,
211            "--ctl-port",
212            CTL_PORT,
213            "--timeout-ms",
214            "2001",
215            HOST_ID,
216        ])?;
217        match get_host_inventory_all.command {
218            CtlCliCommand::Get(CtlGetCommand::HostInventories(GetHostInventoriesCommand {
219                opts,
220                host_id,
221                watch: _,
222            })) => {
223                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
224                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
225                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
226                assert_eq!(opts.timeout_ms, 2001);
227                assert_eq!(host_id.unwrap(), HOST_ID.parse()?);
228            }
229            cmd => panic!("ctl get inventory constructed incorrect command {cmd:?}"),
230        }
231        let get_claims_all: Cmd = Parser::try_parse_from([
232            "ctl",
233            "get",
234            "claims",
235            "--lattice",
236            DEFAULT_LATTICE,
237            "--ctl-host",
238            CTL_HOST,
239            "--ctl-port",
240            CTL_PORT,
241            "--timeout-ms",
242            "2001",
243            "--js-domain",
244            JS_DOMAIN,
245        ])?;
246        match get_claims_all.command {
247            CtlCliCommand::Get(CtlGetCommand::Claims(GetClaimsCommand { opts })) => {
248                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
249                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
250                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
251                assert_eq!(opts.timeout_ms, 2001);
252                assert_eq!(opts.js_domain.unwrap(), JS_DOMAIN);
253            }
254            cmd => panic!("ctl get claims constructed incorrect command {cmd:?}"),
255        }
256        let link_all: Cmd = Parser::try_parse_from([
257            "ctl",
258            "link",
259            "put",
260            "--lattice",
261            DEFAULT_LATTICE,
262            "--ctl-host",
263            CTL_HOST,
264            "--ctl-port",
265            CTL_PORT,
266            "--timeout-ms",
267            "2001",
268            "--link-name",
269            "notdefault",
270            COMPONENT_ID,
271            PROVIDER_ID,
272            "wasmcloud",
273            "provider",
274            "--interface",
275            "foo",
276        ])?;
277        use wash_lib::cli::link::LinkPutCommand;
278        match link_all.command {
279            CtlCliCommand::Link(LinkCommand::Put(LinkPutCommand {
280                opts,
281                source_id,
282                target,
283                wit_namespace,
284                wit_package,
285                interfaces,
286                source_config,
287                target_config,
288                link_name,
289            })) => {
290                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
291                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
292                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
293                assert_eq!(opts.timeout_ms, 2001);
294                assert_eq!(source_id, COMPONENT_ID);
295                assert_eq!(target, PROVIDER_ID);
296                assert_eq!(wit_namespace, "wasmcloud".to_string());
297                assert_eq!(wit_package, "provider".to_string());
298                assert_eq!(link_name.unwrap(), "notdefault".to_string());
299                assert_eq!(interfaces.as_slice(), &["foo".to_string()]);
300                assert!(source_config.is_empty());
301                assert!(target_config.is_empty());
302            }
303            cmd => panic!("ctl link put constructed incorrect command {cmd:?}"),
304        }
305        let update_all: Cmd = Parser::try_parse_from([
306            "ctl",
307            "update",
308            "component",
309            "--lattice",
310            DEFAULT_LATTICE,
311            "--ctl-host",
312            CTL_HOST,
313            "--ctl-port",
314            CTL_PORT,
315            "--timeout-ms",
316            "2001",
317            "--host-id",
318            HOST_ID,
319            COMPONENT_ID,
320            "wasmcloud.azurecr.io/component:v2",
321        ])?;
322        match update_all.command {
323            CtlCliCommand::Update(UpdateCommand::Component(UpdateComponentCommand {
324                opts,
325                host_id,
326                component_id,
327                new_component_ref,
328            })) => {
329                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
330                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
331                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
332                assert_eq!(opts.timeout_ms, 2001);
333                assert_eq!(host_id, Some(HOST_ID.to_string()));
334                assert_eq!(component_id, COMPONENT_ID);
335                assert_eq!(
336                    new_component_ref,
337                    "wasmcloud.azurecr.io/component:v2".to_string()
338                );
339            }
340            cmd => panic!("ctl get claims constructed incorrect command {cmd:?}"),
341        }
342
343        let scale_component_all: Cmd = Parser::try_parse_from([
344            "ctl",
345            "scale",
346            "component",
347            "--lattice",
348            DEFAULT_LATTICE,
349            "--ctl-host",
350            CTL_HOST,
351            "--ctl-port",
352            CTL_PORT,
353            "--timeout-ms",
354            "2001",
355            HOST_ID,
356            "wasmcloud.azurecr.io/component:v2",
357            "mycomponentv2",
358            "--count",
359            "1",
360            "--annotations",
361            "foo=bar",
362            "--config",
363            "default-port",
364            "--config",
365            "lang",
366        ])?;
367
368        match scale_component_all.command {
369            CtlCliCommand::Scale(ScaleCommand::Component(ScaleComponentCommand {
370                opts,
371                host_id,
372                component_ref,
373                component_id,
374                max_instances,
375                annotations,
376                config,
377                skip_wait,
378                wait_timeout_ms,
379            })) => {
380                assert_eq!(&opts.ctl_host.unwrap(), CTL_HOST);
381                assert_eq!(&opts.ctl_port.unwrap(), CTL_PORT);
382                assert_eq!(&opts.lattice.unwrap(), DEFAULT_LATTICE);
383                assert_eq!(opts.timeout_ms, 2001);
384                assert_eq!(host_id, HOST_ID);
385                assert_eq!(
386                    component_ref,
387                    "wasmcloud.azurecr.io/component:v2".to_string()
388                );
389                assert_eq!(component_id, "mycomponentv2".to_string());
390                assert_eq!(max_instances, 1);
391                assert_eq!(annotations, vec!["foo=bar".to_string()]);
392                assert_eq!(config, vec!["default-port", "lang"]);
393                assert!(!skip_wait);
394                assert_eq!(wait_timeout_ms, 5000);
395            }
396            cmd => panic!("ctl scale component constructed incorrect command {cmd:?}"),
397        }
398
399        Ok(())
400    }
401}