wasmer_cli/commands/app/secrets/
delete.rs

1use crate::{
2    commands::{AsyncCliCommand, app::util::AppIdentFlag},
3    config::WasmerEnv,
4};
5use colored::Colorize;
6use dialoguer::theme::ColorfulTheme;
7use is_terminal::IsTerminal;
8use std::path::{Path, PathBuf};
9use wasmer_backend_api::WasmerClient;
10
11use super::utils::{self, get_secrets};
12
13/// Delete an existing app secret.
14#[derive(clap::Parser, Debug)]
15pub struct CmdAppSecretsDelete {
16    /* --- Common flags --- */
17    #[clap(flatten)]
18    pub env: WasmerEnv,
19
20    /// Don't print any message.
21    #[clap(long)]
22    pub quiet: bool,
23
24    /// Do not prompt for user input.
25    #[clap(long, default_value_t = !std::io::stdin().is_terminal())]
26    pub non_interactive: bool,
27
28    /* --- Flags --- */
29    #[clap(flatten)]
30    pub app_id: AppIdentFlag,
31
32    /// The path to the directory where the config file for the application will be written to.
33    #[clap(long = "app-dir", conflicts_with = "app")]
34    pub app_dir_path: Option<PathBuf>,
35
36    /// Path to a file with secrets stored in JSON format to delete secrets from.
37    #[clap(
38        long,
39        name = "from-file",
40        conflicts_with = "name",
41        conflicts_with = "all"
42    )]
43    pub from_file: Option<PathBuf>,
44
45    /// Delete all the secrets related to an app.
46    #[clap(long, conflicts_with = "name")]
47    pub all: bool,
48
49    /// Delete the secret(s) without asking for confirmation.
50    #[clap(long)]
51    pub force: bool,
52
53    /* --- Parameters --- */
54    /// The name of the secret to delete.
55    #[clap(name = "name")]
56    pub secret_name: Option<String>,
57}
58
59impl CmdAppSecretsDelete {
60    fn get_secret_name(&self) -> anyhow::Result<String> {
61        if let Some(name) = &self.secret_name {
62            return Ok(name.clone());
63        }
64
65        if self.non_interactive {
66            anyhow::bail!("No secret name given. Provide one as a positional argument.")
67        } else {
68            let theme = ColorfulTheme::default();
69            Ok(dialoguer::Input::with_theme(&theme)
70                .with_prompt("Enter the name of the secret")
71                .interact_text()?)
72        }
73    }
74
75    async fn delete(
76        &self,
77        client: &WasmerClient,
78        app_id: &str,
79        secret_name: &str,
80    ) -> anyhow::Result<()> {
81        let secret = utils::get_secret_by_name(client, app_id, secret_name).await?;
82
83        if let Some(secret) = secret {
84            if !self.non_interactive && !self.force {
85                let theme = ColorfulTheme::default();
86                let res = dialoguer::Confirm::with_theme(&theme)
87                    .with_prompt(format!("Delete secret '{}'?", secret_name.bold()))
88                    .interact()?;
89                if !res {
90                    return Ok(());
91                }
92            }
93
94            let res = wasmer_backend_api::query::delete_app_secret(client, secret.id.into_inner())
95                .await?;
96
97            match res {
98                Some(res) if !res.success => {
99                    anyhow::bail!("Error deleting secret '{}'", secret.name.bold())
100                }
101                Some(_) => {
102                    if !self.quiet {
103                        eprintln!("Correctly deleted secret '{}'", secret.name.bold());
104                    }
105                    Ok(())
106                }
107                None => {
108                    anyhow::bail!("Error deleting secret '{}'", secret.name.bold())
109                }
110            }
111        } else {
112            if !self.quiet {
113                eprintln!("No secret with name '{}' found", secret_name.bold());
114            }
115            Ok(())
116        }
117    }
118
119    async fn delete_from_file(
120        &self,
121        client: &WasmerClient,
122        path: &Path,
123        app_id: String,
124    ) -> anyhow::Result<()> {
125        let secrets = super::utils::read_secrets_from_file(path).await?;
126
127        for secret in secrets {
128            self.delete(client, &app_id, &secret.name).await?;
129        }
130
131        Ok(())
132    }
133}
134
135#[async_trait::async_trait]
136impl AsyncCliCommand for CmdAppSecretsDelete {
137    type Output = ();
138
139    async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
140        let client = self.env.client()?;
141        let app_id = super::utils::get_app_id(
142            &client,
143            self.app_id.app.as_ref(),
144            self.app_dir_path.as_ref(),
145            self.quiet,
146            self.non_interactive,
147        )
148        .await?;
149        if let Some(file) = &self.from_file {
150            self.delete_from_file(&client, file, app_id).await
151        } else if self.all {
152            if self.non_interactive && !self.force {
153                anyhow::bail!(
154                    "Refusing to delete all secrets in non-interactive mode without the `--force` flag."
155                )
156            }
157            let secrets = get_secrets(&client, &app_id).await?;
158            for secret in secrets {
159                self.delete(&client, &app_id, &secret.name).await?;
160            }
161
162            Ok(())
163        } else {
164            let name = self.get_secret_name()?;
165            self.delete(&client, &app_id, &name).await
166        }
167    }
168}