• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kdash-rs / kdash / 18585133834

17 Oct 2025 07:02AM UTC coverage: 57.358% (-1.0%) from 58.34%
18585133834

push

github

web-flow
Merge pull request #502 from mloskot/ml/feat/configurable-install-dir

feat: Allow getLatest.sh to deploy binary to custom location

3925 of 6843 relevant lines covered (57.36%)

7.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.53
/src/app/ingress.rs
1
use async_trait::async_trait;
2
use k8s_openapi::{
3
  api::networking::v1::{Ingress, IngressBackend, IngressRule, IngressStatus},
4
  chrono::Utc,
5
};
6
use ratatui::{
7
  layout::{Constraint, Rect},
8
  widgets::{Cell, Row},
9
  Frame,
10
};
11

12
use super::{
13
  models::{AppResource, KubeResource},
14
  utils::{self, UNKNOWN},
15
  ActiveBlock, App,
16
};
17
use crate::{
18
  draw_resource_tab,
19
  network::Network,
20
  ui::utils::{
21
    draw_describe_block, draw_resource_block, draw_yaml_block, get_describe_active,
22
    get_resource_title, style_primary, title_with_dual_style, ResourceTableProps, COPY_HINT,
23
    DESCRIBE_YAML_AND_ESC_HINT,
24
  },
25
};
26

27
#[derive(Clone, Debug, PartialEq)]
28
pub struct KubeIngress {
29
  pub namespace: String,
30
  pub name: String,
31
  pub ingress_class: String,
32
  pub address: String,
33
  pub paths: String,
34
  pub default_backend: String,
35
  pub age: String,
36
  k8s_obj: Ingress,
37
}
38

39
impl From<Ingress> for KubeIngress {
40
  fn from(ingress: Ingress) -> Self {
3✔
41
    let (ingress_class, rules, default_backend) = match &ingress.spec {
3✔
42
      Some(spec) => {
3✔
43
        let class_name = match &spec.ingress_class_name {
3✔
44
          Some(c) => c.clone(),
3✔
45
          None => UNKNOWN.into(),
×
46
        };
47
        (
3✔
48
          class_name,
3✔
49
          get_rules(&spec.rules),
3✔
50
          format_backend(&spec.default_backend),
3✔
51
        )
3✔
52
      }
53
      None => (String::default(), None, String::default()),
×
54
    };
55
    let name = match &ingress.metadata.name {
3✔
56
      Some(n) => n.clone(),
3✔
57
      None => UNKNOWN.into(),
×
58
    };
59
    let namespace = match &ingress.metadata.namespace {
3✔
60
      Some(n) => n.clone(),
3✔
61
      None => UNKNOWN.into(),
×
62
    };
63
    let paths = rules.unwrap_or_default();
3✔
64
    Self {
3✔
65
      name,
3✔
66
      namespace,
3✔
67
      ingress_class,
3✔
68
      address: get_addresses(&ingress.status),
3✔
69
      paths,
3✔
70
      default_backend,
3✔
71
      age: utils::to_age(ingress.metadata.creation_timestamp.as_ref(), Utc::now()),
3✔
72
      k8s_obj: utils::sanitize_obj(ingress),
3✔
73
    }
3✔
74
  }
3✔
75
}
76

77
impl KubeResource<Ingress> for KubeIngress {
78
  fn get_name(&self) -> &String {
×
79
    &self.name
×
80
  }
×
81
  fn get_k8s_obj(&self) -> &Ingress {
×
82
    &self.k8s_obj
×
83
  }
×
84
}
85

86
fn format_backend(backend: &Option<IngressBackend>) -> String {
5✔
87
  match backend {
5✔
88
    Some(backend) => {
4✔
89
      if let Some(resource) = &backend.resource {
4✔
90
        return resource.name.to_string();
×
91
      }
4✔
92
      if let Some(service) = &backend.service {
4✔
93
        match &service.port {
4✔
94
          Some(port) => {
4✔
95
            if let Some(name) = &port.name {
4✔
96
              format!("{}:{}", service.name, name)
1✔
97
            } else if let Some(number) = &port.number {
3✔
98
              return format!("{}:{}", service.name, number);
3✔
99
            } else {
100
              return String::default();
×
101
            }
102
          }
103
          None => String::default(),
×
104
        }
105
      } else {
106
        String::default()
×
107
      }
108
    }
109
    None => String::default(),
1✔
110
  }
111
}
5✔
112

113
fn get_rules(i_rules: &Option<Vec<IngressRule>>) -> Option<String> {
3✔
114
  i_rules.as_ref().map(|rules| {
3✔
115
    rules
2✔
116
      .iter()
2✔
117
      .map(|i_rule| {
2✔
118
        let mut rule = i_rule.host.clone().unwrap_or("*".to_string());
2✔
119
        if let Some(http) = &i_rule.http {
2✔
120
          http.paths.iter().for_each(|path| {
2✔
121
            rule = format!(
2✔
122
              "{}{}►{}",
2✔
123
              rule,
124
              &path.path.clone().unwrap_or("/*".to_string()),
2✔
125
              format_backend(&Some(path.backend.clone()))
2✔
126
            );
127
          });
2✔
128
        }
×
129
        rule
2✔
130
      })
2✔
131
      .collect::<Vec<_>>()
2✔
132
      .join(" ")
2✔
133
  })
2✔
134
}
3✔
135

