use std::collections::HashSet;
use std::fs;
use std::path::Path;
use serde_json::Value;
use crate::cache::{json_files, subdirs};
use crate::lookup::ProviderOrigin;
use super::candidate::{ApiVersionCandidate, InferenceSource};
#[must_use]
pub(crate) fn scan_k8s_cache(
root: &Path,
kind: &str,
configured_source_ids: &HashSet<String>,
inference_versions: &HashSet<String>,
) -> Vec<ApiVersionCandidate> {
let mut out = Vec::new();
for (source_id, source_path) in subdirs(root) {
if !configured_source_ids.contains(&source_id) {
continue;
}
for (version_name, version_path) in subdirs(&source_path) {
if !inference_versions.contains(&version_name) {
continue;
}
for p in json_files(&version_path) {
if let Some(api_version) = read_k8s_api_version(&p, kind) {
out.push(ApiVersionCandidate {
api_version,
source: InferenceSource::LocalCacheScan,
origin: ProviderOrigin::KubernetesOpenApi,
});
}
}
}
}
out
}
#[must_use]
pub(crate) fn scan_crd_cache(
root: &Path,
kind: &str,
origin: ProviderOrigin,
configured_source_ids: &HashSet<String>,
) -> Vec<ApiVersionCandidate> {
let mut out = Vec::new();
let kind_lc = kind.to_ascii_lowercase();
for (source_id, source_path) in subdirs(root) {
if !configured_source_ids.contains(&source_id) {
continue;
}
out.extend(scan_crd_source_dir(&source_path, &kind_lc, origin));
}
out
}
#[must_use]
pub(crate) fn scan_crd_source_dir(
source_root: &Path,
kind_lc: &str,
origin: ProviderOrigin,
) -> Vec<ApiVersionCandidate> {
let mut out = Vec::new();
for (group_name, group_path) in subdirs(source_root) {
for p in json_files(&group_path) {
let Some(filename) = p.file_name().and_then(|s| s.to_str()) else {
continue;
};
if let Some(version) = match_crd_filename(filename, kind_lc) {
out.push(ApiVersionCandidate {
api_version: format!("{group_name}/{version}"),
source: InferenceSource::LocalCacheScan,
origin,
});
}
}
}
out
}
fn read_k8s_api_version(path: &Path, kind: &str) -> Option<String> {
let bytes = fs::read(path).ok()?;
let doc: Value = serde_json::from_slice(&bytes).ok()?;
let entries = doc.get("x-kubernetes-group-version-kind")?.as_array()?;
for entry in entries {
let entry_kind = entry.get("kind").and_then(|v| v.as_str())?;
if entry_kind != kind {
continue;
}
let group = entry.get("group").and_then(|v| v.as_str()).unwrap_or("");
let version = entry.get("version").and_then(|v| v.as_str()).unwrap_or("");
if version.is_empty() {
continue;
}
return Some(if group.is_empty() {
version.to_string()
} else {
format!("{group}/{version}")
});
}
None
}
pub(crate) fn match_crd_filename(filename: &str, kind_lc: &str) -> Option<String> {
let prefix = format!("{kind_lc}_");
let stem = filename.strip_suffix(".json")?;
let version = stem.strip_prefix(&prefix)?;
if version.is_empty() {
return None;
}
Some(version.to_string())
}