Skip to content

Commit 38e953c

Browse files
committed
Link CronJob jobs to runtime pods
1 parent 74c9423 commit 38e953c

3 files changed

Lines changed: 71 additions & 17 deletions

File tree

src-tauri/src/kube_commands.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,15 @@ fn resource_age(metadata: &ObjectMeta) -> String {
15581558
.unwrap_or_else(|| "live".to_string())
15591559
}
15601560

1561+
fn owner_label(metadata: &ObjectMeta) -> String {
1562+
metadata
1563+
.owner_references
1564+
.as_ref()
1565+
.and_then(|owners| owners.first())
1566+
.map(|owner| format!("{}/{}", owner.kind, owner.name))
1567+
.unwrap_or_default()
1568+
}
1569+
15611570
async fn list_pods(client: Client, cluster: &str) -> Result<Vec<ResourceSummary>, String> {
15621571
let pods = Api::<Pod>::all(client)
15631572
.list(&ListParams::default())
@@ -1584,13 +1593,7 @@ async fn list_pods(client: Client, cluster: &str) -> Result<Vec<ResourceSummary>
15841593
.map(|status| status.image.clone())
15851594
.unwrap_or_default();
15861595
let status = pod_status(&pod, restarts);
1587-
let owner = pod
1588-
.metadata
1589-
.owner_references
1590-
.as_ref()
1591-
.and_then(|owners| owners.first())
1592-
.map(|owner| format!("{}/{}", owner.kind, owner.name))
1593-
.unwrap_or_default();
1596+
let owner = owner_label(&pod.metadata);
15941597
let labels = pod.metadata.labels.clone().unwrap_or_default();
15951598
let references = pod_dependency_references(&pod, &namespace);
15961599
let node_name = pod
@@ -1675,13 +1678,7 @@ async fn list_replicasets(client: Client, cluster: &str) -> Result<Vec<ResourceS
16751678
.unwrap_or_default();
16761679
let labels = replicaset.metadata.labels.clone().unwrap_or_default();
16771680
let selector = replicaset.spec.as_ref().and_then(|spec| spec.selector.match_labels.clone()).unwrap_or_default();
1678-
let owner = replicaset
1679-
.metadata
1680-
.owner_references
1681-
.as_ref()
1682-
.and_then(|owners| owners.first())
1683-
.map(|owner| format!("{}/{}", owner.kind, owner.name))
1684-
.unwrap_or_default();
1681+
let owner = owner_label(&replicaset.metadata);
16851682

16861683
resource_summary(
16871684
"ReplicaSet",
@@ -1805,6 +1802,13 @@ async fn list_jobs(client: Client, cluster: &str) -> Result<Vec<ResourceSummary>
18051802
.map(|container| container.image.clone().unwrap_or_default())
18061803
.unwrap_or_default();
18071804
let labels = job.metadata.labels.clone().unwrap_or_default();
1805+
let selector = job
1806+
.spec
1807+
.as_ref()
1808+
.and_then(|spec| spec.selector.as_ref())
1809+
.and_then(|selector| selector.match_labels.clone())
1810+
.unwrap_or_default();
1811+
let owner = owner_label(&job.metadata);
18081812

18091813
resource_summary(
18101814
"Job",
@@ -1817,6 +1821,8 @@ async fn list_jobs(client: Client, cluster: &str) -> Result<Vec<ResourceSummary>
18171821
)
18181822
.with_age(age)
18191823
.with_labels(labels)
1824+
.with_owner(owner)
1825+
.with_selector(selector)
18201826
})
18211827
.collect())
18221828
}
@@ -3175,6 +3181,7 @@ mod tests {
31753181
HTTPIngressPath, HTTPIngressRuleValue, IngressBackend, IngressRule, IngressServiceBackend,
31763182
IngressSpec, ServiceBackendPort,
31773183
};
3184+
use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference;
31783185

