seaplane_cli/cli/cmds/
account.rs1use std::io::{self, BufRead};
2
3use clap::{ArgMatches, Command};
4use seaplane::api::identity::v0::TokenRequest;
5
6use crate::{
7 cli::CliCommand,
8 config::RawConfig,
9 context::Ctx,
10 error::{CliError, CliErrorKind, Context, Result},
11 fs::{FromDisk, ToDisk},
12 printer::{Color, OutputFormat},
13};
14
15#[derive(Copy, Clone, Debug)]
16pub struct SeaplaneAccount;
17
18impl SeaplaneAccount {
19 pub fn command() -> Command {
20 Command::new("account")
21 .visible_alias("acct")
22 .about("Operate on Seaplane account details, including access tokens")
23 .subcommand_required(true)
24 .arg_required_else_help(true)
25 .subcommand(SeaplaneAccountLogin::command())
26 .subcommand(SeaplaneAccountToken::command())
27 }
28}
29
30impl CliCommand for SeaplaneAccount {
31 fn next_subcmd<'a>(
32 &self,
33 matches: &'a ArgMatches,
34 ) -> Option<(Box<dyn CliCommand>, &'a ArgMatches)> {
35 match matches.subcommand() {
36 Some(("login", m)) => Some((Box::new(SeaplaneAccountLogin), m)),
37 Some(("token", m)) => Some((Box::new(SeaplaneAccountToken), m)),
38 _ => None,
39 }
40 }
41}
42
43#[derive(Copy, Clone, Debug)]
44pub struct SeaplaneAccountToken;
45
46impl SeaplaneAccountToken {
47 pub fn command() -> Command {
48 Command::new("token").arg(arg!(--json - ('j')).help(
49 "Returns the access token in a JSON object also containing tenant ID and subdomain",
50 ))
51 }
52}
53
54impl CliCommand for SeaplaneAccountToken {
55 fn run(&self, ctx: &mut Ctx) -> Result<()> {
56 let mut builder = TokenRequest::builder().api_key(ctx.args.api_key()?);
57
58 if let Some(url) = ctx.identity_url.as_ref() {
59 builder = builder.base_url(url);
60 }
61
62 #[cfg(feature = "allow_insecure_urls")]
63 {
64 builder = builder.allow_http(ctx.insecure_urls);
65 }
66 #[cfg(feature = "allow_invalid_certs")]
67 {
68 builder = builder.allow_invalid_certs(ctx.invalid_certs);
69 }
70
71 let t = builder.build().map_err(CliError::from)?;
72
73 if ctx.args.out_format == OutputFormat::Json {
74 cli_println!("{}", serde_json::to_string(&t.access_token_json()?)?);
75 } else {
76 cli_println!("{}", t.access_token()?);
77 }
78
79 Ok(())
80 }
81
82 fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
83 if matches.get_flag("json") {
84 ctx.args.out_format = OutputFormat::Json;
85 }
86 Ok(())
87 }
88}
89
90#[derive(Copy, Clone, Debug)]
91pub struct SeaplaneAccountLogin;
92
93impl SeaplaneAccountLogin {
94 pub fn command() -> Command {
95 Command::new("login").arg(arg!(--force - ('f')).help("Override any existing API key"))
96 }
97}
98
99impl CliCommand for SeaplaneAccountLogin {
100 fn run(&self, ctx: &mut Ctx) -> Result<()> {
101 if ctx.args.stateless {
102 cli_bail!("'--stateless' cannot be used with 'seaplane account login'");
103 }
104 let mut cfg = if let Some(f) = ctx.conf_files().first() {
105 RawConfig::load(f)?
106 } else {
107 RawConfig::load_all()?
111 };
112
113 if let Some(key) = cfg.account.api_key {
114 if ctx.args.force {
115 cli_warn!(@Yellow, "warn: ");
116 cli_warn!("overwriting API key ");
117 cli_warn!(@Green, "{} ", key);
118 cli_warn!("due to ");
119 cli_warnln!(@noprefix, @Green, "--force");
120 } else {
121 return Err(CliErrorKind::ExistingValue("an API key")
122 .into_err()
123 .context("(hint: add '")
124 .color_context(Color::Green, "--force")
125 .context("' to overwrite it)\n"));
126 }
127 }
128 cli_println!("Enter an API key below.");
129 cli_print!("(hint: it can be found by visiting ");
130 cli_print!(@Green, "https://flightdeck.cplane.cloud/");
131 cli_println!(")\n");
132
133 let stdin = io::stdin();
134 let mut lines = stdin.lock().lines();
135 if let Some(line) = lines.next() {
136 ctx.args.api_key = Some(line?);
137 }
138
139 cfg.account.api_key = ctx.args.api_key.clone();
140
141 cfg.persist()?;
142
143 cli_println!("Successfully saved the API key!");
144
145 Ok(())
146 }
147
148 fn update_ctx(&self, matches: &ArgMatches, ctx: &mut Ctx) -> Result<()> {
149 ctx.args.force = matches.get_flag("force");
150 Ok(())
151 }
152}