use serde_json::Value;
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum YamlType {
K8sDeployment,
K8sService,
K8sConfigMap,
K8sSecret,
K8sIngress,
K8sHPA,
K8sCronJob,
K8sJob,
K8sPVC,
K8sNetworkPolicy,
K8sStatefulSet,
K8sDaemonSet,
K8sRole,
K8sClusterRole,
K8sRoleBinding,
K8sClusterRoleBinding,
K8sServiceAccount,
K8sGeneric,
GitLabCI,
GitHubActions,
DockerCompose,
Prometheus,
Alertmanager,
HelmValues,
Ansible,
OpenAPI,
Generic,
}
impl YamlType {
pub fn to_schema_key(&self) -> &'static str {
match self {
YamlType::K8sDeployment => "k8s/deployment",
YamlType::K8sService => "k8s/service",
YamlType::K8sConfigMap => "k8s/configmap",
YamlType::K8sSecret => "k8s/secret",
YamlType::K8sIngress => "k8s/ingress",
YamlType::K8sHPA => "k8s/horizontalpodautoscaler",
YamlType::K8sCronJob => "k8s/cronjob",
YamlType::K8sJob => "k8s/job",
YamlType::K8sPVC => "k8s/persistentvolumeclaim",
YamlType::K8sNetworkPolicy => "k8s/networkpolicy",
YamlType::K8sStatefulSet => "k8s/statefulset",
YamlType::K8sDaemonSet => "k8s/daemonset",
YamlType::K8sRole => "k8s/role",
YamlType::K8sClusterRole => "k8s/clusterrole",
YamlType::K8sRoleBinding => "k8s/rolebinding",
YamlType::K8sClusterRoleBinding => "k8s/clusterrolebinding",
YamlType::K8sServiceAccount => "k8s/serviceaccount",
YamlType::K8sGeneric => "k8s/generic",
YamlType::GitLabCI => "gitlab-ci",
YamlType::GitHubActions => "github-actions",
YamlType::DockerCompose => "docker-compose",
YamlType::Prometheus => "prometheus",
YamlType::Alertmanager => "alertmanager",
YamlType::HelmValues => "helm-values",
YamlType::Ansible => "ansible",
YamlType::OpenAPI => "openapi",
YamlType::Generic => "generic",
}
}
pub fn is_kubernetes(&self) -> bool {
matches!(
self,
YamlType::K8sDeployment
| YamlType::K8sService
| YamlType::K8sConfigMap
| YamlType::K8sSecret
| YamlType::K8sIngress
| YamlType::K8sHPA
| YamlType::K8sCronJob
| YamlType::K8sJob
| YamlType::K8sPVC
| YamlType::K8sNetworkPolicy
| YamlType::K8sStatefulSet
| YamlType::K8sDaemonSet
| YamlType::K8sRole
| YamlType::K8sClusterRole
| YamlType::K8sRoleBinding
| YamlType::K8sClusterRoleBinding
| YamlType::K8sServiceAccount
| YamlType::K8sGeneric
)
}
pub fn display_name(&self) -> &'static str {
match self {
YamlType::K8sDeployment => "Kubernetes Deployment",
YamlType::K8sService => "Kubernetes Service",
YamlType::K8sConfigMap => "Kubernetes ConfigMap",
YamlType::K8sSecret => "Kubernetes Secret",
YamlType::K8sIngress => "Kubernetes Ingress",
YamlType::K8sHPA => "Kubernetes HPA",
YamlType::K8sCronJob => "Kubernetes CronJob",
YamlType::K8sJob => "Kubernetes Job",
YamlType::K8sPVC => "Kubernetes PVC",
YamlType::K8sNetworkPolicy => "Kubernetes NetworkPolicy",
YamlType::K8sStatefulSet => "Kubernetes StatefulSet",
YamlType::K8sDaemonSet => "Kubernetes DaemonSet",
YamlType::K8sRole => "Kubernetes Role",
YamlType::K8sClusterRole => "Kubernetes ClusterRole",
YamlType::K8sRoleBinding => "Kubernetes RoleBinding",
YamlType::K8sClusterRoleBinding => "Kubernetes ClusterRoleBinding",
YamlType::K8sServiceAccount => "Kubernetes ServiceAccount",
YamlType::K8sGeneric => "Kubernetes Resource",
YamlType::GitLabCI => "GitLab CI",
YamlType::GitHubActions => "GitHub Actions",
YamlType::DockerCompose => "Docker Compose",
YamlType::Prometheus => "Prometheus Config",
YamlType::Alertmanager => "Alertmanager Config",
YamlType::HelmValues => "Helm Values",
YamlType::Ansible => "Ansible Playbook",
YamlType::OpenAPI => "OpenAPI Spec",
YamlType::Generic => "Generic YAML",
}
}
}
impl std::fmt::Display for YamlType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display_name())
}
}
pub fn detect_type(data: &Value) -> YamlType {
let obj = match data.as_object() {
Some(o) => o,
None => {
if data.as_array().is_some() && looks_like_ansible(data) {
return YamlType::Ansible;
}
return YamlType::Generic;
}
};
if let Some(schema_url) = obj.get("$schema").and_then(|s| s.as_str()) {
return detect_type_from_schema_url(schema_url);
}
if let (Some(_api_version), Some(kind)) = (
obj.get("apiVersion").and_then(|v| v.as_str()),
obj.get("kind").and_then(|v| v.as_str()),
) {
return match kind {
"Deployment" => YamlType::K8sDeployment,
"Service" => YamlType::K8sService,
"ConfigMap" => YamlType::K8sConfigMap,
"Secret" => YamlType::K8sSecret,
"Ingress" => YamlType::K8sIngress,
"HorizontalPodAutoscaler" => YamlType::K8sHPA,
"CronJob" => YamlType::K8sCronJob,
"Job" => YamlType::K8sJob,
"PersistentVolumeClaim" => YamlType::K8sPVC,
"NetworkPolicy" => YamlType::K8sNetworkPolicy,
"StatefulSet" => YamlType::K8sStatefulSet,
"DaemonSet" => YamlType::K8sDaemonSet,
"Role" => YamlType::K8sRole,
"ClusterRole" => YamlType::K8sClusterRole,
"RoleBinding" => YamlType::K8sRoleBinding,
"ClusterRoleBinding" => YamlType::K8sClusterRoleBinding,
"ServiceAccount" => YamlType::K8sServiceAccount,
_ => YamlType::K8sGeneric,
};
}
if obj.contains_key("stages") || obj.values().any(|v| v.get("script").is_some()) {
return YamlType::GitLabCI;
}
if obj.contains_key("on") && obj.contains_key("jobs") {
return YamlType::GitHubActions;
}
if let Some(services) = obj.get("services").and_then(|s| s.as_object())
&& !services.is_empty()
&& services.values().any(|v| v.is_object())
{
return YamlType::DockerCompose;
}
if obj.contains_key("scrape_configs")
|| obj
.get("global")
.and_then(|g| g.get("scrape_interval"))
.is_some()
{
return YamlType::Prometheus;
}
if obj.contains_key("route") && obj.contains_key("receivers") {
return YamlType::Alertmanager;
}
if looks_like_helm_values(obj) {
return YamlType::HelmValues;
}
if obj.contains_key("openapi") || obj.contains_key("swagger") {
return YamlType::OpenAPI;
}
YamlType::Generic
}
fn detect_type_from_schema_url(url: &str) -> YamlType {
if url.contains("kubernetesjsonschema.dev") || url.contains("kubernetes") {
YamlType::K8sGeneric
} else if url.contains("gitlab-ci") {
YamlType::GitLabCI
} else if url.contains("github-workflow") || url.contains("github-actions") {
YamlType::GitHubActions
} else if url.contains("docker-compose") {
YamlType::DockerCompose
} else if url.contains("prometheus") {
YamlType::Prometheus
} else if url.contains("alertmanager") {
YamlType::Alertmanager
} else if url.contains("openapi") || url.contains("swagger") {
YamlType::OpenAPI
} else {
YamlType::Generic
}
}
fn looks_like_ansible(data: &Value) -> bool {
data.as_array()
.map(|arr| {
arr.iter().all(|item| {
item.get("hosts").is_some()
|| item.get("tasks").is_some()
|| item.get("roles").is_some()
})
})
.unwrap_or(false)
}
fn looks_like_helm_values(obj: &serde_json::Map<String, Value>) -> bool {
let has_common_helm_keys = obj.contains_key("image")
|| obj.contains_key("replicaCount")
|| obj.contains_key("service")
&& obj.get("service").and_then(|s| s.get("type")).is_some();
let no_k8s_markers = !obj.contains_key("apiVersion") && !obj.contains_key("kind");
has_common_helm_keys && no_k8s_markers
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_detect_k8s_deployment() {
let data = json!({
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": { "name": "test" },
"spec": {}
});
assert_eq!(detect_type(&data), YamlType::K8sDeployment);
}
#[test]
fn test_detect_k8s_service() {
let data = json!({
"apiVersion": "v1",
"kind": "Service",
"metadata": { "name": "test" }
});
assert_eq!(detect_type(&data), YamlType::K8sService);
}
#[test]
fn test_detect_gitlab_ci() {
let data = json!({
"stages": ["build", "test"],
"build_job": { "script": ["echo hello"] }
});
assert_eq!(detect_type(&data), YamlType::GitLabCI);
}
#[test]
fn test_detect_github_actions() {
let data = json!({
"on": ["push"],
"jobs": { "build": {} }
});
assert_eq!(detect_type(&data), YamlType::GitHubActions);
}
#[test]
fn test_detect_docker_compose() {
let data = json!({
"services": {
"web": { "image": "nginx" }
}
});
assert_eq!(detect_type(&data), YamlType::DockerCompose);
}
#[test]
fn test_detect_prometheus() {
let data = json!({
"global": { "scrape_interval": "15s" },
"scrape_configs": []
});
assert_eq!(detect_type(&data), YamlType::Prometheus);
}
#[test]
fn test_detect_alertmanager() {
let data = json!({
"route": { "receiver": "default" },
"receivers": [{ "name": "default" }]
});
assert_eq!(detect_type(&data), YamlType::Alertmanager);
}
#[test]
fn test_detect_openapi() {
let data = json!({
"openapi": "3.0.0",
"info": { "title": "API", "version": "1.0" }
});
assert_eq!(detect_type(&data), YamlType::OpenAPI);
}
#[test]
fn test_schema_key_conversion() {
assert_eq!(YamlType::K8sDeployment.to_schema_key(), "k8s/deployment");
assert_eq!(YamlType::GitLabCI.to_schema_key(), "gitlab-ci");
assert_eq!(YamlType::DockerCompose.to_schema_key(), "docker-compose");
}
}