use crate::kubernetes::{extract_gvk, GroupVersionKind};
use crate::Result;
use serde_json::Value;
const PRIORITY_NAMESPACE: u32 = 0;
const PRIORITY_CRD: u32 = 10;
const PRIORITY_SERVICE_ACCOUNT: u32 = 20;
const PRIORITY_RBAC: u32 = 25; const PRIORITY_CONFIG: u32 = 30; const PRIORITY_SERVICE: u32 = 40;
const PRIORITY_WORKLOAD: u32 = 50; const PRIORITY_OTHER: u32 = 100;
pub struct ResourceOrdering;
impl ResourceOrdering {
pub fn sort_by_priority(resources: &mut [Value]) -> Result<()> {
resources.sort_by_cached_key(|resource| {
extract_gvk(resource)
.ok()
.map_or(PRIORITY_OTHER, |gvk| Self::get_priority(&gvk))
});
Ok(())
}
fn get_priority(gvk: &GroupVersionKind) -> u32 {
match gvk.kind.as_str() {
"Namespace" => PRIORITY_NAMESPACE,
"CustomResourceDefinition" => PRIORITY_CRD,
"ServiceAccount" => PRIORITY_SERVICE_ACCOUNT,
"Role" | "RoleBinding" | "ClusterRole" | "ClusterRoleBinding" => PRIORITY_RBAC,
"ConfigMap" | "Secret" => PRIORITY_CONFIG,
"Service" | "Endpoints" | "EndpointSlice" => PRIORITY_SERVICE,
"Deployment" | "StatefulSet" | "DaemonSet" | "Job" | "CronJob" | "ReplicaSet" | "Pod" => PRIORITY_WORKLOAD,
_ => PRIORITY_OTHER,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_namespace_sorted_first() {
let mut resources = vec![
json!({
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {"name": "deploy"}
}),
json!({
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {"name": "ns"}
}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
assert_eq!(resources[0]["kind"], "Namespace");
assert_eq!(resources[1]["kind"], "Deployment");
}
#[test]
fn test_crd_before_custom_resource() {
let mut resources = vec![
json!({
"apiVersion": "custom.io/v1",
"kind": "CustomThing",
"metadata": {"name": "custom"}
}),
json!({
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": {"name": "crd"}
}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
assert_eq!(resources[0]["kind"], "CustomResourceDefinition");
assert_eq!(resources[1]["kind"], "CustomThing");
}
#[test]
fn test_configmap_before_deployment() {
let mut resources = vec![
json!({
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {"name": "deploy"}
}),
json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {"name": "config"}
}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
assert_eq!(resources[0]["kind"], "ConfigMap");
assert_eq!(resources[1]["kind"], "Deployment");
}
#[test]
fn test_service_account_before_rbac() {
let mut resources = vec![
json!({
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {"name": "rb"}
}),
json!({
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {"name": "sa"}
}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
assert_eq!(resources[0]["kind"], "ServiceAccount");
assert_eq!(resources[1]["kind"], "RoleBinding");
}
#[test]
fn test_full_ordering_sequence() {
let mut resources = vec![
json!({"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "d"}}),
json!({"apiVersion": "v1", "kind": "Service", "metadata": {"name": "svc"}}),
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "cm"}}),
json!({"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "Role", "metadata": {"name": "r"}}),
json!({"apiVersion": "v1", "kind": "ServiceAccount", "metadata": {"name": "sa"}}),
json!({"apiVersion": "apiextensions.k8s.io/v1", "kind": "CustomResourceDefinition", "metadata": {"name": "crd"}}),
json!({"apiVersion": "v1", "kind": "Namespace", "metadata": {"name": "ns"}}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
let kinds: Vec<&str> = resources.iter().map(|r| r["kind"].as_str().unwrap()).collect();
assert_eq!(
kinds,
vec![
"Namespace",
"CustomResourceDefinition",
"ServiceAccount",
"Role",
"ConfigMap",
"Service",
"Deployment"
]
);
}
#[test]
fn test_stable_sort_preserves_order() {
let mut resources = vec![
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "cm1"}}),
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "cm2"}}),
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "cm3"}}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
assert_eq!(resources[0]["metadata"]["name"], "cm1");
assert_eq!(resources[1]["metadata"]["name"], "cm2");
assert_eq!(resources[2]["metadata"]["name"], "cm3");
}
#[test]
fn test_secret_same_priority_as_configmap() {
let mut resources = vec![
json!({"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "d"}}),
json!({"apiVersion": "v1", "kind": "Secret", "metadata": {"name": "s"}}),
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "cm"}}),
];
ResourceOrdering::sort_by_priority(&mut resources).unwrap();
assert_eq!(resources[2]["kind"], "Deployment");
let first_two_kinds: Vec<&str> = resources[..2].iter().map(|r| r["kind"].as_str().unwrap()).collect();
assert!(first_two_kinds.contains(&"Secret"));
assert!(first_two_kinds.contains(&"ConfigMap"));
}
#[test]
fn test_get_priority_namespace() {
let gvk = GroupVersionKind {
group: String::new(),
version: "v1".to_string(),
kind: "Namespace".to_string(),
};
assert_eq!(ResourceOrdering::get_priority(&gvk), PRIORITY_NAMESPACE);
}
#[test]
fn test_get_priority_crd() {
let gvk = GroupVersionKind {
group: "apiextensions.k8s.io".to_string(),
version: "v1".to_string(),
kind: "CustomResourceDefinition".to_string(),
};
assert_eq!(ResourceOrdering::get_priority(&gvk), PRIORITY_CRD);
}
#[test]
fn test_get_priority_unknown() {
let gvk = GroupVersionKind {
group: "custom.io".to_string(),
version: "v1".to_string(),
kind: "UnknownResource".to_string(),
};
assert_eq!(ResourceOrdering::get_priority(&gvk), PRIORITY_OTHER);
}
}