diff --git a/.gitignore b/.gitignore index 3b83caf..557d6fa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ builddir/ dist/ crates/aetheris-app/libs/ +.codex/ +graphify-out/ diff --git a/AGENTS.md b/AGENTS.md index f50a765..8a323d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -136,3 +136,16 @@ Before finishing a change, check: - warnings are not introduced; - user-facing strings are concise and GNOME-style; - docs and workflows describe behavior that is actually implemented. + +## graphify + +This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. + +When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. + +Rules: +- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. +- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. +- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. +- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/Cargo.toml b/Cargo.toml index 620eb6d..4ff40b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/aetheris-app", "crates/aetheris-kube"] [workspace.package] version = "1.3.0" -edition = "2021" +edition = "2024" license = "GPL-3.0-or-later" authors = ["Leandro Rodrigues "] diff --git a/crates/aetheris-app/Cargo.toml b/crates/aetheris-app/Cargo.toml index 66b1bbd..fc088b5 100644 --- a/crates/aetheris-app/Cargo.toml +++ b/crates/aetheris-app/Cargo.toml @@ -2,7 +2,7 @@ name = "aetheris-app" description = "Aetheris native Kubernetes client for GNOME" version = "1.3.0" -edition = "2021" +edition = "2024" license = "GPL-3.0-or-later" authors = ["Leandro Rodrigues "] diff --git a/crates/aetheris-app/src/app.rs b/crates/aetheris-app/src/app.rs index 97ab0d2..52b1c79 100644 --- a/crates/aetheris-app/src/app.rs +++ b/crates/aetheris-app/src/app.rs @@ -9,8 +9,8 @@ use aetheris_kube::{ ObjectDetail, ObjectEvent, ObjectSummary, ObjectWatchEvent, PodExecEvent, PodExecRequest, PodLogRequest, PodPortForwardEvent, PodPortForwardRequest, ResourceKind, ResourceUsage, }; -use futures::future::{AbortHandle, Abortable}; use futures::FutureExt; +use futures::future::{AbortHandle, Abortable}; use relm4::adw::prelude::*; use relm4::prelude::*; use relm4::{adw, gtk}; diff --git a/crates/aetheris-app/src/app/commands.rs b/crates/aetheris-app/src/app/commands.rs index e9d3fe5..ddcf6f0 100644 --- a/crates/aetheris-app/src/app/commands.rs +++ b/crates/aetheris-app/src/app/commands.rs @@ -84,15 +84,13 @@ pub(super) async fn list_objects_snapshot( resource: ResourceKind, namespace: Option, ) -> Result, String> { - let result = async { + async { let manager = KubeManager::load()?; let session = manager.connect_context(&context).await?; session.list_objects(&resource, namespace.as_deref()).await } .await - .map_err(format_error); - - result + .map_err(format_error) } pub(super) async fn stream_object_watch( diff --git a/crates/aetheris-app/src/app/handler.rs b/crates/aetheris-app/src/app/handler.rs index f82909d..f592619 100644 --- a/crates/aetheris-app/src/app/handler.rs +++ b/crates/aetheris-app/src/app/handler.rs @@ -363,16 +363,16 @@ impl App { self.show_custom_namespace_dialog(root); return; } - if let Some(namespace) = choices.get(index as usize) { - if self.selected_namespace != *namespace { - self.selected_namespace.clone_from(namespace); - self.remember_selected_namespace(); - self.sync_dropdowns(Some(sender.clone())); - self.show_object_list(); - self.stop_log_stream(); - self.stop_port_forward(); - self.refresh_objects(sender); - } + if let Some(namespace) = choices.get(index as usize) + && self.selected_namespace != *namespace + { + self.selected_namespace.clone_from(namespace); + self.remember_selected_namespace(); + self.sync_dropdowns(Some(sender.clone())); + self.show_object_list(); + self.stop_log_stream(); + self.stop_port_forward(); + self.refresh_objects(sender); } } AppMsg::CustomNamespaceEntered => { diff --git a/crates/aetheris-app/src/app/methods.rs b/crates/aetheris-app/src/app/methods.rs index b5b688f..8c67919 100644 --- a/crates/aetheris-app/src/app/methods.rs +++ b/crates/aetheris-app/src/app/methods.rs @@ -341,12 +341,11 @@ impl App { self.namespaces.push(namespace.to_owned()); } - if let Some(context) = self.selected_context.clone() { - if let Some(project) = self.projects.selected_project_mut() { - if project.add_custom_namespace(&context, namespace) { - self.save_projects_or_toast(); - } - } + if let Some(context) = self.selected_context.clone() + && let Some(project) = self.projects.selected_project_mut() + && project.add_custom_namespace(&context, namespace) + { + self.save_projects_or_toast(); } } diff --git a/crates/aetheris-app/src/app/projects.rs b/crates/aetheris-app/src/app/projects.rs index d6b9d42..b3e3bdb 100644 --- a/crates/aetheris-app/src/app/projects.rs +++ b/crates/aetheris-app/src/app/projects.rs @@ -860,9 +860,11 @@ mod tests { project.custom_namespaces_for_context(Some("prod")), vec![String::from("billing")] ); - assert!(project - .custom_namespaces_for_context(Some("stage")) - .is_empty()); + assert!( + project + .custom_namespaces_for_context(Some("stage")) + .is_empty() + ); assert!(!project.has_custom_namespace(Some("stage"), "billing")); } @@ -876,9 +878,11 @@ mod tests { project.add_custom_namespace("prod", "billing"); assert!(project.remove_custom_namespace("prod", "billing")); - assert!(project - .custom_namespaces_for_context(Some("prod")) - .is_empty()); + assert!( + project + .custom_namespaces_for_context(Some("prod")) + .is_empty() + ); assert!(project.custom_namespaces_by_context.is_empty()); } diff --git a/crates/aetheris-app/src/app/style.rs b/crates/aetheris-app/src/app/style.rs index 6cda1ae..1a71eca 100644 --- a/crates/aetheris-app/src/app/style.rs +++ b/crates/aetheris-app/src/app/style.rs @@ -47,12 +47,12 @@ fn app_style_candidates() -> Vec { paths.extend(std::env::split_paths(&data_dirs).map(|path| path.join(APP_STYLE_RESOURCE))); } - if let Ok(exe) = std::env::current_exe() { - if let Some(bin_dir) = exe.parent() { - paths.push(bin_dir.join("../share").join(APP_STYLE_RESOURCE)); - paths.push(bin_dir.join("share").join(APP_STYLE_RESOURCE)); - paths.push(bin_dir.join("../style.css")); - } + if let Ok(exe) = std::env::current_exe() + && let Some(bin_dir) = exe.parent() + { + paths.push(bin_dir.join("../share").join(APP_STYLE_RESOURCE)); + paths.push(bin_dir.join("share").join(APP_STYLE_RESOURCE)); + paths.push(bin_dir.join("../style.css")); } if let Some(app_dir) = std::env::var_os("APPDIR") { diff --git a/crates/aetheris-app/src/app/utils.rs b/crates/aetheris-app/src/app/utils.rs index 420cf83..8dc17e9 100644 --- a/crates/aetheris-app/src/app/utils.rs +++ b/crates/aetheris-app/src/app/utils.rs @@ -42,7 +42,9 @@ pub(super) fn terminal_error_message(error: &str) -> String { || lower.contains("pods/exec") || (lower.contains("cannot create") && lower.contains("exec")) { - return tr("You do not have permission to open a terminal for this Pod. Your Kubernetes user needs create access to pods/exec in this namespace."); + return tr( + "You do not have permission to open a terminal for this Pod. Your Kubernetes user needs create access to pods/exec in this namespace.", + ); } tr_format( diff --git a/crates/aetheris-app/src/app/widgets.rs b/crates/aetheris-app/src/app/widgets.rs index 2fd2dde..ce5cd5c 100644 --- a/crates/aetheris-app/src/app/widgets.rs +++ b/crates/aetheris-app/src/app/widgets.rs @@ -107,30 +107,28 @@ pub(super) fn namespace_selector_row( .build(); action.add_prefix(>k::Image::from_icon_name("folder-symbolic")); - if is_custom { - if let Some(sender) = sender { - let edit_button = gtk::Button::from_icon_name("document-edit-symbolic"); - edit_button.add_css_class("flat"); - edit_button.set_valign(gtk::Align::Center); - edit_button.set_tooltip_text(Some(&tr("Rename"))); - edit_button.connect_clicked({ - let sender = sender.clone(); - let namespace = namespace.to_owned(); - move |_| sender.input(AppMsg::OpenRenameNamespaceDialog(namespace.clone())) - }); - action.add_suffix(&edit_button); - - let delete_button = gtk::Button::from_icon_name("user-trash-symbolic"); - delete_button.add_css_class("flat"); - delete_button.add_css_class("destructive-action"); - delete_button.set_valign(gtk::Align::Center); - delete_button.set_tooltip_text(Some(&tr("Remove"))); - delete_button.connect_clicked({ - let namespace = namespace.to_owned(); - move |_| sender.input(AppMsg::RemoveCustomNamespace(namespace.clone())) - }); - action.add_suffix(&delete_button); - } + if is_custom && let Some(sender) = sender { + let edit_button = gtk::Button::from_icon_name("document-edit-symbolic"); + edit_button.add_css_class("flat"); + edit_button.set_valign(gtk::Align::Center); + edit_button.set_tooltip_text(Some(&tr("Rename"))); + edit_button.connect_clicked({ + let sender = sender.clone(); + let namespace = namespace.to_owned(); + move |_| sender.input(AppMsg::OpenRenameNamespaceDialog(namespace.clone())) + }); + action.add_suffix(&edit_button); + + let delete_button = gtk::Button::from_icon_name("user-trash-symbolic"); + delete_button.add_css_class("flat"); + delete_button.add_css_class("destructive-action"); + delete_button.set_valign(gtk::Align::Center); + delete_button.set_tooltip_text(Some(&tr("Remove"))); + delete_button.connect_clicked({ + let namespace = namespace.to_owned(); + move |_| sender.input(AppMsg::RemoveCustomNamespace(namespace.clone())) + }); + action.add_suffix(&delete_button); } if selected { @@ -602,8 +600,8 @@ const RELATED_POD_COLUMNS: [ObjectColumn; 3] = [ /// same cell factories as the main object table, default widths, no /// persistence. Returns the sorted model too — activation positions are /// indices into it, not into the unsorted store. -pub(super) fn related_pods_column_view( -) -> (gtk::ColumnView, gtk::gio::ListStore, gtk::SortListModel) { +pub(super) fn related_pods_column_view() +-> (gtk::ColumnView, gtk::gio::ListStore, gtk::SortListModel) { let store = gtk::gio::ListStore::new::(); let view = gtk::ColumnView::builder() .single_click_activate(true) diff --git a/crates/aetheris-app/src/app/yaml.rs b/crates/aetheris-app/src/app/yaml.rs index 7bb6f25..846e549 100644 --- a/crates/aetheris-app/src/app/yaml.rs +++ b/crates/aetheris-app/src/app/yaml.rs @@ -205,12 +205,12 @@ fn update_yaml_error_state(buffer: &sourceview5::Buffer, error_label: >k::Labe return; }; - if let Some(tag) = text_buffer.tag_table().lookup("yaml-error-line") { - if let Some(start) = text_buffer.iter_at_line((line - 1) as i32) { - let mut end = start; - end.forward_to_line_end(); - text_buffer.apply_tag(&tag, &start, &end); - } + if let Some(tag) = text_buffer.tag_table().lookup("yaml-error-line") + && let Some(start) = text_buffer.iter_at_line((line - 1) as i32) + { + let mut end = start; + end.forward_to_line_end(); + text_buffer.apply_tag(&tag, &start, &end); } error_label.set_label(&tr_format( "Line {line}: {message}", @@ -579,7 +579,9 @@ pub(super) fn desired_state_explanation(kind: &str, mapping: &serde_yaml::Mappin "ConfigMap" => { tr("ConfigMaps usually express desired configuration through data instead of spec.") } - "Secret" => tr("Secrets usually express desired sensitive data through data or stringData instead of spec."), + "Secret" => tr( + "Secrets usually express desired sensitive data through data or stringData instead of spec.", + ), _ => tr("This manifest does not include a spec section."), }; } @@ -863,14 +865,20 @@ pub(super) fn kind_explanation(kind: &str) -> String { pub(super) fn spec_explanation(kind: &str) -> String { match kind { - "Pod" => tr("Desired Pod configuration, including containers, volumes, restart policy and scheduling hints."), - "Deployment" => tr("Desired Deployment state, usually replicas, selector and the Pod template."), + "Pod" => tr( + "Desired Pod configuration, including containers, volumes, restart policy and scheduling hints.", + ), + "Deployment" => { + tr("Desired Deployment state, usually replicas, selector and the Pod template.") + } "Service" => tr("Desired Service routing, including selector, type and exposed ports."), "Ingress" => tr("Desired routing rules, TLS configuration and backend Services."), "Job" => tr("Desired one-shot workload, including completion policy and Pod template."), "CronJob" => tr("Desired schedule and Job template."), "Node" => tr("Node spec is mostly managed by Kubernetes and should be edited carefully."), - _ => tr("Desired state for this resource. Controllers reconcile the live object toward this section."), + _ => tr( + "Desired state for this resource. Controllers reconcile the live object toward this section.", + ), } } diff --git a/crates/aetheris-kube/src/events.rs b/crates/aetheris-kube/src/events.rs index 2e506c9..1c08866 100644 --- a/crates/aetheris-kube/src/events.rs +++ b/crates/aetheris-kube/src/events.rs @@ -5,7 +5,7 @@ use kube::api::ListParams; use kube::{Api, ResourceExt}; use crate::status::age_label; -use crate::{resource_scope, KubeSession, ObjectEvent, ResourceKind}; +use crate::{KubeSession, ObjectEvent, ResourceKind, resource_scope}; impl KubeSession { pub(crate) async fn object_events( diff --git a/crates/aetheris-kube/src/exec.rs b/crates/aetheris-kube/src/exec.rs index 6b220b0..cd6b025 100644 --- a/crates/aetheris-kube/src/exec.rs +++ b/crates/aetheris-kube/src/exec.rs @@ -1,7 +1,7 @@ -use anyhow::{bail, Context as AnyhowContext, Result}; +use anyhow::{Context as AnyhowContext, Result, bail}; use k8s_openapi::api::core::v1::Pod; -use kube::api::AttachParams; use kube::Api; +use kube::api::AttachParams; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; use crate::{KubeSession, PodExecEvent, PodExecRequest}; @@ -70,15 +70,15 @@ impl KubeSession { .await .context("failed to finish exec session")?; - if let Some(status) = status { - if status.status.as_deref() == Some("Failure") { - bail!( - "{}", - status - .message - .unwrap_or_else(|| String::from("command failed")) - ); - } + if let Some(status) = status + && status.status.as_deref() == Some("Failure") + { + bail!( + "{}", + status + .message + .unwrap_or_else(|| String::from("command failed")) + ); } Ok(()) @@ -155,15 +155,15 @@ impl KubeSession { .await .context("failed to finish terminal session")?; - if let Some(status) = status { - if status.status.as_deref() == Some("Failure") { - bail!( - "{}", - status - .message - .unwrap_or_else(|| String::from("terminal session failed")) - ); - } + if let Some(status) = status + && status.status.as_deref() == Some("Failure") + { + bail!( + "{}", + status + .message + .unwrap_or_else(|| String::from("terminal session failed")) + ); } Ok(()) diff --git a/crates/aetheris-kube/src/kubeconfig.rs b/crates/aetheris-kube/src/kubeconfig.rs index ed2056d..05e74a5 100644 --- a/crates/aetheris-kube/src/kubeconfig.rs +++ b/crates/aetheris-kube/src/kubeconfig.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::fs; use std::path::PathBuf; -use anyhow::{bail, Context as AnyhowContext, Result}; +use anyhow::{Context as AnyhowContext, Result, bail}; use kube::config::{ AuthInfo, Cluster, Context as KubeContext, Kubeconfig, NamedAuthInfo, NamedCluster, NamedContext, @@ -96,15 +96,15 @@ impl KubeManager { // Renaming a cluster creates fresh entries under the new name above; // drop the stale ones left behind under the old name so it doesn't // linger as a duplicate in the cluster list. - if let Some(original_name) = request.original_context_name.as_deref() { - if original_name != request.context_name { - kubeconfig - .contexts - .retain(|context| context.name != original_name); - kubeconfig - .clusters - .retain(|cluster| cluster.name != original_name); - } + if let Some(original_name) = request.original_context_name.as_deref() + && original_name != request.context_name + { + kubeconfig + .contexts + .retain(|context| context.name != original_name); + kubeconfig + .clusters + .retain(|cluster| cluster.name != original_name); } kubeconfig.current_context = Some(request.context_name); @@ -144,10 +144,10 @@ impl KubeManager { kubeconfig = Kubeconfig::merge(kubeconfig, imported) .context("failed to merge imported kubeconfig")?; - if kubeconfig.current_context.is_none() { - if let Some(context) = kubeconfig.contexts.first() { - kubeconfig.current_context = Some(context.name.clone()); - } + if kubeconfig.current_context.is_none() + && let Some(context) = kubeconfig.contexts.first() + { + kubeconfig.current_context = Some(context.name.clone()); } kubeconfig.api_version = Some(String::from("v1")); kubeconfig.kind = Some(String::from("Config")); diff --git a/crates/aetheris-kube/src/logs.rs b/crates/aetheris-kube/src/logs.rs index fed42a6..26994b3 100644 --- a/crates/aetheris-kube/src/logs.rs +++ b/crates/aetheris-kube/src/logs.rs @@ -1,8 +1,8 @@ use anyhow::{Context as AnyhowContext, Result}; use futures::{AsyncBufReadExt, TryStreamExt}; use k8s_openapi::api::core::v1::Pod; -use kube::api::LogParams; use kube::Api; +use kube::api::LogParams; use crate::{KubeSession, PodLogRequest}; diff --git a/crates/aetheris-kube/src/manager.rs b/crates/aetheris-kube/src/manager.rs index 5b70853..3478a9a 100644 --- a/crates/aetheris-kube/src/manager.rs +++ b/crates/aetheris-kube/src/manager.rs @@ -1,8 +1,8 @@ use std::collections::BTreeSet; use anyhow::{Context as AnyhowContext, Result}; -use kube::config::{Config, KubeConfigOptions, Kubeconfig}; use kube::Client; +use kube::config::{Config, KubeConfigOptions, Kubeconfig}; use crate::kubeconfig::{cluster_server, cluster_skips_tls_verify, server_host}; use crate::{ContextInfo, KubeSession}; @@ -74,12 +74,10 @@ impl KubeManager { .contexts .iter() .find(|named_context| named_context.name == context) + && let Some(context) = &named_context.context + && let Some(namespace) = &context.namespace { - if let Some(context) = &named_context.context { - if let Some(namespace) = &context.namespace { - namespaces.insert(namespace.clone()); - } - } + namespaces.insert(namespace.clone()); } namespaces.into_iter().collect() diff --git a/crates/aetheris-kube/src/mutations.rs b/crates/aetheris-kube/src/mutations.rs index f26f57d..b1c82e3 100644 --- a/crates/aetheris-kube/src/mutations.rs +++ b/crates/aetheris-kube/src/mutations.rs @@ -1,11 +1,11 @@ -use anyhow::{bail, Context as AnyhowContext, Result}; +use anyhow::{Context as AnyhowContext, Result, bail}; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{Node, Pod}; use kube::api::{DeleteParams, DynamicObject, ListParams, Patch, PatchParams}; use kube::{Api, ResourceExt}; use serde_json::Value; -use crate::{api_resource, resource_scope, KubeSession, ObjectDetail, ResourceKind}; +use crate::{KubeSession, ObjectDetail, ResourceKind, api_resource, resource_scope}; impl KubeSession { pub async fn apply_object_yaml( diff --git a/crates/aetheris-kube/src/objects.rs b/crates/aetheris-kube/src/objects.rs index 679c92c..71954a2 100644 --- a/crates/aetheris-kube/src/objects.rs +++ b/crates/aetheris-kube/src/objects.rs @@ -4,15 +4,15 @@ use anyhow::{Context as AnyhowContext, Result}; use futures::StreamExt; use k8s_openapi::api::core::v1::Pod; use kube::api::{DynamicObject, ListParams}; -use kube::runtime::watcher::{watcher, Event as WatcherEvent}; +use kube::runtime::watcher::{Event as WatcherEvent, watcher}; use kube::{Api, ResourceExt}; use serde_json::Value; use crate::status::{age_label, status_label}; use crate::{ - api_resource, namespace_scope, resource_scope, KubeSession, ObjectCondition, ObjectDetail, - ObjectSummary, ObjectWatchEvent, PodSummary, ResourceKind, ResourceRatio, ResourceScope, - ResourceUsage, + KubeSession, ObjectCondition, ObjectDetail, ObjectSummary, ObjectWatchEvent, PodSummary, + ResourceKind, ResourceRatio, ResourceScope, ResourceUsage, api_resource, namespace_scope, + resource_scope, }; impl KubeSession { diff --git a/crates/aetheris-kube/src/portforward.rs b/crates/aetheris-kube/src/portforward.rs index a5e8ebd..78c5201 100644 --- a/crates/aetheris-kube/src/portforward.rs +++ b/crates/aetheris-kube/src/portforward.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context as AnyhowContext, Result}; +use anyhow::{Context as AnyhowContext, Result, bail}; use k8s_openapi::api::core::v1::Pod; use kube::Api; use tokio::io::copy_bidirectional; diff --git a/crates/aetheris-kube/src/resources.rs b/crates/aetheris-kube/src/resources.rs index 9a221f3..937572c 100644 --- a/crates/aetheris-kube/src/resources.rs +++ b/crates/aetheris-kube/src/resources.rs @@ -1,7 +1,7 @@ use anyhow::{Context as AnyhowContext, Result}; use k8s_openapi::api::core::v1::Namespace; use kube::api::{ApiResource, DynamicObject, ListParams}; -use kube::discovery::{verbs, Discovery, Scope}; +use kube::discovery::{Discovery, Scope, verbs}; use kube::{Api, ResourceExt}; use crate::{KubeSession, ResourceKind, ResourceScope}; @@ -64,10 +64,10 @@ impl KubeSession { if let Ok(names) = self.list_openshift_projects().await { return Ok(normalized_namespace_names(names)); } - if self.server.contains("/k8s/clusters/") { - if let Ok(names) = self.list_rancher_namespaces().await { - return Ok(normalized_namespace_names(names)); - } + if self.server.contains("/k8s/clusters/") + && let Ok(names) = self.list_rancher_namespaces().await + { + return Ok(normalized_namespace_names(names)); } Err(native_error) diff --git a/crates/aetheris-kube/src/status.rs b/crates/aetheris-kube/src/status.rs index d75b67c..71c38ad 100644 --- a/crates/aetheris-kube/src/status.rs +++ b/crates/aetheris-kube/src/status.rs @@ -21,7 +21,7 @@ pub(crate) fn status_label( return ( spec_string(object, "type").unwrap_or_else(|| String::from("ClusterIP")), None, - ) + ); } ("networking.k8s.io", "Ingress") => return (ingress_status_label(object), None), ("", "ConfigMap") => return (data_entries_status_label(object), None), @@ -37,17 +37,17 @@ pub(crate) fn status_label( return (phase.to_owned(), None); } - if let Some(conditions) = status.get("conditions").and_then(|value| value.as_array()) { - if let Some(ready) = conditions.iter().find(|condition| { + if let Some(conditions) = status.get("conditions").and_then(|value| value.as_array()) + && let Some(ready) = conditions.iter().find(|condition| { condition.get("type").and_then(|value| value.as_str()) == Some("Ready") - }) { - let label = ready - .get("status") - .and_then(|value| value.as_str()) - .map(|status| format!("Ready={status}")) - .unwrap_or_else(|| String::from("Ready")); - return (label, None); - } + }) + { + let label = ready + .get("status") + .and_then(|value| value.as_str()) + .map(|status| format!("Ready={status}")) + .unwrap_or_else(|| String::from("Ready")); + return (label, None); } let ready_replicas = status.get("readyReplicas").and_then(|value| value.as_i64()); diff --git a/rustfmt.toml b/rustfmt.toml index d114434..7b8d1d9 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -edition = "2021" +edition = "2024" fn_params_layout = "Tall" match_arm_leading_pipes = "Preserve" merge_derives = true