31793186
#[test]
31803187
fn running_ready_pod_without_restarts_is_healthy() {
@@ -3183,6 +3190,22 @@ mod tests {
31833190
assert_eq!(pod_status(&pod, 0), HealthState::Healthy);
31843191
}
31853192

3193+
#[test]
3194+
fn owner_label_preserves_controller_lineage() {
3195+
let metadata = ObjectMeta {
3196+
owner_references: Some(vec![OwnerReference {
3197+
api_version: "batch/v1".to_string(),
3198+
kind: "CronJob".to_string(),
3199+
name: "nightly-reconcile".to_string(),
3200+
uid: "uid-1".to_string(),
3201+
..OwnerReference::default()
3202+
}]),
3203+
..ObjectMeta::default()
3204+
};
3205+
3206+
assert_eq!(owner_label(&metadata), "CronJob/nightly-reconcile");
3207+
}
3208+
31863209
#[test]
31873210
fn running_crashlooping_pod_is_critical() {
31883211
let pod = pod_with_status(

src/components/ResourceDetail.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useMemo, useRef, useState } from "react";
22
import { Activity, ArrowLeft, Box, CheckCircle2, FileText, GitCommitHorizontal, ImageIcon, Network, RotateCw, Server, ShieldAlert, Skull, Star, TerminalSquare } from "lucide-react";
33
import { containerCurrentState, containerLastState, currentStateTime, lastStateTime } from "../lib/podLifecycle";
4-
import { matchesSelector, ownsPod, referencesResource, workloadKinds } from "../lib/resourceRelationships";
4+
import { matchesSelector, ownsPod, ownsResource, referencesResource, workloadKinds } from "../lib/resourceRelationships";
55
import type { ContainerDetails, HealthState, PodActionResult, PodCondition, ResourceDetails, ResourceRow } from "../types/kube";
66
import { PodEventRail } from "./PodEventRail";
77
import { PodIssueStrip } from "./PodIssueStrip";
@@ -1007,9 +1007,26 @@ function workloadPodsFor(resource: ResourceRow, resources: ResourceRow[]) {
10071007
return [];
10081008
}
10091009

1010+
if (resource.kind === "CronJob") {
1011+
const jobs = cronJobJobsFor(resource, resources);
1012+
return resources.filter((item) =>
1013+
item.kind === "Pod" &&
1014+
item.namespace === resource.namespace &&
1015+
jobs.some((job) => ownsPod(job, item))
1016+
);
1017+
}
1018+
10101019
return resources.filter((item) => item.kind === "Pod" && item.namespace === resource.namespace && ownsPod(resource, item));
10111020
}
10121021

1022+
function cronJobJobsFor(resource: ResourceRow, resources: ResourceRow[]) {
1023+
if (resource.kind !== "CronJob") {
1024+
return [];
1025+
}
1026+
1027+
return resources.filter((item) => item.kind === "Job" && ownsResource(resource, item));
1028+
}
1029+
10131030
function nodePodsFor(resource: ResourceRow, resources: ResourceRow[]) {
10141031
if (resource.kind !== "Node") {
10151032
return [];
@@ -1329,8 +1346,18 @@ function hierarchyFor(resource: ResourceRow, resources: ResourceRow[]): Hierarch
13291346
];
13301347
}
13311348

1349+
if (resource.kind === "CronJob") {
1350+
const jobs = cronJobJobsFor(resource, resources);
1351+
const pods = workloadPodsFor(resource, resources);
1352+
return [
1353+
{ title: "Jobs", resources: jobs },
1354+
{ title: "Pods", resources: pods },
1355+
{ title: "Config nearby", resources: resources.filter((item) => item.namespace === resource.namespace && ["ConfigMap", "Secret"].includes(item.kind)) },
1356+
];
1357+
}
1358+
13321359
if (workloadKinds.has(resource.kind)) {
1333-
const pods = resources.filter((item) => item.kind === "Pod" && item.namespace === resource.namespace && ownsPod(resource, item));
1360+
const pods = workloadPodsFor(resource, resources);
13341361
const services = resources.filter(
13351362
(item) =>
13361363
item.kind === "Service" &&

src/lib/resourceRelationships.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ import type { ResourceRow } from "../types/kube";
22

33
export const workloadKinds = new Set(["Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob", "ReplicaSet"]);
44

5+
export function ownsResource(owner: ResourceRow, child: ResourceRow) {
6+
return child.namespace === owner.namespace && child.owner === `${owner.kind}/${owner.name}`;
7+
}
8+
59
export function ownsPod(owner: ResourceRow, pod: ResourceRow) {
6-
if (pod.owner.includes(`/${owner.name}`)) {
10+
if (ownsResource(owner, pod)) {
711
return true;
812
}
913
if (owner.kind === "Deployment" && pod.owner.startsWith(`ReplicaSet/${owner.name}-`)) {

0 commit comments

Comments
 (0)