136
fn get_addresses(i_status: &Option<IngressStatus>) -> String {
3✔
137
  match i_status {
3✔
138
    Some(status) => match &status.load_balancer {
3✔
139
      Some(lb) => match &lb.ingress {
3✔
140
        Some(ingress) => ingress
2✔
141
          .iter()
2✔
142
          .map(|i| {
2✔
143
            if let Some(h) = &i.hostname {
2✔
144
              h.to_string()
×
145
            } else if let Some(ip) = &i.ip {
2✔
146
              ip.to_string()
2✔
147
            } else {
148
              "<pending>".to_string()
×
149
            }
150
          })
2✔
151
          .collect::<Vec<_>>()
2✔
152
          .join(" "),
2✔
153
        None => String::default(),
1✔
154
      },
155
      None => String::default(),
×
156
    },
157
    None => String::default(),
×
158
  }
159
}
3✔
160

161
static INGRESS_TITLE: &str = "Ingresses";
162

163
pub struct IngressResource {}
164

165
#[async_trait]
166
impl AppResource for IngressResource {
167
  fn render(block: ActiveBlock, f: &mut Frame<'_>, app: &mut App, area: Rect) {
×
168
    draw_resource_tab!(
×
169
      INGRESS_TITLE,
×
170
      block,
×
171
      f,
×
172
      app,
×
173
      area,
×
174
      Self::render,
175
      draw_block,
176
      app.data.ingress
177
    );
178
  }
×
179

180
  async fn get_resource(nw: &Network<'_>) {
×
181
    let items: Vec<KubeIngress> = nw.get_namespaced_resources(Ingress::into).await;
×
182

183
    let mut app = nw.app.lock().await;
×
184
    app.data.ingress.set_items(items);
×
185
  }
×
186
}
187

188
fn draw_block(f: &mut Frame<'_>, app: &mut App, area: Rect) {
×
189
  let title = get_resource_title(app, INGRESS_TITLE, "", app.data.ingress.items.len());
×
190

191
  draw_resource_block(
×
192
    f,
×
193
    area,
×
194
    ResourceTableProps {
×
195
      title,
×
196
      inline_help: DESCRIBE_YAML_AND_ESC_HINT.into(),
×
197
      resource: &mut app.data.ingress,
×
198
      table_headers: vec![
×
199
        "Namespace",
×
200
        "Name",
×
201
        "Ingress class",
×
202
        "Paths",
×
203
        "Default backend",
×
204
        "Addresses",
×
205
        "Age",
×
206
      ],
×
207
      column_widths: vec![
×
208
        Constraint::Percentage(10),
×
209
        Constraint::Percentage(20),
×
210
        Constraint::Percentage(10),
×
211
        Constraint::Percentage(25),
×
212
        Constraint::Percentage(10),
×
213
        Constraint::Percentage(10),
×
214
        Constraint::Percentage(10),
×
215
      ],
×
216
    },
×
217
    |c| {
×
218
      Row::new(vec![
×
219
        Cell::from(c.namespace.to_owned()),
×
220
        Cell::from(c.name.to_owned()),
×
221
        Cell::from(c.ingress_class.to_owned()),
×
222
        Cell::from(c.paths.to_owned()),
×
223
        Cell::from(c.default_backend.to_owned()),
×
224
        Cell::from(c.address.to_owned()),
×
225
        Cell::from(c.age.to_owned()),
×
226
      ])
227
      .style(style_primary(app.light_theme))
×
228
    },
×
229
    app.light_theme,
×
230
    app.is_loading,
×
231
    app.data.selected.filter.to_owned(),
×
232
  );
233
}
×
234

235
#[cfg(test)]
236
mod tests {
237
  use super::*;
238
  use crate::app::test_utils::*;
239

240
  #[test]
241
  fn test_ingresses_from_api() {
1✔
242
    let (ingresses, ingress_list): (Vec<KubeIngress>, Vec<_>) =
1✔
243
      convert_resource_from_file("ingress");
1✔
244

245
    assert_eq!(ingresses.len(), 3);
1✔
246
    assert_eq!(
1✔
247
      ingresses[0],
1✔
248
      KubeIngress {
1✔
249
        name: "ingdefault".into(),
1✔
250
        namespace: "default".into(),
1✔
251
        age: utils::to_age(Some(&get_time("2023-05-24T16:14:32Z")), Utc::now()),
1✔
252
        k8s_obj: ingress_list[0].clone(),
1✔
253
        ingress_class: "default".into(),
1✔
254
        address: "".into(),
1✔
255
        paths: "foo.com/►svc:8080".into(),
1✔
256
        default_backend: "defaultsvc:http".into(),
1✔
257
      }
1✔
258
    );
259
    assert_eq!(
1✔
260
      ingresses[1],
1✔
261
      KubeIngress {
1✔
262
        name: "test".into(),
1✔
263
        namespace: "default".into(),
1✔
264
        age: utils::to_age(Some(&get_time("2023-05-24T16:20:48Z")), Utc::now()),
1✔
265
        k8s_obj: ingress_list[1].clone(),
1✔
266
        ingress_class: "nginx".into(),
1✔
267
        address: "192.168.49.2".into(),
1✔
268
        paths: "".into(),
1✔
269
        default_backend: "test:5701".into(),
1✔
270
      }
1✔
271
    );
272
    assert_eq!(
1✔
273
      ingresses[2],
1✔
274
      KubeIngress {
1✔
275
        name: "test-ingress".into(),
1✔
276
        namespace: "dev".into(),
1✔
277
        age: utils::to_age(Some(&get_time("2023-05-24T16:22:23Z")), Utc::now()),
1✔
278
        k8s_obj: ingress_list[2].clone(),
1✔
279
        ingress_class: "nginx".into(),
1✔
280
        address: "192.168.49.2".into(),
1✔
281
        paths: "demo.apps.mlopshub.com/►hello-service:80".into(),
1✔
282
        default_backend: "".into(),
1✔
283
      }
1✔
284
    );
285
  }
1✔
286
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc