use crate::{NylError, Result};
pub fn filter_manifests_by_kind(
manifests: Vec<serde_json::Value>,
only_kind: &[String],
exclude_kind: &[String],
) -> Result<Vec<serde_json::Value>> {
if only_kind.is_empty() && exclude_kind.is_empty() {
return Ok(manifests);
}
if !only_kind.is_empty() && !exclude_kind.is_empty() {
return Err(NylError::Config(
"Cannot specify both --only-kind and --exclude-kind".to_string(),
));
}
let original_count = manifests.len();
let filtered: Vec<serde_json::Value> = manifests
.into_iter()
.filter(|manifest| {
let kind = manifest.get("kind").and_then(|k| k.as_str()).unwrap_or("");
let api_version = manifest.get("apiVersion").and_then(|a| a.as_str()).unwrap_or("");
let full_kind = format!("{}/{}", api_version, kind);
if !only_kind.is_empty() {
return only_kind.iter().any(|filter| match_kind(filter, kind, &full_kind));
}
if !exclude_kind.is_empty() {
return !exclude_kind.iter().any(|filter| match_kind(filter, kind, &full_kind));
}
true
})
.collect();
if filtered.is_empty() && original_count > 0 {
if !only_kind.is_empty() {
tracing::warn!("No resources matched --only-kind filter: {}", only_kind.join(", "));
} else if !exclude_kind.is_empty() {
tracing::warn!(
"All resources excluded by --exclude-kind filter: {}",
exclude_kind.join(", ")
);
}
}
Ok(filtered)
}
fn match_kind(filter: &str, kind: &str, full_kind: &str) -> bool {
if filter.contains('/') {
filter == full_kind
} else {
filter == kind
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_filter_empty_filters() {
let manifests = vec![
json!({"apiVersion": "v1", "kind": "ConfigMap"}),
json!({"apiVersion": "v1", "kind": "Secret"}),
];
let result = filter_manifests_by_kind(manifests.clone(), &[], &[]).unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_filter_only_kind_single() {
let manifests = vec![
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "test1"}}),
json!({"apiVersion": "v1", "kind": "Secret", "metadata": {"name": "test2"}}),
json!({"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "test3"}}),
];
let result = filter_manifests_by_kind(manifests, &["ConfigMap".to_string()], &[]).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0]["kind"], "ConfigMap");
}
#[test]
fn test_filter_only_kind_multiple() {
let manifests = vec![
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "test1"}}),
json!({"apiVersion": "v1", "kind": "Secret", "metadata": {"name": "test2"}}),
json!({"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "test3"}}),
];
let result =
filter_manifests_by_kind(manifests, &["ConfigMap".to_string(), "Secret".to_string()], &[]).unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_filter_exclude_kind() {
let manifests = vec![
json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "test1"}}),
json!({"apiVersion": "v1", "kind": "Secret", "metadata": {"name": "test2"}}),
json!({"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "test3"}}),
];
let result = filter_manifests_by_kind(manifests, &[], &["Secret".to_string()]).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0]["kind"], "ConfigMap");
assert_eq!(result[1]["kind"], "Deployment");
}
#[test]
fn test_filter_with_api_version() {
let manifests = vec![
json!({"apiVersion": "v1", "kind": "Secret", "metadata": {"name": "test1"}}),
json!({"apiVersion": "custom.io/v1", "kind": "Secret", "metadata": {"name": "test2"}}),
];
let result = filter_manifests_by_kind(manifests, &["v1/Secret".to_string()], &[]).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0]["metadata"]["name"], "test1");
}
#[test]
fn test_filter_case_sensitive() {
let manifests = vec![json!({"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "test1"}})];
let result = filter_manifests_by_kind(manifests, &["configmap".to_string()], &[]).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_filter_crd_short_form() {
let manifests = vec![
json!({"apiVersion": "apiextensions.k8s.io/v1", "kind": "CustomResourceDefinition", "metadata": {"name": "test1"}}),
json!({"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "test2"}}),
];
let result = filter_manifests_by_kind(manifests, &["CustomResourceDefinition".to_string()], &[]).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0]["kind"], "CustomResourceDefinition");
}
#[test]
fn test_filter_mutual_exclusivity() {
let manifests = vec![json!({"apiVersion": "v1", "kind": "ConfigMap"})];
let result = filter_manifests_by_kind(manifests, &["ConfigMap".to_string()], &["Secret".to_string()]);
assert!(result.is_err());
}
#[test]
fn test_match_kind_with_slash() {
assert!(match_kind("v1/Secret", "Secret", "v1/Secret"));
assert!(!match_kind("v1/Secret", "Secret", "custom.io/v1/Secret"));
}
#[test]
fn test_match_kind_without_slash() {
assert!(match_kind("Secret", "Secret", "v1/Secret"));
assert!(match_kind("Secret", "Secret", "custom.io/v1/Secret"));
assert!(!match_kind("ConfigMap", "Secret", "v1/Secret"));
}
}