seaplane_cli/ops/
locks.rs

1use std::io::Write;
2
3use seaplane::api::locks::v1::{LockInfo, LockInfoInner, LockName as LockNameModel};
4use serde::Serialize;
5use tabwriter::TabWriter;
6
7use crate::{
8    context::Ctx,
9    error::{CliError, Result},
10    ops::EncodedString,
11    printer::{printer, Output},
12};
13
14/// We use our own LockName instead of the models because we need to *not* enforce base64 encoding,
15/// and implement a bunch of additional methods and traits that wouldn't make sense for the models
16///
17/// We also need to keep track if the values are encoded or not
18#[derive(Debug, Default, Clone, Serialize)]
19pub struct LockName {
20    pub name: EncodedString,
21}
22
23impl LockName {
24    /// Creates a new LockName from an encoded name. You must pinky promise the name
25    /// is URL safe base64 encoded or Bad Things may happen.
26    pub fn new<S: Into<String>>(name: S) -> Self { Self { name: EncodedString::new(name.into()) } }
27
28    /// Creates a new LockName from an un-encoded byte slice ref, encoding it along the way
29    pub fn from_name_unencoded<S: AsRef<[u8]>>(name: S) -> Self {
30        let engine = ::base64::engine::fast_portable::FastPortable::from(
31            &::base64::alphabet::URL_SAFE,
32            ::base64::engine::fast_portable::NO_PAD,
33        );
34        let name = base64::encode_engine(name.as_ref(), &engine);
35        Self { name: EncodedString::new(name) }
36    }
37
38    /// Creates a new LockName from self's data.
39    pub fn to_model(&self) -> LockNameModel { LockNameModel::from_encoded(self.name.to_string()) }
40}
41
42#[derive(Debug, Serialize)]
43pub struct HeldLock {
44    pub lock_id: String,
45    pub sequencer: u32,
46}
47
48impl Output for HeldLock {
49    fn print_json(&self, _ctx: &Ctx) -> Result<()> {
50        cli_println!("{}", serde_json::to_string(self)?);
51        Ok(())
52    }
53
54    fn print_table(&self, ctx: &Ctx) -> Result<()> {
55        let show_headers = !ctx.locks_ctx.get_or_init().no_header;
56        let mut ptr = printer();
57
58        let id_prefix = if show_headers { "LOCK-ID: " } else { "" };
59        let seq_prefix = if show_headers { "SEQUENCER: " } else { "" };
60        writeln!(ptr, "{id_prefix}{}", self.lock_id)?;
61        writeln!(ptr, "{seq_prefix}{}", self.sequencer)?;
62
63        ptr.flush()?;
64
65        Ok(())
66    }
67}
68
69#[derive(Debug, Serialize)]
70pub struct ListedLockInfoInner {
71    pub ttl: u32,
72    #[serde(rename = "client-id")]
73    pub client_id: String,
74    pub ip: String,
75}
76
77impl From<LockInfoInner> for ListedLockInfoInner {
78    fn from(other: LockInfoInner) -> Self {
79        Self { ttl: other.ttl, client_id: other.client_id, ip: other.ip }
80    }
81}
82
83#[derive(Debug, Serialize)]
84pub struct ListedLock {
85    name: EncodedString,
86    id: String,
87    info: ListedLockInfoInner,
88}
89
90impl From<LockInfo> for ListedLock {
91    fn from(other: LockInfo) -> Self {
92        let info = other.info.into();
93        Self {
94            name: EncodedString::new(other.name.encoded().to_owned()),
95            id: other.id.encoded().to_owned(),
96            info,
97        }
98    }
99}
100
101pub fn print_lock_table<I>(headers: bool, chunk: I, ctx: &Ctx) -> Result<()>
102where
103    I: IntoIterator<Item = ListedLock>,
104{
105    let buffer = Vec::new();
106    let mut tw = TabWriter::new(buffer);
107    if headers {
108        writeln!(tw, "LOCK-NAME\tLOCK-ID\tCLIENT-ID\tCLIENT-IP\tTTL")?;
109    }
110
111    let locksctx = ctx.locks_ctx.get_or_init();
112    for l in chunk {
113        if locksctx.decode {
114            // decoded names with tabs in them are going to act funny
115            // (workaround is to not decode them)
116            tw.write_all(&l.name.decoded()?)?;
117        } else {
118            write!(tw, "{}", l.name)?;
119        };
120
121        writeln!(tw, "\t{}\t{}\t{}\t{}", l.id, l.info.client_id, l.info.ip, l.info.ttl)?;
122    }
123    tw.flush()?;
124
125    let mut ptr = printer();
126    let page = tw
127        .into_inner()
128        .map_err(|_| CliError::bail("IO flush error writing locks"))?;
129    ptr.write_all(&page)?;
130    ptr.flush()?;
131
132    Ok(())
133}