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