1use super::compression::{CompressionConfig, compress_tool_output};
15use super::error::{ErrorCategory, format_error_for_llm};
16use rig::completion::ToolDefinition;
17use rig::tool::Tool;
18use serde::{Deserialize, Serialize};
19use serde_json::json;
20use std::path::PathBuf;
21
22use crate::analyzer::kubelint::{
23 KubelintConfig, LintResult, Severity, lint, lint_content, lint_file,
24};
25
26#[derive(Debug, Deserialize)]
28pub struct KubelintArgs {
29 #[serde(default)]
32 pub path: Option<String>,
33
34 #[serde(default)]
36 pub content: Option<String>,
37
38 #[serde(default)]
40 pub include: Vec<String>,
41
42 #[serde(default)]
44 pub exclude: Vec<String>,
45
46 #[serde(default)]
48 pub threshold: Option<String>,
49}
50
51#[derive(Debug, thiserror::Error)]
53#[error("Kubelint error: {0}")]
54pub struct KubelintError(String);
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct KubelintTool {
70 project_path: PathBuf,
71}
72
73impl KubelintTool {
74 pub fn new(project_path: PathBuf) -> Self {
75 Self { project_path }
76 }
77
78 fn parse_threshold(threshold: &str) -> Severity {
79 match threshold.to_lowercase().as_str() {
80 "error" => Severity::Error,
81 "warning" => Severity::Warning,
82 "info" => Severity::Info,
83 _ => Severity::Warning,
84 }
85 }
86
87 fn get_check_category(code: &str) -> &'static str {
89 match code {
90 "privileged-container"
92 | "privilege-escalation"
93 | "run-as-non-root"
94 | "read-only-root-fs"
95 | "drop-net-raw-capability"
96 | "hostnetwork"
97 | "hostpid"
98 | "hostipc"
99 | "host-mounts"
100 | "writable-host-mount"
101 | "docker-sock"
102 | "unsafe-proc-mount"
103 | "scc-deny-privileged-container" => "security",
104
105 "latest-tag"
107 | "no-liveness-probe"
108 | "no-readiness-probe"
109 | "unset-cpu-requirements"
110 | "unset-memory-requirements"
111 | "minimum-replicas"
112 | "no-anti-affinity"
113 | "no-rolling-update-strategy"
114 | "default-service-account"
115 | "deprecated-service-account"
116 | "env-var-secret"
117 | "read-secret-from-env-var"
118 | "priority-class-name"
119 | "no-node-affinity"
120 | "restart-policy"
121 | "sysctls"
122 | "dnsconfig-options" => "best-practice",
123
124 "access-to-secrets"
126 | "access-to-create-pods"
127 | "cluster-admin-role-binding"
128 | "wildcard-in-rules" => "rbac",
129
130 "dangling-service"
132 | "dangling-ingress"
133 | "dangling-horizontalpodautoscaler"
134 | "dangling-networkpolicy"
135 | "mismatching-selector"
136 | "duplicate-env-var"
137 | "invalid-target-ports"
138 | "non-existent-service-account"
139 | "non-isolated-pod"
140 | "use-namespace"
141 | "env-var-value-from"
142 | "job-ttl-seconds-after-finished" => "validation",
143
144 "ssh-port" | "privileged-ports" | "liveness-port" | "readiness-port"
146 | "startup-port" => "ports",
147
148 "pdb-max-unavailable" | "pdb-min-available" | "pdb-unhealthy-pod-eviction-policy" => {
150 "disruption-budget"
151 }
152
153 "hpa-minimum-replicas" => "autoscaling",
155
156 "no-extensions-v1beta" => "deprecated-api",
158
159 "service-type" => "service",
161
162 _ => "other",
163 }
164 }
165
166 fn get_priority(severity: Severity, code: &str) -> &'static str {
168 let category = Self::get_check_category(code);
169 match (severity, category) {
170 (Severity::Error, "security") => "critical",
171 (Severity::Error, "rbac") => "critical",
172 (Severity::Error, _) => "high",
173 (Severity::Warning, "security") => "high",
174 (Severity::Warning, "rbac") => "high",
175 (Severity::Warning, "validation") => "medium",
176 (Severity::Warning, "best-practice") => "medium",
177 (Severity::Warning, _) => "medium",
178 (Severity::Info, _) => "low",
179 }
180 }
181
182 fn format_result(result: &LintResult, source: &str) -> String {
184 let enriched_failures: Vec<serde_json::Value> = result
186 .failures
187 .iter()
188 .map(|f| {
189 let code = f.code.as_str();
190 let category = Self::get_check_category(code);
191 let priority = Self::get_priority(f.severity, code);
192
193 json!({
194 "check": code,
195 "severity": format!("{:?}", f.severity).to_lowercase(),
196 "priority": priority,
197 "category": category,
198 "message": f.message,
199 "object": {
200 "name": f.object_name,
201 "kind": f.object_kind,
202 "namespace": f.object_namespace,
203 },
204 "file": f.file_path.display().to_string(),
205 "line": f.line,
206 "remediation": f.remediation,
207 })
208 })
209 .collect();
210
211 let critical: Vec<_> = enriched_failures
213 .iter()
214 .filter(|f| f["priority"] == "critical")
215 .cloned()
216 .collect();
217 let high: Vec<_> = enriched_failures
218 .iter()
219 .filter(|f| f["priority"] == "high")
220 .cloned()
221 .collect();
222 let medium: Vec<_> = enriched_failures
223 .iter()
224 .filter(|f| f["priority"] == "medium")
225 .cloned()
226 .collect();
227 let low: Vec<_> = enriched_failures
228 .iter()
229 .filter(|f| f["priority"] == "low")
230 .cloned()
231 .collect();
232
233 let mut by_category: std::collections::HashMap<&str, usize> =
235 std::collections::HashMap::new();
236 for f in &result.failures {
237 let cat = Self::get_check_category(f.code.as_str());
238 *by_category.entry(cat).or_default() += 1;
239 }
240
241 let decision_context = if critical.is_empty() && high.is_empty() {
243 if medium.is_empty() && low.is_empty() {
244 "Kubernetes manifests follow security best practices. No issues found."
245 } else if medium.is_empty() {
246 "Minor improvements possible. Low priority issues only."
247 } else {
248 "Good baseline. Medium priority improvements recommended."
249 }
250 } else if !critical.is_empty() {
251 "CRITICAL security issues found. Fix before deployment to production."
252 } else {
253 "High priority issues found. Review security and best practice violations."
254 };
255
256 let mut output = json!({
258 "source": source,
259 "success": result.summary.passed,
260 "decision_context": decision_context,
261 "tool_guidance": "Use kubelint for K8s manifest security/best practices. Use helmlint for Helm chart structure/template syntax.",
262 "summary": {
263 "total_issues": result.failures.len(),
264 "objects_analyzed": result.summary.objects_analyzed,
265 "checks_run": result.summary.checks_run,
266 "by_priority": {
267 "critical": critical.len(),
268 "high": high.len(),
269 "medium": medium.len(),
270 "low": low.len(),
271 },
272 "by_category": by_category,
273 },
274 "action_plan": {
275 "critical": critical,
276 "high": high,
277 "medium": medium,
278 "low": low,
279 },
280 });
281
282 if !enriched_failures.is_empty() {
284 let quick_fixes: Vec<String> = enriched_failures
285 .iter()
286 .filter(|f| f["priority"] == "critical" || f["priority"] == "high")
287 .take(5)
288 .map(|f| {
289 let remediation = f["remediation"]
290 .as_str()
291 .unwrap_or("Review the check documentation.");
292 format!(
293 "{}/{}: {} - {}",
294 f["object"]["kind"].as_str().unwrap_or(""),
295 f["object"]["name"].as_str().unwrap_or(""),
296 f["check"].as_str().unwrap_or(""),
297 remediation
298 )
299 })
300 .collect();
301
302 if !quick_fixes.is_empty() {
303 output["quick_fixes"] = json!(quick_fixes);
304 }
305 }
306
307 if !result.parse_errors.is_empty() {
308 output["parse_errors"] = json!(result.parse_errors);
309 }
310
311 let config = CompressionConfig::default();
314 compress_tool_output(&output, "kubelint", &config)
315 }
316}
317
318impl Tool for KubelintTool {
319 const NAME: &'static str = "kubelint";
320
321 type Error = KubelintError;
322 type Args = KubelintArgs;
323 type Output = String;
324
325 async fn definition(&self, _prompt: String) -> ToolDefinition {
326 ToolDefinition {
327 name: Self::NAME.to_string(),
328 description: "Native Kubernetes manifest linting for SECURITY and BEST PRACTICES.
329
330Analyzes rendered K8s manifests (YAML files, Helm charts, Kustomize) for:
331- **Security**: privileged containers, privilege escalation, host access, capabilities
332- **Resources**: missing limits/requests, missing probes (liveness/readiness)
333- **RBAC**: overprivileged roles, cluster-admin bindings, wildcard permissions
334- **Best Practice**: latest tag, missing labels, deprecated APIs, service accounts
335
336**Use kubelint for:** Security analysis of deployed/rendered Kubernetes resources.
337**Use helmlint for:** Helm chart structure, template syntax, Chart.yaml validation.
338
339**Parameters:**
340- path: K8s manifest file, directory, Helm chart dir, or Kustomize dir
341- content: Inline YAML to lint (alternative to path)
342- include: Run only specific checks (e.g., ['privileged-container'])
343- exclude: Skip specific checks (e.g., ['minimum-replicas'])
344- threshold: Minimum severity to report ('error', 'warning', 'info')
345
346**Output:** Issues categorized by priority (critical/high/medium/low) with remediation steps.
347Large outputs are compressed with retrieval_id - use retrieve_output for full details."
348 .to_string(),
349 parameters: json!({
350 "type": "object",
351 "properties": {
352 "path": {
353 "type": "string",
354 "description": "Path to K8s manifest(s) relative to project root. Can be: \
355 single YAML file, directory with YAMLs, Helm chart directory, or Kustomize directory."
356 },
357 "content": {
358 "type": "string",
359 "description": "Inline YAML content to lint. Use this to validate generated manifests before writing."
360 },
361 "include": {
362 "type": "array",
363 "items": { "type": "string" },
364 "description": "Specific checks to run (e.g., ['privileged-container', 'latest-tag']). If empty, runs all default checks."
365 },
366 "exclude": {
367 "type": "array",
368 "items": { "type": "string" },
369 "description": "Checks to skip (e.g., ['no-liveness-probe', 'minimum-replicas'])"
370 },
371 "threshold": {
372 "type": "string",
373 "enum": ["error", "warning", "info"],
374 "description": "Minimum severity to report. Default is 'warning'."
375 }
376 }
377 }),
378 }
379 }
380
381 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
382 let mut config = KubelintConfig::default().with_all_builtin();
384
385 for check in &args.include {
387 config = config.include(check.as_str());
388 }
389
390 for check in &args.exclude {
392 config = config.exclude(check.as_str());
393 }
394
395 if let Some(threshold) = &args.threshold {
397 config = config.with_threshold(Self::parse_threshold(threshold));
398 }
399
400 let (result, source) = if args.content.as_ref().is_some_and(|c| !c.trim().is_empty()) {
404 (
406 lint_content(args.content.as_ref().unwrap(), &config),
407 "<inline>".to_string(),
408 )
409 } else if let Some(path) = &args.path {
410 let full_path = self.project_path.join(path);
412
413 if !full_path.exists() {
414 return Ok(format_error_for_llm(
415 "kubelint",
416 ErrorCategory::FileNotFound,
417 &format!("Path '{}' does not exist", full_path.display()),
418 Some(vec![
419 "Check if the path is correct relative to project root",
420 "Use list_directory to explore available paths",
421 "Provide inline YAML via 'content' parameter instead",
422 ]),
423 ));
424 }
425
426 if full_path.is_file() {
427 (lint_file(&full_path, &config), path.clone())
428 } else {
429 (lint(&full_path, &config), path.clone())
430 }
431 } else {
432 let candidates = [
434 "kubernetes",
435 "k8s",
436 "manifests",
437 "deploy",
438 "deployment",
439 "helm",
440 "charts",
441 "test-lint", "test-lint/k8s", ".",
444 ];
445
446 let mut found = None;
447 for candidate in &candidates {
448 let candidate_path = self.project_path.join(candidate);
449 if candidate_path.exists() {
450 if candidate_path.join("Chart.yaml").exists()
452 || candidate_path.join("kustomization.yaml").exists()
453 || candidate_path.join("kustomization.yml").exists()
454 {
455 found = Some((candidate_path, candidate.to_string()));
456 break;
457 }
458 if let Ok(entries) = std::fs::read_dir(&candidate_path) {
460 let has_yaml = entries.filter_map(|e| e.ok()).any(|e| {
461 e.path()
462 .extension()
463 .map(|ext| ext == "yaml" || ext == "yml")
464 .unwrap_or(false)
465 });
466 if has_yaml {
467 found = Some((candidate_path, candidate.to_string()));
468 break;
469 }
470 }
471 }
472 }
473
474 if let Some((path, name)) = found {
475 (lint(&path, &config), name)
476 } else {
477 return Ok(format_error_for_llm(
478 "kubelint",
479 ErrorCategory::ValidationFailed,
480 "No valid Kubernetes manifests found",
481 Some(vec![
482 "Specify a path with 'path' parameter (e.g., 'k8s/', 'deployment.yaml')",
483 "Provide inline YAML via 'content' parameter",
484 "Ensure files have .yaml or .yml extension",
485 "Files must have 'apiVersion' and 'kind' fields to be valid K8s manifests",
486 ]),
487 ));
488 }
489 };
490
491 if !result.parse_errors.is_empty() {
493 log::warn!("K8s manifest parse errors: {:?}", result.parse_errors);
494 }
495
496 if result.summary.objects_analyzed == 0 {
498 if !result.parse_errors.is_empty() {
499 return Ok(format_error_for_llm(
501 "kubelint",
502 ErrorCategory::ValidationFailed,
503 "Failed to parse Kubernetes manifests",
504 Some(vec![
505 &format!("Parse errors: {}", result.parse_errors.join("; ")),
506 "Check YAML syntax (proper indentation, valid structure)",
507 "Ensure files contain valid Kubernetes manifests with 'apiVersion' and 'kind'",
508 "Use helmlint for Helm chart template syntax issues",
509 ]),
510 ));
511 } else {
512 return Ok(format_error_for_llm(
514 "kubelint",
515 ErrorCategory::ValidationFailed,
516 &format!("No Kubernetes objects found in '{}'", source),
517 Some(vec![
518 "Directory may be empty or contain no .yaml/.yml files",
519 "Files may be valid YAML but not Kubernetes manifests",
520 "Kubernetes manifests require 'apiVersion' and 'kind' fields",
521 "Try specifying a different path or use 'content' for inline YAML",
522 ]),
523 ));
524 }
525 }
526
527 Ok(Self::format_result(&result, &source))
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use std::fs;
535 use tempfile::TempDir;
536
537 #[tokio::test]
538 async fn test_kubelint_inline_content() {
539 let temp_dir = TempDir::new().unwrap();
540 let tool = KubelintTool::new(temp_dir.path().to_path_buf());
541
542 let yaml = r#"
543apiVersion: apps/v1
544kind: Deployment
545metadata:
546 name: insecure-deploy
547spec:
548 replicas: 1
549 selector:
550 matchLabels:
551 app: test
552 template:
553 spec:
554 containers:
555 - name: nginx
556 image: nginx:latest
557 securityContext:
558 privileged: true
559"#;
560
561 let args = KubelintArgs {
562 path: None,
563 content: Some(yaml.to_string()),
564 include: vec!["privileged-container".to_string(), "latest-tag".to_string()],
565 exclude: vec![],
566 threshold: None,
567 };
568
569 let result = tool.call(args).await.unwrap();
570 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
571
572 assert!(parsed["summary"]["total_issues"].as_u64().unwrap_or(0) > 0);
574 assert!(parsed["decision_context"].is_string());
575 assert!(parsed["tool_guidance"].is_string());
576 }
577
578 #[tokio::test]
579 async fn test_kubelint_secure_deployment() {
580 let temp_dir = TempDir::new().unwrap();
581 let tool = KubelintTool::new(temp_dir.path().to_path_buf());
582
583 let yaml = r#"
584apiVersion: apps/v1
585kind: Deployment
586metadata:
587 name: secure-deploy
588spec:
589 replicas: 3
590 selector:
591 matchLabels:
592 app: test
593 template:
594 spec:
595 serviceAccountName: my-service-account
596 securityContext:
597 runAsNonRoot: true
598 containers:
599 - name: nginx
600 image: nginx:1.25.0
601 securityContext:
602 privileged: false
603 allowPrivilegeEscalation: false
604 readOnlyRootFilesystem: true
605 capabilities:
606 drop:
607 - ALL
608"#;
609
610 let args = KubelintArgs {
611 path: None,
612 content: Some(yaml.to_string()),
613 include: vec!["privileged-container".to_string(), "latest-tag".to_string()],
614 exclude: vec![],
615 threshold: None,
616 };
617
618 let result = tool.call(args).await.unwrap();
619 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
620
621 let critical = parsed["summary"]["by_priority"]["critical"]
623 .as_u64()
624 .unwrap_or(99);
625 let high = parsed["summary"]["by_priority"]["high"]
626 .as_u64()
627 .unwrap_or(99);
628 assert_eq!(critical, 0);
629 assert_eq!(high, 0);
630 }
631
632 #[tokio::test]
633 async fn test_kubelint_file() {
634 let temp_dir = TempDir::new().unwrap();
635 let manifest_path = temp_dir.path().join("deployment.yaml");
636
637 fs::write(
638 &manifest_path,
639 r#"apiVersion: apps/v1
640kind: Deployment
641metadata:
642 name: test
643spec:
644 replicas: 1
645 selector:
646 matchLabels:
647 app: test
648 template:
649 spec:
650 containers:
651 - name: nginx
652 image: nginx:1.25.0
653"#,
654 )
655 .unwrap();
656
657 let tool = KubelintTool::new(temp_dir.path().to_path_buf());
658 let args = KubelintArgs {
659 path: Some("deployment.yaml".to_string()),
660 content: None,
661 include: vec![],
662 exclude: vec![],
663 threshold: None,
664 };
665
666 let result = tool.call(args).await.unwrap();
667 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
668
669 assert!(
670 parsed["source"]
671 .as_str()
672 .unwrap()
673 .contains("deployment.yaml")
674 );
675 assert!(parsed["summary"]["objects_analyzed"].as_u64().unwrap_or(0) >= 1);
676 }
677
678 #[tokio::test]
679 async fn test_kubelint_output_format() {
680 let temp_dir = TempDir::new().unwrap();
681 let tool = KubelintTool::new(temp_dir.path().to_path_buf());
682
683 let yaml = r#"
684apiVersion: apps/v1
685kind: Deployment
686metadata:
687 name: insecure-deploy
688spec:
689 replicas: 1
690 selector:
691 matchLabels:
692 app: test
693 template:
694 spec:
695 containers:
696 - name: nginx
697 image: nginx:latest
698 securityContext:
699 privileged: true
700"#;
701
702 let args = KubelintArgs {
703 path: None,
704 content: Some(yaml.to_string()),
705 include: vec![], exclude: vec![],
707 threshold: None,
708 };
709
710 let result = tool.call(args).await.unwrap();
711 println!("\n=== KUBELINT OUTPUT ===\n{}\n", result);
712
713 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
714
715 assert!(
717 parsed["summary"]["total_issues"].as_u64().unwrap() > 0,
718 "Expected issues but got none. Output: {}",
719 result
720 );
721 assert!(
722 !parsed["action_plan"]["critical"]
723 .as_array()
724 .unwrap()
725 .is_empty()
726 || !parsed["action_plan"]["high"].as_array().unwrap().is_empty(),
727 "Expected critical or high priority issues"
728 );
729 }
730
731 #[tokio::test]
732 async fn test_kubelint_excludes() {
733 let temp_dir = TempDir::new().unwrap();
734 let tool = KubelintTool::new(temp_dir.path().to_path_buf());
735
736 let yaml = r#"
737apiVersion: apps/v1
738kind: Deployment
739metadata:
740 name: test
741spec:
742 replicas: 1
743 selector:
744 matchLabels:
745 app: test
746 template:
747 spec:
748 containers:
749 - name: nginx
750 image: nginx:latest
751 securityContext:
752 privileged: true
753"#;
754
755 let args = KubelintArgs {
756 path: None,
757 content: Some(yaml.to_string()),
758 include: vec![],
759 exclude: vec!["privileged-container".to_string(), "latest-tag".to_string()],
760 threshold: None,
761 };
762
763 let result = tool.call(args).await.unwrap();
764 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
765
766 let all_issues: Vec<_> = ["critical", "high", "medium", "low"]
768 .iter()
769 .flat_map(|p| {
770 parsed["action_plan"][p]
771 .as_array()
772 .cloned()
773 .unwrap_or_default()
774 })
775 .collect();
776
777 assert!(
778 !all_issues
779 .iter()
780 .any(|i| i["check"] == "privileged-container")
781 );
782 assert!(!all_issues.iter().any(|i| i["check"] == "latest-tag"));
783 }
784
785 #[test]
786 fn test_parse_threshold() {
787 assert_eq!(KubelintTool::parse_threshold("error"), Severity::Error);
788 assert_eq!(KubelintTool::parse_threshold("warning"), Severity::Warning);
789 assert_eq!(KubelintTool::parse_threshold("info"), Severity::Info);
790 assert_eq!(KubelintTool::parse_threshold("ERROR"), Severity::Error);
792 assert_eq!(KubelintTool::parse_threshold("Warning"), Severity::Warning);
793 assert_eq!(KubelintTool::parse_threshold("invalid"), Severity::Warning);
795 assert_eq!(KubelintTool::parse_threshold(""), Severity::Warning);
796 }
797
798 #[test]
799 fn test_get_check_category() {
800 assert_eq!(
802 KubelintTool::get_check_category("privileged-container"),
803 "security"
804 );
805 assert_eq!(
806 KubelintTool::get_check_category("run-as-non-root"),
807 "security"
808 );
809 assert_eq!(KubelintTool::get_check_category("hostnetwork"), "security");
810 assert_eq!(KubelintTool::get_check_category("hostpid"), "security");
811 assert_eq!(
812 KubelintTool::get_check_category("privilege-escalation"),
813 "security"
814 );
815 assert_eq!(
816 KubelintTool::get_check_category("read-only-root-fs"),
817 "security"
818 );
819
820 assert_eq!(
822 KubelintTool::get_check_category("latest-tag"),
823 "best-practice"
824 );
825 assert_eq!(
826 KubelintTool::get_check_category("no-liveness-probe"),
827 "best-practice"
828 );
829 assert_eq!(
830 KubelintTool::get_check_category("unset-cpu-requirements"),
831 "best-practice"
832 );
833
834 assert_eq!(
836 KubelintTool::get_check_category("access-to-secrets"),
837 "rbac"
838 );
839 assert_eq!(
840 KubelintTool::get_check_category("cluster-admin-role-binding"),
841 "rbac"
842 );
843 assert_eq!(
844 KubelintTool::get_check_category("wildcard-in-rules"),
845 "rbac"
846 );
847
848 assert_eq!(
850 KubelintTool::get_check_category("dangling-service"),
851 "validation"
852 );
853 assert_eq!(
854 KubelintTool::get_check_category("duplicate-env-var"),
855 "validation"
856 );
857
858 assert_eq!(KubelintTool::get_check_category("ssh-port"), "ports");
860 assert_eq!(
861 KubelintTool::get_check_category("privileged-ports"),
862 "ports"
863 );
864
865 assert_eq!(
867 KubelintTool::get_check_category("pdb-max-unavailable"),
868 "disruption-budget"
869 );
870
871 assert_eq!(
873 KubelintTool::get_check_category("hpa-minimum-replicas"),
874 "autoscaling"
875 );
876
877 assert_eq!(
879 KubelintTool::get_check_category("no-extensions-v1beta"),
880 "deprecated-api"
881 );
882
883 assert_eq!(KubelintTool::get_check_category("service-type"), "service");
885
886 assert_eq!(KubelintTool::get_check_category("unknown-check"), "other");
888 }
889
890 #[test]
891 fn test_get_priority() {
892 assert_eq!(
894 KubelintTool::get_priority(Severity::Error, "privileged-container"),
895 "critical"
896 );
897 assert_eq!(
898 KubelintTool::get_priority(Severity::Error, "access-to-secrets"),
899 "critical"
900 );
901
902 assert_eq!(
904 KubelintTool::get_priority(Severity::Error, "latest-tag"),
905 "high"
906 );
907 assert_eq!(
908 KubelintTool::get_priority(Severity::Error, "dangling-service"),
909 "high"
910 );
911
912 assert_eq!(
914 KubelintTool::get_priority(Severity::Warning, "run-as-non-root"),
915 "high"
916 );
917 assert_eq!(
918 KubelintTool::get_priority(Severity::Warning, "wildcard-in-rules"),
919 "high"
920 );
921
922 assert_eq!(
924 KubelintTool::get_priority(Severity::Warning, "duplicate-env-var"),
925 "medium"
926 );
927 assert_eq!(
928 KubelintTool::get_priority(Severity::Warning, "no-liveness-probe"),
929 "medium"
930 );
931 assert_eq!(
932 KubelintTool::get_priority(Severity::Warning, "ssh-port"),
933 "medium"
934 );
935
936 assert_eq!(
938 KubelintTool::get_priority(Severity::Info, "privileged-container"),
939 "low"
940 );
941 assert_eq!(
942 KubelintTool::get_priority(Severity::Info, "latest-tag"),
943 "low"
944 );
945 }
946}