isb_core/ingress/
status.rs1use super::*;
5
6impl Manager {
7 pub fn status(&self, orgs: Option<&[OrgId]>) -> Value {
10 let sees = |o: &OrgId| orgs.is_none_or(|v| v.contains(o));
11 let st = self.state.lock().unwrap();
12 let mut routes = Vec::new();
13 for s in st.served.iter().filter(|s| sees(&s.route.org)) {
14 let ds = self.domain_status_of(&st, s);
15 routes.push(json!({
16 "org": s.route.org,
17 "stack": s.route.stack,
18 "service": s.route.service,
19 "domain": ds,
20 }));
21 }
22 let conflicts: Vec<Value> = st
23 .conflicts
24 .iter()
25 .filter(|c| sees(&c.route.org))
26 .map(|c| {
27 json!({
28 "org": c.route.org, "stack": c.route.stack, "service": c.route.service,
29 "host": c.route.host, "path": c.route.path, "reason": c.reason,
30 })
31 })
32 .collect();
33 let mut refused = Vec::new();
34 for ((q, svc), list) in &st.refused {
35 let org = crate::stack::split_qualified(q)
36 .map(|(o, _)| o)
37 .unwrap_or_else(|_| OrgId::default_org());
38 if !sees(&org) {
39 continue;
40 }
41 for (h, p, why) in list {
42 refused
43 .push(json!({"stack": q, "service": svc, "host": h, "path": p, "reason": why}));
44 }
45 }
46 let tunnels: Vec<&TunnelStatus> = st
47 .tunnels
48 .iter()
49 .filter(|(o, _)| sees(o))
50 .map(|(_, t)| t)
51 .collect();
52 json!({
53 "enabled": true,
54 "http": self.cfg.http.map(|a| a.to_string()),
55 "https": self.cfg.https.map(|a| a.to_string()),
56 "ca": match &self.cfg.ca { Ca::Internal => "internal".to_string(), Ca::Acme(u) => u.clone() },
57 "public_ip": self.cfg.public_ip.map(|a| a.to_string()),
58 "tunnel_port": self.cfg.tunnel_port,
59 "caddy": self.edge.get().map(|e| serde_json::to_value(e.status()).unwrap_or_default()),
60 "error": st.last_error,
61 "routes": routes,
62 "conflicts": conflicts,
63 "refused": refused,
64 "tunnels": tunnels,
65 })
66 }
67
68 pub(super) fn domain_status_of(&self, st: &State, s: &Served) -> DomainStatus {
69 let r = &s.route;
70 let provider = match s.via {
71 Via::Public => crate::org::INGRESS_CADDY,
72 Via::Tunnel(_) => crate::org::INGRESS_CLOUDFLARE_TUNNEL,
73 };
74 let mut d = DomainStatus {
75 host: r.host.clone(),
76 path: r.path.clone(),
77 url: None,
78 https: r.https,
79 provider: provider.into(),
80 state: String::new(),
81 cert: String::new(),
82 message: None,
83 upstreams: s.upstreams.iter().map(|a| a.to_string()).collect(),
84 };
85 let off = match s.via {
86 Via::Tunnel(_) => {
87 d.cert = if r.https { "cloudflare" } else { "none" }.into();
88 d.url = Some(r.url(None, None));
89 None
90 }
91 Via::Public if r.https => {
92 d.url = Some(r.url(self.cfg.https.map(|a| a.port()), None));
93 if self.cfg.https.is_none() {
94 d.cert = "none".into();
95 Some("this server has no HTTPS listener (isb serve --ingress-https)")
96 } else if caddy::needs_dns_challenge(&r.host, &self.cfg.ca) {
97 d.cert = "unsupported".into();
98 d.message = Some("a wildcard certificate needs a DNS challenge, which this ingress cannot do; use the cloudflare-tunnel provider".into());
99 None
100 } else {
101 let key = self.cfg.ca.issuer_key();
102 match st.certs.get(&r.host) {
103 Some(c) if c.state == "failed" => {
104 d.cert = "failed".into();
105 d.message = c.error.clone();
106 }
107 Some(c) if c.state == "issued" => d.cert = "issued".into(),
108 _ if caddy::cert_in_storage(&self.storage(), &key, &r.host) => {
109 d.cert = "issued".into()
110 }
111 _ => d.cert = "pending".into(),
112 }
113 None
114 }
115 }
116 Via::Public => {
117 d.cert = "none".into();
118 d.url = Some(r.url(None, self.cfg.http.map(|a| a.port())));
119 if self.cfg.http.is_none() {
120 Some("this server has no HTTP listener (isb serve --ingress-http)")
121 } else {
122 None
123 }
124 }
125 };
126 d.state = if let Some(why) = off {
127 d.message = Some(why.into());
128 "off".into()
129 } else if r.redirect.is_some() {
130 "redirect".into()
131 } else if s.upstreams.is_empty() {
132 "no-replicas".into()
133 } else {
134 "serving".into()
135 };
136 d
137 }
138}