wasmer_cli/commands/app/cdn/
status.rs

1use bytesize::ByteSize;
2use comfy_table::{Cell, Table};
3use time::{Duration, OffsetDateTime};
4
5use super::{AppIdentOpts, WasmerEnv};
6use crate::{
7    commands::AsyncCliCommand,
8    opts::ItemFormatOpts,
9    utils::render::{CliRender, ListFormat},
10};
11
12/// Show CDN cache status for an app.
13#[derive(clap::Parser, Debug)]
14pub struct CmdAppCdnStatus {
15    #[clap(flatten)]
16    pub env: WasmerEnv,
17
18    #[clap(flatten)]
19    pub fmt: ItemFormatOpts,
20
21    #[clap(flatten)]
22    pub ident: AppIdentOpts,
23
24    /// Include CDN cache metrics for the last 30 days.
25    #[clap(long)]
26    pub with_metrics: bool,
27}
28
29#[async_trait::async_trait]
30impl AsyncCliCommand for CmdAppCdnStatus {
31    type Output = ();
32
33    async fn run_async(self) -> Result<(), anyhow::Error> {
34        let client = self.env.client()?;
35        let (_ident, app) = self.ident.load_app(&client).await?;
36        let cdn_status = wasmer_backend_api::query::app_cdn_cache_status(
37            &client,
38            wasmer_backend_api::types::GetAppCdnCacheStatusVars {
39                app: app.id.clone(),
40            },
41        )
42        .await?;
43
44        let metrics = if self.with_metrics {
45            let end_at = OffsetDateTime::now_utc();
46            let start_at = end_at - Duration::days(30);
47            let metrics = wasmer_backend_api::query::app_cdn_cache_metrics(
48                &client,
49                wasmer_backend_api::types::GetAppCdnCacheMetricsVars {
50                    app: app.id.clone(),
51                    start_at: start_at.try_into()?,
52                    end_at: end_at.try_into()?,
53                    grouped_by: wasmer_backend_api::types::MetricGrouping::ByDay,
54                },
55            )
56            .await?;
57            Some(CdnCacheMetricsStatus::from(metrics))
58        } else {
59            None
60        };
61
62        let status = CdnCacheStatus {
63            app: app.name,
64            owner: app.owner.global_name,
65            enabled: cdn_status.cdn_cache_enabled,
66            last_purged_at: cdn_status.cdn_cache_purged_at.map(|dt| dt.0),
67            metrics,
68        };
69
70        println!("{}", self.fmt.get().render(&status));
71
72        Ok(())
73    }
74}
75
76#[derive(Debug, serde::Serialize)]
77struct CdnCacheStatus {
78    app: String,
79    owner: String,
80    enabled: bool,
81    last_purged_at: Option<String>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    metrics: Option<CdnCacheMetricsStatus>,
84}
85
86#[derive(Debug, serde::Serialize)]
87struct CdnCacheMetricsStatus {
88    cached_requests: i64,
89    total_requests: i64,
90    cached_network_egress_bytes: i64,
91    total_network_egress_bytes: i64,
92}
93
94impl From<wasmer_backend_api::types::AppCdnCacheMetrics> for CdnCacheMetricsStatus {
95    fn from(value: wasmer_backend_api::types::AppCdnCacheMetrics) -> Self {
96        let requests = value.grouped_metrics.totals.requests;
97        Self {
98            cached_requests: requests.cached_requests.0,
99            total_requests: requests.total_requests.0,
100            cached_network_egress_bytes: requests.data_cached_bytes.0,
101            total_network_egress_bytes: requests.data_served_bytes.0,
102        }
103    }
104}
105
106impl CliRender for CdnCacheStatus {
107    fn render_item_table(&self) -> String {
108        let mut table = Table::new();
109        table.load_preset(comfy_table::presets::NOTHING);
110        table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
111        table.set_header(vec![
112            Cell::new("App"),
113            Cell::new("Owner"),
114            Cell::new("Status"),
115            Cell::new("Last purge"),
116            Cell::new("Cached requests"),
117            Cell::new("Cached egress"),
118        ]);
119
120        let (cached_requests, cached_egress) = match &self.metrics {
121            Some(metrics) => (
122                format!("{} / {}", metrics.cached_requests, metrics.total_requests),
123                format!(
124                    "{} / {}",
125                    ByteSize(metrics.cached_network_egress_bytes as u64),
126                    ByteSize(metrics.total_network_egress_bytes as u64)
127                ),
128            ),
129            None => ("-".to_string(), "-".to_string()),
130        };
131
132        table.add_row(vec![
133            Cell::new(&self.app),
134            Cell::new(&self.owner),
135            Cell::new(if self.enabled { "enabled" } else { "disabled" }),
136            Cell::new(self.last_purged_at.as_deref().unwrap_or("-")),
137            Cell::new(cached_requests),
138            Cell::new(cached_egress),
139        ]);
140
141        table.to_string()
142    }
143
144    fn render_list(_items: &[Self], _format: ListFormat) -> String {
145        unreachable!("CDN cache status is rendered as a single item")
146    }
147
148    fn render_list_table(items: &[Self]) -> String {
149        items
150            .iter()
151            .map(Self::render_item_table)
152            .collect::<Vec<_>>()
153            .join("\n\n")
154    }
155}