seaplane_cli/cli/cmds/metadata/
list.rs

1use clap::{ArgMatches, Command};
2use seaplane::api::{
3    metadata::v1::Key,
4    shared::v1::{Directory, RangeQueryContext},
5};
6
7use crate::{
8    api::MetadataReq,
9    cli::{cmds::metadata::common, CliCommand},
10    context::{Ctx, MetadataCtx},
11    error::{CliError, CliErrorKind, Result},
12    ops::metadata::KeyValues,
13    printer::{Output, OutputFormat},
14};
15
16static LONG_ABOUT: &str = "List one or more metadata key-value pairs
17
18Keys and values will be displayed in base64 encoded format by default because they may contain
19arbitrary binary data. Using --decode allows one to decode them and display the unencoded
20values.";
21
22#[derive(Copy, Clone, Debug)]
23pub struct SeaplaneMetadataList;
24
25impl SeaplaneMetadataList {
26    pub fn command() -> Command {
27        Command::new("list")
28            .visible_alias("ls")
29            .about("List one or more metadata key-value pairs")
30            .long_about(LONG_ABOUT)
31            .arg(
32                arg!(dir =["DIR"])
33                    .help("The root directory of the metadata key-value pairs to list"),
34            )
35            .arg(common::base64())
36            .args(common::display_args())
37            .group(common::keys_or_values())
38            .arg(arg!(--from - ('f') =["KEY"]).help("Only print metadata key-value pairs after this key (note: if this key has a value it will be included in the results)"))
39    }
40}
41
42impl CliCommand for SeaplaneMetadataList {
43    fn run(&self, ctx: &mut Ctx) -> Result<()> {
44        // Scope releases the mutex on the MetadataCtx so that when we hand off the ctx to print_*
45        // we don't have the chance of a deadlock if those functions need to acquire a
46        // MetadataCtx
47        let kvs = {
48            let mdctx = ctx.md_ctx.get_or_init();
49
50            let mut range = RangeQueryContext::new();
51            if let Some(dir) = &mdctx.directory {
52                range.set_directory(dir.clone());
53            }
54            if let Some(from) = &mdctx.from {
55                range.set_from(from.clone());
56            }
57            // Using the KeyValues container makes displaying easy
58            let mut req = MetadataReq::new(ctx)?;
59            req.set_dir(range)?;
60            KeyValues::from_model(req.get_all_pages()?)
61        };
62
63        match ctx.args.out_format {
64            OutputFormat::Json => kvs.print_json(ctx)?,
65            OutputFormat::Table => kvs.print_table(ctx)?,
66        }
67
68        Ok(())
69    }
70
71    fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
72        ctx.md_ctx.init(MetadataCtx::default());
73        ctx.args.out_format = matches.get_one("format").copied().unwrap_or_default();
74        let mut mdctx = ctx.md_ctx.get_mut().unwrap();
75        mdctx.base64 = matches.get_flag("base64");
76        mdctx.decode = matches.get_flag("decode");
77        mdctx.decode_safe = matches.get_flag("decode-safe");
78        mdctx.no_decode = matches.get_flag("no-decode");
79        mdctx.no_keys = matches.get_flag("only-values");
80        mdctx.no_values = matches.get_flag("only-keys");
81        mdctx.no_header = matches.get_flag("no-header");
82        mdctx.keys_width_limit = matches
83            .get_one::<usize>("keys-width-limit")
84            .copied()
85            .unwrap_or_default();
86        mdctx.values_width_limit = matches
87            .get_one::<usize>("values-width-limit")
88            .copied()
89            .unwrap_or_default();
90        mdctx.from =
91            maybe_base64_arg!(matches, "from", matches.get_flag("base64")).map(Key::from_encoded);
92        mdctx.directory = maybe_base64_arg!(matches, "dir", matches.get_flag("base64"))
93            .map(Directory::from_encoded);
94
95        // We set the decode_safe flag if there's no `decode` or `no-decode`
96        // flags set, because there's no built-in clap method to turn a flag on
97        // with default_value_if()
98        if matches.get_flag("human-readable") && !(mdctx.decode || mdctx.no_decode) {
99            mdctx.decode_safe = true
100        };
101
102        if mdctx.decode && ctx.args.out_format != OutputFormat::Table {
103            let format_arg = format!("--format {}", ctx.args.out_format);
104            return Err(CliError::from(CliErrorKind::ConflictingArguments(
105                "--decode".to_owned(),
106                format_arg,
107            )));
108        }
109
110        Ok(())
111    }
112}