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 origin: None,
85 };
86 let off = match s.via {
87 Via::Tunnel(ref org) => {
88 d.cert = if r.https { "cloudflare" } else { "none" }.into();
89 d.url = Some(r.url(None, None));
90 d.origin = st.tunnels.get(org).and_then(|t| t.origin.clone());
91 None
92 }
93 Via::Public if r.https => {
94 d.url = Some(r.url(self.cfg.https.map(|a| a.port()), None));
95 d.origin = self.cfg.https.map(|a| format!("https://{a}"));
96 if self.cfg.https.is_none() {
97 d.cert = "none".into();
98 Some("this server has no HTTPS listener (isb serve --ingress-https)")
99 } else if caddy::needs_dns_challenge(&r.host, &self.cfg.ca) {
100 d.cert = "unsupported".into();
101 d.message = Some("a wildcard certificate needs a DNS challenge, which this ingress cannot do; use the cloudflare-tunnel provider".into());
102 None
103 } else {
104 let key = self.cfg.ca.issuer_key();
105 match st.certs.get(&r.host) {
106 Some(c) if c.state == "failed" => {
107 d.cert = "failed".into();
108 d.message = c.error.clone();
109 }
110 Some(c) if c.state == "issued" => d.cert = "issued".into(),
111 _ if caddy::cert_in_storage(&self.storage(), &key, &r.host) => {
112 d.cert = "issued".into()
113 }
114 _ => d.cert = "pending".into(),
115 }
116 None
117 }
118 }
119 Via::Public => {
120 d.cert = "none".into();
121 d.url = Some(r.url(None, self.cfg.http.map(|a| a.port())));
122 d.origin = self.cfg.http.map(|a| format!("http://{a}"));
123 if self.cfg.http.is_none() {
124 Some("this server has no HTTP listener (isb serve --ingress-http)")
125 } else {
126 None
127 }
128 }
129 };
130 d.state = if let Some(why) = off {
131 d.message = Some(why.into());
132 "off".into()
133 } else if r.redirect.is_some() {
134 "redirect".into()
135 } else if s.upstreams.is_empty() {
136 "no-replicas".into()
137 } else {
138 "serving".into()
139 };
140 d
141 }
142}