pub(super) mod rotate_secrets;
use crate::{
commands::{AsyncCliCommand, app::util::AppIdentOpts},
config::WasmerEnv,
};
#[derive(clap::Parser, Debug)]
pub struct CmdAppVolumesCredentials {
#[clap(flatten)]
pub env: WasmerEnv,
#[clap(flatten)]
pub fmt: ItemFormatOpts,
#[clap(flatten)]
pub ident: AppIdentOpts,
}
#[async_trait::async_trait]
impl AsyncCliCommand for CmdAppVolumesCredentials {
type Output = ();
async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
let client = self.env.client()?;
let (_ident, app) = self.ident.load_app(&client).await?;
let volumes = super::list_volumes(&client, &app.owner.global_name, &app.name).await?;
let with_creds: Vec<_> = volumes
.iter()
.filter_map(|volume| volume.s3.as_ref().map(|s3| (volume, s3)))
.collect();
if with_creds.is_empty() {
eprintln!(
"App {} has no S3-enabled volumes with credentials. \
Enable S3 with `wasmer app volume enable-s3`.",
app.name
);
}
let records: Vec<S3CredentialRecord> = with_creds
.into_iter()
.map(|(volume, s3)| S3CredentialRecord {
app_name: app.name.clone(),
volume: volume.mount_path.clone(),
access_key: s3.access_key.clone(),
secret_key: s3.secret_key.clone(),
endpoint: s3.endpoint.clone(),
})
.collect();
println!("{}", render_s3_credentials(self.fmt.format, &records));
Ok(())
}
}
#[derive(Debug, serde::Serialize)]
pub(crate) struct S3CredentialRecord {
pub app_name: String,
pub volume: String,
pub access_key: String,
pub secret_key: String,
pub endpoint: String,
}
pub(crate) fn render_s3_credentials(
format: CredsItemFormat,
records: &[S3CredentialRecord],
) -> String {
match format {
CredsItemFormat::Rclone => records.iter().map(render_rclone_section).collect(),
CredsItemFormat::Json => serde_json::to_string_pretty(records).unwrap(),
CredsItemFormat::Yaml => serde_yaml::to_string(records).unwrap(),
CredsItemFormat::Table => {
let mut table = comfy_table::Table::new();
table.add_row(vec![
"App name",
"Volume",
"Access key",
"Secret key",
"Endpoint",
]);
for record in records {
table.add_row(vec![
record.app_name.as_str(),
record.volume.as_str(),
record.access_key.as_str(),
record.secret_key.as_str(),
record.endpoint.as_str(),
]);
}
table.to_string()
}
}
}
fn render_rclone_section(record: &S3CredentialRecord) -> String {
let S3CredentialRecord {
app_name,
volume,
access_key,
secret_key,
endpoint,
} = record;
let section = format!(
"edge-{app_name}-{}",
volume.trim_start_matches('/').replace('/', "-")
);
format!(
r#"
[{section}]
# rclone configuration for volume {volume} of {app_name}
type = s3
provider = Other
acl = private
access_key_id = {access_key}
secret_access_key = {secret_key}
endpoint = {endpoint}
"#
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
pub enum CredsItemFormat {
Json,
Yaml,
Table,
Rclone,
}
#[derive(clap::Parser, Debug)]
pub struct ItemFormatOpts {
#[clap(short = 'f', long, default_value = "rclone")]
pub format: CredsItemFormat,
}