seaplane_cli/cli/cmds/locks/
list.rs1use clap::{ArgMatches, Command};
2
3use crate::{
4 api::LocksReq,
5 cli::cmds::locks::{common, common::SeaplaneLocksCommonArgMatches, CliCommand},
6 context::{Ctx, LocksCtx},
7 error::{CliError, CliErrorKind, Result},
8 ops::locks::{self, ListedLock, LockName},
9 printer::OutputFormat,
10};
11
12static OUTPUT_PAGE_SIZE: usize = 10;
13
14static LONG_ABOUT: &str = "Get information around currently held locks.
15
16There are 3 ways to list locks with this command:
17- Omit the LOCK_NAME argument to list all locks
18- Use a single lock name as the argument, without a trailing slash, this will list only that single lock
19- Use a lock name followed by a trailing slash to list all locks under that directory
20
21Locknames will be displayed in base64 encoded format by default because they may contain
22arbitrary binary data. Using --decode to output the decoded values instead.";
23
24#[derive(Copy, Clone, Debug)]
25pub struct SeaplaneLocksList;
26
27impl SeaplaneLocksList {
28 pub fn command() -> Command {
29 Command::new("list")
30 .visible_alias("ls")
31 .about("Get information around currently held locks")
32 .long_about(LONG_ABOUT)
33 .arg(
34 arg!(lock_name = ["LOCK_NAME"] !required)
35 .help("The name of a lock. If omitted, all locks are shown. Append a trailing slash to list directory contents"),
36 )
37 .arg(common::base64().requires("lock_name"))
38 .args(common::display_args())
39 }
40}
41
42fn run_one_info(ctx: &mut Ctx) -> Result<()> {
43 let locksctx = ctx.locks_ctx.get_or_init();
44 let lock_name = locksctx.lock_name.as_ref().unwrap();
45 let model_name = lock_name.to_model();
46
47 let mut req = LocksReq::new(ctx)?;
48 req.set_name(model_name)?;
49
50 let resp = req.get_lock_info()?;
51 let out = ListedLock::from(resp);
52
53 match ctx.args.out_format {
54 OutputFormat::Json => cli_println!("{}", serde_json::to_string(&out)?),
55 OutputFormat::Table => locks::print_lock_table(!locksctx.no_header, vec![out], ctx)?,
56 };
57
58 Ok(())
59}
60
61fn run_dir_info(ctx: &mut Ctx, dir_name: Option<LockName>) -> Result<()> {
63 let mut last_key = None;
64 let dir = dir_name.map(|d| d.to_model());
65 let mut headers = !ctx.locks_ctx.get_or_init().no_header;
66 let mut table_page = Vec::with_capacity(OUTPUT_PAGE_SIZE);
67
68 loop {
69 let mut req = LocksReq::new(ctx)?;
70 let page = req.get_page(last_key, dir.clone())?;
71
72 for info in page.locks {
76 let out = ListedLock::from(info);
77 match ctx.args.out_format {
78 OutputFormat::Json => cli_println!("{}", serde_json::to_string(&out)?),
79 OutputFormat::Table => {
80 table_page.push(out);
81 if table_page.len() >= OUTPUT_PAGE_SIZE {
82 locks::print_lock_table(headers, table_page.drain(..), ctx)?;
83 headers = false;
84 }
85 }
86 }
87 }
88
89 if let Some(next_key) = page.next {
90 last_key = Some(next_key);
91 } else {
92 if !table_page.is_empty() {
93 locks::print_lock_table(headers, table_page, ctx)?;
94 }
95
96 break;
97 }
98 }
99
100 Ok(())
101}
102
103impl CliCommand for SeaplaneLocksList {
104 fn run(&self, ctx: &mut Ctx) -> Result<()> {
105 let locksctx = ctx.locks_ctx.get_or_init();
106
107 match &locksctx.lock_name {
109 None => run_dir_info(ctx, None),
111 Some(name) => {
112 let mut decoded_lock_name = name
115 .name
116 .decoded()
117 .expect("decoding of a string we encoded shouldn't ever fail");
118
119 if *decoded_lock_name
120 .last()
121 .expect("Lock name should hold something else it'd be None")
122 == b'/'
123 {
124 decoded_lock_name.pop();
127 run_dir_info(ctx, Some(LockName::from_name_unencoded(decoded_lock_name)))
128 } else {
129 run_one_info(ctx)
130 }
131 }
132 }
133 }
134
135 fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
136 ctx.locks_ctx
137 .init(LocksCtx::from_locks_common(&SeaplaneLocksCommonArgMatches(matches))?);
138
139 ctx.args.out_format = matches.get_one("format").copied().unwrap_or_default();
140 let mut locksctx = ctx.locks_ctx.get_mut().unwrap();
141 locksctx.base64 = matches.get_flag("base64");
142 locksctx.decode = matches.get_flag("decode");
143 locksctx.no_header = matches.get_flag("no-header");
144
145 if locksctx.decode && ctx.args.out_format != OutputFormat::Table {
146 let format_arg = format!("--format {}", ctx.args.out_format);
147 return Err(CliError::from(CliErrorKind::ConflictingArguments(
148 "--decode".to_owned(),
149 format_arg,
150 )));
151 }
152
153 Ok(())
154 }
155}