sozu_command_lib/proto/
display.rs

1use std::{
2    collections::{BTreeMap, HashMap, HashSet},
3    fmt::{self, Display, Formatter},
4    net::SocketAddr,
5};
6
7use prettytable::{cell, row, Row, Table};
8use time::format_description;
9use x509_parser::time::ASN1Time;
10
11use crate::{
12    proto::{
13        command::{
14            filtered_metrics, protobuf_endpoint, request::RequestType,
15            response_content::ContentType, AggregatedMetrics, AvailableMetrics, CertificateAndKey,
16            CertificateSummary, CertificatesWithFingerprints, ClusterMetrics, CustomHttpAnswers,
17            Event, EventKind, FilteredMetrics, HttpEndpoint, HttpListenerConfig,
18            HttpsListenerConfig, ListOfCertificatesByAddress, ListedFrontends, ListenersList,
19            ProtobufEndpoint, QueryCertificatesFilters, RequestCounts, Response, ResponseContent,
20            ResponseStatus, RunState, SocketAddress, TlsVersion, WorkerInfos, WorkerMetrics,
21            WorkerResponses,
22        },
23        DisplayError,
24    },
25    AsString,
26};
27
28use super::command::FilteredHistogram;
29
30impl Display for CertificateAndKey {
31    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
32        let versions = self.versions.iter().fold(String::new(), |acc, tls_v| {
33            acc + " "
34                + match TlsVersion::try_from(*tls_v) {
35                    Ok(v) => v.as_str_name(),
36                    Err(_) => "",
37                }
38        });
39        write!(
40            f,
41            "\tcertificate: {}\n\tcertificate_chain: {:?}\n\tkey: {}\n\tTLS versions: {}\n\tnames: {:?}",
42            self.certificate, self.certificate_chain, self.key, versions,
43            concatenate_vector(&self.names)
44        )
45    }
46}
47
48impl Display for CertificateSummary {
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        write!(f, "{}:\t{}", self.fingerprint, self.domain)
51    }
52}
53
54impl Display for QueryCertificatesFilters {
55    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56        if let Some(d) = self.domain.clone() {
57            write!(f, "domain:{}", d)
58        } else if let Some(fp) = self.fingerprint.clone() {
59            write!(f, "domain:{}", fp)
60        } else {
61            write!(f, "all certificates")
62        }
63    }
64}
65
66pub fn concatenate_vector(vec: &[String]) -> String {
67    vec.join(", ")
68}
69
70pub fn format_request_type(request_type: &RequestType) -> &str {
71    match request_type {
72        RequestType::SaveState(_) => "SaveState",
73        RequestType::LoadState(_) => "LoadState",
74        RequestType::CountRequests(_) => "CountRequests",
75        RequestType::ListWorkers(_) => "ListWorkers",
76        RequestType::ListFrontends(_) => "ListFrontends",
77        RequestType::ListListeners(_) => "ListListeners",
78        RequestType::LaunchWorker(_) => "LaunchWorker",
79        RequestType::UpgradeMain(_) => "UpgradeMain",
80        RequestType::UpgradeWorker(_) => "UpgradeWorker",
81        RequestType::SubscribeEvents(_) => "SubscribeEvents",
82        RequestType::ReloadConfiguration(_) => "ReloadConfiguration",
83        RequestType::Status(_) => "Status",
84        RequestType::AddCluster(_) => "AddCluster",
85        RequestType::RemoveCluster(_) => "RemoveCluster",
86        RequestType::AddHttpFrontend(_) => "AddHttpFrontend",
87        RequestType::RemoveHttpFrontend(_) => "RemoveHttpFrontend",
88        RequestType::AddHttpsFrontend(_) => "AddHttpsFrontend",
89        RequestType::RemoveHttpsFrontend(_) => "RemoveHttpsFrontend",
90        RequestType::AddCertificate(_) => "AddCertificate",
91        RequestType::ReplaceCertificate(_) => "ReplaceCertificate",
92        RequestType::RemoveCertificate(_) => "RemoveCertificate",
93        RequestType::AddTcpFrontend(_) => "AddTcpFrontend",
94        RequestType::RemoveTcpFrontend(_) => "RemoveTcpFrontend",
95        RequestType::AddBackend(_) => "AddBackend",
96        RequestType::RemoveBackend(_) => "RemoveBackend",
97        RequestType::AddHttpListener(_) => "AddHttpListener",
98        RequestType::AddHttpsListener(_) => "AddHttpsListener",
99        RequestType::AddTcpListener(_) => "AddTcpListener",
100        RequestType::RemoveListener(_) => "RemoveListener",
101        RequestType::ActivateListener(_) => "ActivateListener",
102        RequestType::DeactivateListener(_) => "DeactivateListener",
103        RequestType::QueryClusterById(_) => "QueryClusterById",
104        RequestType::QueryClustersByDomain(_) => "QueryClustersByDomain",
105        RequestType::QueryClustersHashes(_) => "QueryClustersHashes",
106        RequestType::QueryMetrics(_) => "QueryMetrics",
107        RequestType::SoftStop(_) => "SoftStop",
108        RequestType::HardStop(_) => "HardStop",
109        RequestType::ConfigureMetrics(_) => "ConfigureMetrics",
110        RequestType::Logging(_) => "Logging",
111        RequestType::ReturnListenSockets(_) => "ReturnListenSockets",
112        RequestType::QueryCertificatesFromTheState(_) => "QueryCertificatesFromTheState",
113        RequestType::QueryCertificatesFromWorkers(_) => "QueryCertificatesFromWorkers",
114    }
115}
116
117pub fn print_json_response<T: ::serde::Serialize>(input: &T) -> Result<(), DisplayError> {
118    let pretty_json = serde_json::to_string_pretty(&input).map_err(DisplayError::Json)?;
119    println!("{pretty_json}");
120    Ok(())
121}
122
123impl Response {
124    pub fn display(&self, json: bool) -> Result<(), DisplayError> {
125        match self.status() {
126            ResponseStatus::Ok => {
127                // avoid displaying anything else than JSON
128                if !json {
129                    println!("Success: {}", self.message)
130                }
131            }
132            ResponseStatus::Failure => println!("Failure: {}", self.message),
133            ResponseStatus::Processing => {
134                return Err(DisplayError::WrongResponseType(
135                    "ResponseStatus::Processing".to_string(),
136                ))
137            }
138        }
139
140        match &self.content {
141            Some(content) => content.display(json),
142            None => {
143                if json {
144                    println!("{{}}");
145                } else {
146                    println!("No content");
147                }
148                Ok(())
149            }
150        }
151    }
152}
153
154impl ResponseContent {
155    fn display(&self, json: bool) -> Result<(), DisplayError> {
156        let content_type = match &self.content_type {
157            Some(content_type) => content_type,
158            None => return Ok(println!("No content")),
159        };
160
161        if json {
162            return print_json_response(&content_type);
163        }
164
165        match content_type {
166            ContentType::Workers(worker_infos) => print_status(worker_infos),
167            ContentType::Metrics(aggr_metrics) => print_metrics(aggr_metrics),
168            ContentType::FrontendList(frontends) => print_frontends(frontends),
169            ContentType::ListenersList(listeners) => print_listeners(listeners),
170            ContentType::WorkerMetrics(worker_metrics) => print_worker_metrics(worker_metrics),
171            ContentType::AvailableMetrics(list) => print_available_metrics(list),
172            ContentType::RequestCounts(request_counts) => print_request_counts(request_counts),
173            ContentType::CertificatesWithFingerprints(certs) => {
174                print_certificates_with_validity(certs)
175            }
176            ContentType::WorkerResponses(worker_responses) => {
177                // exception when displaying clusters
178                if worker_responses.contain_cluster_infos() {
179                    print_cluster_infos(worker_responses)
180                } else if worker_responses.contain_cluster_hashes() {
181                    print_cluster_hashes(worker_responses)
182                } else {
183                    print_responses_by_worker(worker_responses, json)
184                }
185            }
186            ContentType::Clusters(_) | ContentType::ClusterHashes(_) => Ok(()), // not displayed directly, see print_cluster_responses
187            ContentType::CertificatesByAddress(certs) => print_certificates_by_address(certs),
188            ContentType::Event(_event) => Ok(()), // not event displayed yet!
189        }
190    }
191}
192
193impl WorkerResponses {
194    fn contain_cluster_infos(&self) -> bool {
195        for (_worker_id, response) in self.map.iter() {
196            if let Some(content_type) = &response.content_type {
197                if matches!(content_type, ContentType::Clusters(_)) {
198                    return true;
199                }
200            }
201        }
202        false
203    }
204
205    fn contain_cluster_hashes(&self) -> bool {
206        for (_worker_id, response) in self.map.iter() {
207            if let Some(content_type) = &response.content_type {
208                if matches!(content_type, ContentType::ClusterHashes(_)) {
209                    return true;
210                }
211            }
212        }
213        false
214    }
215}
216
217pub fn print_status(worker_infos: &WorkerInfos) -> Result<(), DisplayError> {
218    let mut table = Table::new();
219    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
220    table.add_row(row!["worker id", "pid", "run state"]);
221
222    let mut sorted_infos = worker_infos.vec.clone();
223    sorted_infos.sort_by_key(|worker| worker.id);
224
225    for worker_info in &sorted_infos {
226        let row = row!(
227            worker_info.id,
228            worker_info.pid,
229            RunState::try_from(worker_info.run_state)
230                .map_err(DisplayError::DecodeError)?
231                .as_str_name()
232        );
233        table.add_row(row);
234    }
235
236    table.printstd();
237    Ok(())
238}
239
240pub fn print_metrics(aggregated_metrics: &AggregatedMetrics) -> Result<(), DisplayError> {
241    // main process metrics
242    println!("\nMAIN PROCESS\n============");
243    print_proxy_metrics(&aggregated_metrics.main);
244
245    if !aggregated_metrics.proxying.is_empty() {
246        println!("\nPROXYING\n============");
247        print_proxy_metrics(&aggregated_metrics.proxying);
248    }
249
250    // workers
251    for (worker_id, worker) in aggregated_metrics.workers.iter() {
252        if !worker.clusters.is_empty() && !worker.proxy.is_empty() {
253            println!("\nWorker {worker_id}\n=========");
254            print_worker_metrics(worker)?;
255        }
256    }
257
258    // clusters
259    if !aggregated_metrics.clusters.is_empty() {
260        println!("\nClusters\n=======");
261        print_cluster_metrics(&aggregated_metrics.clusters);
262    }
263
264    Ok(())
265}
266
267fn print_proxy_metrics(proxy_metrics: &BTreeMap<String, FilteredMetrics>) {
268    let filtered = filter_metrics(proxy_metrics);
269    print_gauges_and_counts(&filtered);
270    print_percentiles(&filtered);
271    print_histograms(&filtered);
272}
273
274fn print_worker_metrics(worker_metrics: &WorkerMetrics) -> Result<(), DisplayError> {
275    print_proxy_metrics(&worker_metrics.proxy);
276    print_cluster_metrics(&worker_metrics.clusters);
277
278    Ok(())
279}
280
281fn print_cluster_metrics(cluster_metrics: &BTreeMap<String, ClusterMetrics>) {
282    for (cluster_id, cluster_metrics_data) in cluster_metrics.iter() {
283        println!("\nCluster {cluster_id}\n--------");
284
285        let filtered = filter_metrics(&cluster_metrics_data.cluster);
286        print_gauges_and_counts(&filtered);
287        print_percentiles(&filtered);
288        print_histograms(&filtered);
289
290        // backend_id -> (metric_name -> value )
291        let mut backend_metric_acc: HashMap<String, BTreeMap<String, FilteredMetrics>> =
292            HashMap::new();
293
294        for backend in &cluster_metrics_data.backends {
295            for (metric_name, value) in &backend.metrics {
296                backend_metric_acc
297                    .entry(backend.backend_id.clone())
298                    .and_modify(|map| {
299                        map.insert(metric_name.clone(), value.clone());
300                    })
301                    .or_insert(BTreeMap::from([(metric_name.clone(), value.clone())]));
302            }
303        }
304
305        for (backend_id, metrics) in backend_metric_acc {
306            println!("\n{cluster_id}/{backend_id}\n--------");
307            let filtered = filter_metrics(&metrics);
308            print_gauges_and_counts(&filtered);
309            print_percentiles(&filtered);
310            print_histograms(&filtered);
311        }
312    }
313}
314
315fn filter_metrics(
316    metrics: &BTreeMap<String, FilteredMetrics>,
317) -> BTreeMap<String, FilteredMetrics> {
318    let mut filtered_metrics = BTreeMap::new();
319
320    for (metric_key, filtered_value) in metrics.iter() {
321        filtered_metrics.insert(
322            metric_key.replace('\t', ".").to_string(),
323            filtered_value.clone(),
324        );
325    }
326    filtered_metrics
327}
328
329fn print_gauges_and_counts(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
330    let mut titles: Vec<String> = filtered_metrics
331        .iter()
332        .filter_map(|(title, filtered_data)| match filtered_data.inner {
333            Some(filtered_metrics::Inner::Count(_)) | Some(filtered_metrics::Inner::Gauge(_)) => {
334                Some(title.to_owned())
335            }
336            _ => None,
337        })
338        .collect();
339
340    // sort the titles so they always appear in the same order
341    titles.sort();
342
343    if titles.is_empty() {
344        return;
345    }
346
347    let mut table = Table::new();
348    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
349
350    table.set_titles(Row::new(vec![cell!(""), cell!("gauge"), cell!("count")]));
351
352    for title in titles {
353        let mut row = vec![cell!(title)];
354        match filtered_metrics.get(&title) {
355            Some(filtered_metrics) => match filtered_metrics.inner {
356                Some(filtered_metrics::Inner::Count(c)) => {
357                    row.push(cell!(""));
358                    row.push(cell!(c))
359                }
360                Some(filtered_metrics::Inner::Gauge(c)) => {
361                    row.push(cell!(c));
362                    row.push(cell!(""))
363                }
364                _ => {}
365            },
366            _ => row.push(cell!("")),
367        }
368        table.add_row(Row::new(row));
369    }
370
371    table.printstd();
372}
373
374fn print_percentiles(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
375    let mut percentile_titles: Vec<String> = filtered_metrics
376        .iter()
377        .filter_map(|(title, filtered_data)| match filtered_data.inner.clone() {
378            Some(filtered_metrics::Inner::Percentiles(_)) => Some(title.to_owned()),
379            _ => None,
380        })
381        .collect();
382
383    // sort the metrics so they always appear in the same order
384    percentile_titles.sort();
385
386    if percentile_titles.is_empty() {
387        return;
388    }
389
390    let mut percentile_table = Table::new();
391    percentile_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
392
393    percentile_table.set_titles(Row::new(vec![
394        cell!("Percentiles"),
395        cell!("samples"),
396        cell!("p50"),
397        cell!("p90"),
398        cell!("p99"),
399        cell!("p99.9"),
400        cell!("p99.99"),
401        cell!("p99.999"),
402        cell!("p100"),
403    ]));
404
405    for title in percentile_titles {
406        if let Some(FilteredMetrics {
407            inner: Some(filtered_metrics::Inner::Percentiles(percentiles)),
408        }) = filtered_metrics.get(&title)
409        {
410            percentile_table.add_row(Row::new(vec![
411                cell!(title),
412                cell!(percentiles.samples),
413                cell!(percentiles.p_50),
414                cell!(percentiles.p_90),
415                cell!(percentiles.p_99),
416                cell!(percentiles.p_99_9),
417                cell!(percentiles.p_99_99),
418                cell!(percentiles.p_99_999),
419                cell!(percentiles.p_100),
420            ]));
421        } else {
422            println!("Something went VERY wrong here");
423        }
424    }
425
426    percentile_table.printstd();
427}
428
429fn print_histograms(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
430    let histograms: BTreeMap<String, FilteredHistogram> = filtered_metrics
431        .iter()
432        .filter_map(|(name, metric)| match metric.inner.clone() {
433            Some(filtered_metrics::Inner::Histogram(hist)) => Some((name.to_owned(), hist)),
434            _ => None,
435        })
436        .collect();
437
438    let mut histogram_titles: Vec<String> = histograms.keys().map(ToOwned::to_owned).collect();
439    histogram_titles.sort();
440    if histogram_titles.is_empty() {
441        return;
442    }
443
444    let mut histogram_table = Table::new();
445    histogram_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
446
447    let mut first_row = Row::new(vec![cell!("Histograms (ms)"), cell!("sum"), cell!("count")]);
448
449    let biggest_hist_length = histograms
450        .values()
451        .map(|hist| hist.buckets.len())
452        .max()
453        .unwrap_or(0);
454
455    // 0, 1, 3, 7... in the upper row
456    for exponent in 0..biggest_hist_length {
457        first_row.add_cell(cell!(format!("{}", (1 << exponent) - 1)));
458    }
459    histogram_table.set_titles(first_row);
460
461    for title in histogram_titles {
462        if let Some(hist) = histograms.get(&title) {
463            let trimmed_name = title.strip_suffix("_histogram").unwrap_or_default();
464            let mut row = Row::new(vec![
465                cell!(trimmed_name),
466                cell!(hist.sum),
467                cell!(hist.count),
468            ]);
469            // display the count by bucket, not the incremented count
470            let mut last_bucket_count = 0;
471            for bucket in &hist.buckets {
472                row.add_cell(cell!(bucket.count - last_bucket_count));
473                last_bucket_count = bucket.count;
474            }
475            histogram_table.add_row(row);
476        }
477    }
478
479    histogram_table.printstd();
480}
481
482fn print_available_metrics(available_metrics: &AvailableMetrics) -> Result<(), DisplayError> {
483    println!("Available metrics on the proxy level:");
484    for metric_name in &available_metrics.proxy_metrics {
485        println!("\t{metric_name}");
486    }
487    println!("Available metrics on the cluster level:");
488    for metric_name in &available_metrics.cluster_metrics {
489        println!("\t{metric_name}");
490    }
491    Ok(())
492}
493
494fn print_frontends(frontends: &ListedFrontends) -> Result<(), DisplayError> {
495    trace!(" We received this frontends to display {:#?}", frontends);
496    // HTTP frontends
497    if !frontends.http_frontends.is_empty() {
498        let mut table = Table::new();
499        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
500        table.add_row(row!["HTTP frontends "]);
501        table.add_row(row![
502            "cluster_id",
503            "address",
504            "hostname",
505            "path",
506            "method",
507            "position",
508            "tags"
509        ]);
510        for http_frontend in frontends.http_frontends.iter() {
511            table.add_row(row!(
512                http_frontend
513                    .cluster_id
514                    .clone()
515                    .unwrap_or("Deny".to_owned()),
516                http_frontend.address.to_string(),
517                http_frontend.hostname.to_string(),
518                format!("{:?}", http_frontend.path),
519                format!("{:?}", http_frontend.method),
520                format!("{:?}", http_frontend.position),
521                format_tags_to_string(&http_frontend.tags)
522            ));
523        }
524        table.printstd();
525    }
526
527    // HTTPS frontends
528    if !frontends.https_frontends.is_empty() {
529        let mut table = Table::new();
530        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
531        table.add_row(row!["HTTPS frontends"]);
532        table.add_row(row![
533            "cluster_id",
534            "address",
535            "hostname",
536            "path",
537            "method",
538            "position",
539            "tags"
540        ]);
541        for https_frontend in frontends.https_frontends.iter() {
542            table.add_row(row!(
543                https_frontend
544                    .cluster_id
545                    .clone()
546                    .unwrap_or("Deny".to_owned()),
547                https_frontend.address.to_string(),
548                https_frontend.hostname.to_string(),
549                format!("{:?}", https_frontend.path),
550                format!("{:?}", https_frontend.method),
551                format!("{:?}", https_frontend.position),
552                format_tags_to_string(&https_frontend.tags)
553            ));
554        }
555        table.printstd();
556    }
557
558    // TCP frontends
559    if !frontends.tcp_frontends.is_empty() {
560        let mut table = Table::new();
561        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
562        table.add_row(row!["TCP frontends  "]);
563        table.add_row(row!["Cluster ID", "address", "tags"]);
564        for tcp_frontend in frontends.tcp_frontends.iter() {
565            table.add_row(row!(
566                tcp_frontend.cluster_id,
567                tcp_frontend.address,
568                format_tags_to_string(&tcp_frontend.tags)
569            ));
570        }
571        table.printstd();
572    }
573    Ok(())
574}
575
576pub fn print_listeners(listeners_list: &ListenersList) -> Result<(), DisplayError> {
577    println!("\nHTTP LISTENERS\n================");
578
579    for (_, http_listener) in listeners_list.http_listeners.iter() {
580        println!("{}", http_listener);
581    }
582
583    println!("\nHTTPS LISTENERS\n================");
584
585    for (_, https_listener) in listeners_list.https_listeners.iter() {
586        println!("{}", https_listener);
587    }
588
589    println!("\nTCP LISTENERS\n================");
590
591    if !listeners_list.tcp_listeners.is_empty() {
592        let mut table = Table::new();
593        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
594        table.add_row(row!["TCP frontends"]);
595        table.add_row(row![
596            "socket address",
597            "public address",
598            "expect proxy",
599            "front timeout",
600            "back timeout",
601            "connect timeout",
602            "activated"
603        ]);
604        for (_, tcp_listener) in listeners_list.tcp_listeners.iter() {
605            table.add_row(row![
606                format!("{:?}", tcp_listener.address),
607                format!("{:?}", tcp_listener.public_address),
608                tcp_listener.expect_proxy,
609                tcp_listener.front_timeout,
610                tcp_listener.back_timeout,
611                tcp_listener.connect_timeout,
612                tcp_listener.active,
613            ]);
614        }
615        table.printstd();
616    }
617    Ok(())
618}
619
620fn print_cluster_infos(worker_responses: &WorkerResponses) -> Result<(), DisplayError> {
621    let mut cluster_table = create_cluster_table(
622        vec!["id", "sticky_session", "https_redirect"],
623        &worker_responses.map,
624    );
625
626    let mut frontend_table =
627        create_cluster_table(vec!["id", "hostname", "path"], &worker_responses.map);
628
629    let mut https_frontend_table =
630        create_cluster_table(vec!["id", "hostname", "path"], &worker_responses.map);
631
632    let mut tcp_frontend_table = create_cluster_table(vec!["id", "address"], &worker_responses.map);
633
634    let mut backend_table = create_cluster_table(
635        vec!["backend id", "IP address", "Backup"],
636        &worker_responses.map,
637    );
638
639    let worker_ids: HashSet<&String> = worker_responses.map.keys().collect();
640
641    let mut cluster_infos = BTreeMap::new();
642    let mut http_frontends = BTreeMap::new();
643    let mut https_frontends = BTreeMap::new();
644    let mut tcp_frontends = BTreeMap::new();
645    let mut backends = BTreeMap::new();
646
647    for (worker_id, response_content) in worker_responses.map.iter() {
648        if let Some(ContentType::Clusters(clusters)) = &response_content.content_type {
649            for cluster in clusters.vec.iter() {
650                if cluster.configuration.is_some() {
651                    let entry = cluster_infos.entry(cluster).or_insert(Vec::new());
652                    entry.push(worker_id.to_owned());
653                }
654
655                for frontend in cluster.http_frontends.iter() {
656                    let entry = http_frontends.entry(frontend).or_insert(Vec::new());
657                    entry.push(worker_id.to_owned());
658                }
659
660                for frontend in cluster.https_frontends.iter() {
661                    let entry = https_frontends.entry(frontend).or_insert(Vec::new());
662                    entry.push(worker_id.to_owned());
663                }
664
665                for frontend in cluster.tcp_frontends.iter() {
666                    let entry = tcp_frontends.entry(frontend).or_insert(Vec::new());
667                    entry.push(worker_id.to_owned());
668                }
669
670                for backend in cluster.backends.iter() {
671                    let entry = backends.entry(backend).or_insert(Vec::new());
672                    entry.push(worker_id.to_owned());
673                }
674            }
675        }
676    }
677
678    if cluster_infos.is_empty() {
679        println!("no cluster found");
680        return Ok(());
681    }
682
683    println!("Cluster level configuration:\n");
684
685    for (cluster_info, workers_the_cluster_is_present_on) in cluster_infos.iter() {
686        let mut row = Vec::new();
687        row.push(cell!(cluster_info
688            .configuration
689            .as_ref()
690            .map(|conf| conf.cluster_id.to_owned())
691            .unwrap_or_else(|| String::from("None"))));
692        row.push(cell!(cluster_info
693            .configuration
694            .as_ref()
695            .map(|conf| conf.sticky_session)
696            .unwrap_or_else(|| false)));
697        row.push(cell!(cluster_info
698            .configuration
699            .as_ref()
700            .map(|conf| conf.https_redirect)
701            .unwrap_or_else(|| false)));
702
703        for worker in workers_the_cluster_is_present_on {
704            if worker_ids.contains(worker) {
705                row.push(cell!("X"));
706            } else {
707                row.push(cell!(""));
708            }
709        }
710
711        cluster_table.add_row(Row::new(row));
712    }
713
714    cluster_table.printstd();
715
716    println!("\nHTTP frontends configuration for:\n");
717
718    for (key, values) in http_frontends.iter() {
719        let mut row = Vec::new();
720        match &key.cluster_id {
721            Some(cluster_id) => row.push(cell!(cluster_id)),
722            None => row.push(cell!("-")),
723        }
724        row.push(cell!(key.hostname));
725        row.push(cell!(key.path));
726
727        for val in values.iter() {
728            if worker_ids.contains(val) {
729                row.push(cell!("X"));
730            } else {
731                row.push(cell!(""));
732            }
733        }
734
735        frontend_table.add_row(Row::new(row));
736    }
737
738    frontend_table.printstd();
739
740    println!("\nHTTPS frontends configuration for:\n");
741
742    for (key, values) in https_frontends.iter() {
743        let mut row = Vec::new();
744        match &key.cluster_id {
745            Some(cluster_id) => row.push(cell!(cluster_id)),
746            None => row.push(cell!("-")),
747        }
748        row.push(cell!(key.hostname));
749        row.push(cell!(key.path));
750
751        for val in values.iter() {
752            if worker_ids.contains(val) {
753                row.push(cell!("X"));
754            } else {
755                row.push(cell!(""));
756            }
757        }
758
759        https_frontend_table.add_row(Row::new(row));
760    }
761
762    https_frontend_table.printstd();
763
764    println!("\nTCP frontends configuration:\n");
765
766    for (key, values) in tcp_frontends.iter() {
767        let mut row = vec![cell!(key.cluster_id), cell!(format!("{}", key.address))];
768
769        for val in values.iter() {
770            if worker_ids.contains(val) {
771                row.push(cell!(String::from("X")));
772            } else {
773                row.push(cell!(String::from("")));
774            }
775        }
776
777        tcp_frontend_table.add_row(Row::new(row));
778    }
779
780    tcp_frontend_table.printstd();
781
782    println!("\nbackends configuration:\n");
783
784    for (key, values) in backends.iter() {
785        let mut row = vec![
786            cell!(key.backend_id),
787            cell!(format!("{}", key.address)),
788            cell!(key
789                .backup
790                .map(|b| if b { "X" } else { "" })
791                .unwrap_or_else(|| "")),
792        ];
793
794        for val in values {
795            if worker_ids.contains(&val) {
796                row.push(cell!("X"));
797            } else {
798                row.push(cell!(""));
799            }
800        }
801
802        backend_table.add_row(Row::new(row));
803    }
804
805    backend_table.printstd();
806
807    Ok(())
808}
809
810/// display all clusters in a simplified table showing their hashes
811fn print_cluster_hashes(worker_responses: &WorkerResponses) -> Result<(), DisplayError> {
812    let mut clusters_table = Table::new();
813    clusters_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
814    let mut header = vec![cell!("cluster id")];
815    for worker_id in worker_responses.map.keys() {
816        header.push(cell!(format!("worker {}", worker_id)));
817    }
818    header.push(cell!("desynchronized"));
819    clusters_table.add_row(Row::new(header));
820
821    let mut cluster_hashes = HashMap::new();
822
823    for response_content in worker_responses.map.values() {
824        if let Some(ContentType::ClusterHashes(hashes)) = &response_content.content_type {
825            for (cluster_id, hash) in hashes.map.iter() {
826                cluster_hashes
827                    .entry(cluster_id)
828                    .or_insert(Vec::new())
829                    .push(hash);
830            }
831        }
832    }
833
834    for (cluster_id, hashes) in cluster_hashes.iter() {
835        let mut row = vec![cell!(cluster_id)];
836        for val in hashes.iter() {
837            row.push(cell!(format!("{val}")));
838        }
839
840        let hs: HashSet<&u64> = hashes.iter().cloned().collect();
841        if hs.len() > 1 {
842            row.push(cell!("X"));
843        } else {
844            row.push(cell!(""));
845        }
846
847        clusters_table.add_row(Row::new(row));
848    }
849
850    clusters_table.printstd();
851    Ok(())
852}
853
854fn print_responses_by_worker(
855    worker_responses: &WorkerResponses,
856    json: bool,
857) -> Result<(), DisplayError> {
858    for (worker_id, content) in worker_responses.map.iter() {
859        println!("Worker {}", worker_id);
860        content.display(json)?;
861    }
862
863    Ok(())
864}
865
866pub fn print_certificates_with_validity(
867    certs: &CertificatesWithFingerprints,
868) -> Result<(), DisplayError> {
869    if certs.certs.is_empty() {
870        return Ok(println!("No certificates match your request."));
871    }
872
873    let mut table = Table::new();
874    table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
875    table.add_row(row![
876        "fingeprint",
877        "valid not before",
878        "valide not after",
879        "domain names",
880    ]);
881
882    for (fingerprint, cert) in &certs.certs {
883        let (_unparsed, pem_certificate) =
884            x509_parser::pem::parse_x509_pem(cert.certificate.as_bytes())
885                .expect("Could not parse pem certificate");
886
887        let x509_certificate = pem_certificate
888            .parse_x509()
889            .expect("Could not parse x509 certificate");
890
891        let validity = x509_certificate.validity();
892
893        table.add_row(row!(
894            fingerprint,
895            format_datetime(validity.not_before)?,
896            format_datetime(validity.not_after)?,
897            concatenate_vector(&cert.names),
898        ));
899    }
900    table.printstd();
901
902    Ok(())
903}
904
905fn print_certificates_by_address(list: &ListOfCertificatesByAddress) -> Result<(), DisplayError> {
906    for certs in list.certificates.iter() {
907        println!("\t{}:", certs.address);
908
909        for summary in certs.certificate_summaries.iter() {
910            println!("\t\t{}", summary);
911        }
912    }
913    Ok(())
914}
915
916fn print_request_counts(request_counts: &RequestCounts) -> Result<(), DisplayError> {
917    let mut table = Table::new();
918    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
919    table.add_row(row!["request type", "count"]);
920
921    for (request_type, count) in &request_counts.map {
922        table.add_row(row!(request_type, count));
923    }
924    table.printstd();
925    Ok(())
926}
927
928fn format_tags_to_string(tags: &BTreeMap<String, String>) -> String {
929    tags.iter()
930        .map(|(k, v)| format!("{k}={v}"))
931        .collect::<Vec<_>>()
932        .join(", ")
933}
934
935fn list_string_vec(vec: &[String]) -> String {
936    let mut output = String::new();
937    for item in vec.iter() {
938        output.push_str(item);
939        output.push('\n');
940    }
941    output
942}
943
944// ISO 8601
945fn format_datetime(asn1_time: ASN1Time) -> Result<String, DisplayError> {
946    let datetime = asn1_time.to_datetime();
947
948    let formatted = datetime
949        .format(&format_description::well_known::Iso8601::DEFAULT)
950        .map_err(|_| DisplayError::DateTime)?;
951    Ok(formatted)
952}
953
954/// Creates an empty table of the form
955/// ```text
956/// ┌────────────┬─────────────┬───────────┬────────┐
957/// │            │ header      │ header    │ header │
958/// ├────────────┼─────────────┼───────────┼────────┤
959/// │ cluster_id │             │           │        │
960/// ├────────────┼─────────────┼───────────┼────────┤
961/// │ cluster_id │             │           │        │
962/// ├────────────┼─────────────┼───────────┼────────┤
963/// │ cluster_id │             │           │        │
964/// └────────────┴─────────────┴───────────┴────────┘
965/// ```
966fn create_cluster_table(headers: Vec<&str>, data: &BTreeMap<String, ResponseContent>) -> Table {
967    let mut table = Table::new();
968    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
969    let mut row_header: Vec<_> = headers.iter().map(|h| cell!(h)).collect();
970    for ref key in data.keys() {
971        row_header.push(cell!(&key));
972    }
973    table.add_row(Row::new(row_header));
974    table
975}
976
977impl Display for SocketAddress {
978    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
979        write!(f, "{}", SocketAddr::from(*self))
980    }
981}
982
983impl Display for ProtobufEndpoint {
984    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
985        match &self.inner {
986            Some(protobuf_endpoint::Inner::Http(HttpEndpoint {
987                method,
988                authority,
989                path,
990                status,
991                ..
992            })) => write!(
993                f,
994                "{} {} {} -> {}",
995                authority.as_string_or("-"),
996                method.as_string_or("-"),
997                path.as_string_or("-"),
998                status.as_string_or("-"),
999            ),
1000            Some(protobuf_endpoint::Inner::Tcp(_)) => {
1001                write!(f, "-")
1002            }
1003            None => Ok(()),
1004        }
1005    }
1006}
1007
1008impl Display for HttpListenerConfig {
1009    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1010        let mut table = Table::new();
1011        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1012        table.add_row(row!["socket address", format!("{:?}", self.address)]);
1013        table.add_row(row!["public address", format!("{:?}", self.public_address),]);
1014        for http_answer_row in CustomHttpAnswers::to_rows(&self.http_answers) {
1015            table.add_row(http_answer_row);
1016        }
1017        table.add_row(row!["expect proxy", self.expect_proxy]);
1018        table.add_row(row!["sticky name", self.sticky_name]);
1019        table.add_row(row!["front timeout", self.front_timeout]);
1020        table.add_row(row!["back timeout", self.back_timeout]);
1021        table.add_row(row!["connect timeout", self.connect_timeout]);
1022        table.add_row(row!["request timeout", self.request_timeout]);
1023        table.add_row(row!["activated", self.active]);
1024        write!(f, "{}", table)
1025    }
1026}
1027
1028impl Display for HttpsListenerConfig {
1029    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1030        let mut table = Table::new();
1031        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1032        let mut tls_versions = String::new();
1033        for tls_version in self.versions.iter() {
1034            tls_versions.push_str(&format!("{tls_version:?}\n"));
1035        }
1036
1037        table.add_row(row!["socket address", format!("{:?}", self.address)]);
1038        table.add_row(row!["public address", format!("{:?}", self.public_address)]);
1039        for http_answer_row in CustomHttpAnswers::to_rows(&self.http_answers) {
1040            table.add_row(http_answer_row);
1041        }
1042        table.add_row(row!["versions", tls_versions]);
1043        table.add_row(row!["cipher list", list_string_vec(&self.cipher_list),]);
1044        table.add_row(row!["cipher suites", list_string_vec(&self.cipher_suites),]);
1045        table.add_row(row![
1046            "signature algorithms",
1047            list_string_vec(&self.signature_algorithms),
1048        ]);
1049        table.add_row(row!["groups list", list_string_vec(&self.groups_list),]);
1050        table.add_row(row!["key", format!("{:?}", self.key),]);
1051        table.add_row(row!["expect proxy", self.expect_proxy]);
1052        table.add_row(row!["sticky name", self.sticky_name]);
1053        table.add_row(row!["front timeout", self.front_timeout]);
1054        table.add_row(row!["back timeout", self.back_timeout]);
1055        table.add_row(row!["connect timeout", self.connect_timeout]);
1056        table.add_row(row!["request timeout", self.request_timeout]);
1057        table.add_row(row!["activated", self.active]);
1058        write!(f, "{}", table)
1059    }
1060}
1061
1062impl CustomHttpAnswers {
1063    fn to_rows(option: &Option<Self>) -> Vec<Row> {
1064        let mut rows = Vec::new();
1065        if let Some(answers) = option {
1066            if let Some(a) = &answers.answer_301 {
1067                rows.push(row!("301", a));
1068            }
1069            if let Some(a) = &answers.answer_400 {
1070                rows.push(row!("400", a));
1071            }
1072            if let Some(a) = &answers.answer_404 {
1073                rows.push(row!("404", a));
1074            }
1075            if let Some(a) = &answers.answer_408 {
1076                rows.push(row!("408", a));
1077            }
1078            if let Some(a) = &answers.answer_413 {
1079                rows.push(row!("413", a));
1080            }
1081            if let Some(a) = &answers.answer_502 {
1082                rows.push(row!("502", a));
1083            }
1084            if let Some(a) = &answers.answer_503 {
1085                rows.push(row!("503", a));
1086            }
1087            if let Some(a) = &answers.answer_504 {
1088                rows.push(row!("504", a));
1089            }
1090            if let Some(a) = &answers.answer_507 {
1091                rows.push(row!("507", a));
1092            }
1093        }
1094        rows
1095    }
1096}
1097
1098impl Display for Event {
1099    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1100        let kind = match self.kind() {
1101            EventKind::BackendDown => "backend down",
1102            EventKind::BackendUp => "backend up",
1103            EventKind::NoAvailableBackends => "no available backends",
1104            EventKind::RemovedBackendHasNoConnections => "removed backend has no connections",
1105        };
1106        let address = match &self.address {
1107            Some(a) => a.to_string(),
1108            None => String::new(),
1109        };
1110        write!(
1111            f,
1112            "{}, backend={}, cluster={}, address={}",
1113            kind,
1114            self.backend_id(),
1115            self.cluster_id(),
1116            address,
1117        )
1118    }
1119}