wasmer_cli/commands/app/secrets/utils/
mod.rs

1pub(crate) mod render;
2use anyhow::Context;
3use colored::Colorize;
4use std::{
5    env::current_dir,
6    path::{Path, PathBuf},
7    str::FromStr,
8};
9use wasmer_backend_api::{WasmerClient, types::Secret as BackendSecret};
10
11use crate::commands::app::util::{AppIdent, get_app_config_from_dir, prompt_app_ident};
12
13#[derive(serde::Serialize, serde::Deserialize)]
14pub(super) struct Secret {
15    pub name: String,
16    pub value: String,
17}
18
19pub(super) async fn read_secrets_from_file(path: &Path) -> anyhow::Result<Vec<Secret>> {
20    let mut ret = vec![];
21    for item in dotenvy::from_path_iter(path)? {
22        let (name, value) = item?;
23        ret.push(Secret { name, value })
24    }
25    Ok(ret)
26}
27
28pub(super) async fn get_secret_by_name(
29    client: &WasmerClient,
30    app_id: &str,
31    secret_name: &str,
32) -> anyhow::Result<Option<BackendSecret>> {
33    wasmer_backend_api::query::get_app_secret_by_name(client, app_id, secret_name).await
34}
35pub(crate) async fn get_secrets(
36    client: &WasmerClient,
37    app_id: &str,
38) -> anyhow::Result<Vec<wasmer_backend_api::types::Secret>> {
39    wasmer_backend_api::query::get_all_app_secrets(client, app_id).await
40}
41
42pub(crate) async fn get_secret_value(
43    client: &WasmerClient,
44    secret: &wasmer_backend_api::types::Secret,
45) -> anyhow::Result<String> {
46    wasmer_backend_api::query::get_app_secret_value_by_id(client, secret.id.clone().into_inner())
47        .await?
48        .ok_or_else(|| {
49            anyhow::anyhow!(
50                "No value found for secret with name '{}'",
51                secret.name.bold()
52            )
53        })
54}
55
56pub(crate) async fn get_secret_value_by_name(
57    client: &WasmerClient,
58    app_id: &str,
59    secret_name: &str,
60) -> anyhow::Result<String> {
61    match get_secret_by_name(client, app_id, secret_name).await? {
62        Some(secret) => get_secret_value(client, &secret).await,
63        None => anyhow::bail!("No secret found with name {secret_name} for app {app_id}"),
64    }
65}
66
67pub(crate) async fn reveal_secrets(
68    client: &WasmerClient,
69    app_id: &str,
70) -> anyhow::Result<Vec<Secret>> {
71    let secrets = wasmer_backend_api::query::get_all_app_secrets(client, app_id).await?;
72    let mut ret = vec![];
73    for secret in secrets {
74        let name = secret.name.clone();
75        let value = get_secret_value(client, &secret).await?;
76        ret.push(Secret { name, value });
77    }
78
79    Ok(ret)
80}
81
82/// Utility struct used just to implement [`CliRender`].
83#[derive(Debug, serde::Serialize)]
84pub(super) struct BackendSecretWrapper(pub BackendSecret);
85
86impl From<BackendSecret> for BackendSecretWrapper {
87    fn from(value: BackendSecret) -> Self {
88        Self(value)
89    }
90}
91
92/// A secrets-specific app to retrieve an app identifier.
93pub(super) async fn get_app_id(
94    client: &WasmerClient,
95    app: Option<&AppIdent>,
96    app_dir_path: Option<&PathBuf>,
97    quiet: bool,
98    non_interactive: bool,
99) -> anyhow::Result<String> {
100    if let Some(app_id) = app {
101        let app = app_id.resolve(client).await?;
102        return Ok(app.id.into_inner());
103    }
104
105    let path = if let Some(path) = app_dir_path {
106        path.clone()
107    } else {
108        current_dir()?
109    };
110
111    if let Ok(r) = get_app_config_from_dir(&path) {
112        let (app, _) = r;
113
114        let app_name = if let Some(owner) = &app.owner {
115            format!(
116                "{owner}/{}",
117                &app.name.clone().context("App name has to be specified")?
118            )
119        } else {
120            app.name
121                .clone()
122                .context("App name has to be specified")?
123                .to_string()
124        };
125
126        let id = if let Some(id) = &app.app_id {
127            Some(id.clone())
128        } else if let Ok(app_ident) = AppIdent::from_str(&app_name) {
129            if let Ok(app) = app_ident.resolve(client).await {
130                Some(app.id.into_inner())
131            } else {
132                if !quiet {
133                    eprintln!(
134                        "{}: the app found in {} does not exist.\n{}: maybe it was not deployed yet?",
135                        "Warning".bold().yellow(),
136                        format!("'{}'", path.display()).dimmed(),
137                        "Hint".bold()
138                    );
139                }
140                None
141            }
142        } else {
143            None
144        };
145
146        if let Some(id) = id {
147            if !quiet {
148                if let Some(owner) = &app.owner {
149                    eprintln!(
150                        "Managing secrets related to app {} ({owner}).",
151                        app.name.context("App name has to be specified")?.bold()
152                    );
153                } else {
154                    eprintln!(
155                        "Managing secrets related to app {}.",
156                        app.name.context("App name has to be specified")?.bold()
157                    );
158                }
159            }
160            return Ok(id);
161        }
162    } else if let Some(path) = app_dir_path {
163        anyhow::bail!(
164            "No app configuration file found in path {}.",
165            path.display()
166        )
167    }
168
169    if non_interactive {
170        anyhow::bail!("No app id given. Provide one using the `--app` flag.")
171    } else {
172        let id = prompt_app_ident("Enter the name of the app")?;
173        let app = id.resolve(client).await?;
174        Ok(app.id.into_inner())
175    }
176}