wasmer_cli/commands/app/volumes/credentials/
mod.rs1pub(super) mod rotate_secrets;
2
3use crate::{
4 commands::{AsyncCliCommand, app::util::AppIdentOpts},
5 config::WasmerEnv,
6};
7
8#[derive(clap::Parser, Debug)]
13pub struct CmdAppVolumesCredentials {
14 #[clap(flatten)]
15 pub env: WasmerEnv,
16
17 #[clap(flatten)]
18 pub fmt: ItemFormatOpts,
19
20 #[clap(flatten)]
21 pub ident: AppIdentOpts,
22}
23
24#[async_trait::async_trait]
25impl AsyncCliCommand for CmdAppVolumesCredentials {
26 type Output = ();
27
28 async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
29 let client = self.env.client()?;
30 let (_ident, app) = self.ident.load_app(&client).await?;
31
32 let volumes = super::list_volumes(&client, &app.owner.global_name, &app.name).await?;
35
36 let with_creds: Vec<_> = volumes
37 .iter()
38 .filter_map(|volume| volume.s3.as_ref().map(|s3| (volume, s3)))
39 .collect();
40
41 if with_creds.is_empty() {
42 eprintln!(
44 "App {} has no S3-enabled volumes with credentials. \
45 Enable S3 with `wasmer app volume enable-s3`.",
46 app.name
47 );
48 }
49
50 let records: Vec<S3CredentialRecord> = with_creds
51 .into_iter()
52 .map(|(volume, s3)| S3CredentialRecord {
53 app_name: app.name.clone(),
54 volume: volume.mount_path.clone(),
55 access_key: s3.access_key.clone(),
56 secret_key: s3.secret_key.clone(),
57 endpoint: s3.endpoint.clone(),
58 })
59 .collect();
60
61 println!("{}", render_s3_credentials(self.fmt.format, &records));
62
63 Ok(())
64 }
65}
66
67#[derive(Debug, serde::Serialize)]
69pub(crate) struct S3CredentialRecord {
70 pub app_name: String,
71 pub volume: String,
73 pub access_key: String,
74 pub secret_key: String,
75 pub endpoint: String,
76}
77
78pub(crate) fn render_s3_credentials(
85 format: CredsItemFormat,
86 records: &[S3CredentialRecord],
87) -> String {
88 match format {
89 CredsItemFormat::Rclone => records.iter().map(render_rclone_section).collect(),
90 CredsItemFormat::Json => serde_json::to_string_pretty(records).unwrap(),
91 CredsItemFormat::Yaml => serde_yaml::to_string(records).unwrap(),
92 CredsItemFormat::Table => {
93 let mut table = comfy_table::Table::new();
94 table.add_row(vec![
95 "App name",
96 "Volume",
97 "Access key",
98 "Secret key",
99 "Endpoint",
100 ]);
101 for record in records {
102 table.add_row(vec![
103 record.app_name.as_str(),
104 record.volume.as_str(),
105 record.access_key.as_str(),
106 record.secret_key.as_str(),
107 record.endpoint.as_str(),
108 ]);
109 }
110 table.to_string()
111 }
112 }
113}
114
115fn render_rclone_section(record: &S3CredentialRecord) -> String {
117 let S3CredentialRecord {
118 app_name,
119 volume,
120 access_key,
121 secret_key,
122 endpoint,
123 } = record;
124 let section = format!(
125 "edge-{app_name}-{}",
126 volume.trim_start_matches('/').replace('/', "-")
127 );
128 format!(
129 r#"
130[{section}]
131# rclone configuration for volume {volume} of {app_name}
132type = s3
133provider = Other
134acl = private
135access_key_id = {access_key}
136secret_access_key = {secret_key}
137endpoint = {endpoint}
138
139"#
140 )
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
150pub enum CredsItemFormat {
151 Json,
152 Yaml,
153 Table,
154 Rclone,
155}
156
157#[derive(clap::Parser, Debug)]
159pub struct ItemFormatOpts {
160 #[clap(short = 'f', long, default_value = "rclone")]
162 pub format: CredsItemFormat,
163}