
K8s Yaml Validator
- 421 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
k8s-yaml-validator is a Claude Code skill that validates Kubernetes YAML manifests for schema, syntax, and policy issues before kubectl apply or CI deploy pipelines run.
About
k8s-yaml-validator is a DevOps skill for teams shipping workloads to Kubernetes who need manifest review before changes hit clusters. It checks YAML for schema correctness, syntax errors, and policy violations that commonly cause failed rollouts or rejected admissions. Developers invoke it during pull-request review, pre-apply checks, or CI stages where Deployment, Service, Ingress, and ConfigMap files must pass validation gates. The skill complements kubectl dry-run workflows by surfacing misconfigurations early—wrong apiVersions, malformed selectors, missing required fields, or policy conflicts—so deploy pipelines fail in CI instead of production.
- Kubernetes manifest schema validation
- Pre-apply and CI gate checks
- Catches misconfigurations early
- Supports safer cluster rollouts
- DevOps-focused YAML review workflow
K8s Yaml Validator by the numbers
- 421 all-time installs (skills.sh)
- Ranked #285 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill k8s-yaml-validatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 421 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you validate Kubernetes YAML before deploy?
Validate Kubernetes YAML manifests for schema, syntax, and policy issues before apply or CI deploy to prevent misconfigurations and failed rollouts.
Who is it for?
Platform and backend engineers running Kubernetes deploy pipelines who want pre-apply manifest gates in CI.
Skip if: Teams managing non-Kubernetes infrastructure or needing runtime cluster monitoring instead of static YAML validation.
When should I use this skill?
The user asks to validate, lint, or review Kubernetes YAML manifests before apply or CI deploy.
What you get
Reviewed manifest reports listing schema, syntax, and policy issues fixed before cluster apply.
- Manifest validation report
- Pre-apply issue list
- CI-ready YAML review notes
Files
Kubernetes YAML Validator
Overview
This skill provides a comprehensive validation workflow for Kubernetes YAML resources, combining syntax linting, schema validation, cluster dry-run testing, and intelligent CRD documentation lookup. Validate any Kubernetes manifest with confidence before applying it to the cluster.
IMPORTANT: This is a REPORT-ONLY validation tool. Do NOT modify files, do NOT use Edit tool, do NOT use AskUserQuestion to offer fixes. Generate a comprehensive validation report with suggested fixes shown as before/after code blocks, then let the user decide what to do next.
Trigger Phrases
Use this skill when prompts look like:
- "Validate this Kubernetes YAML before deploy."
- "Lint these manifests and report what is broken."
- "Check this CRD manifest and explain schema issues."
- "Run dry-run checks on this manifest."
- "Find line-level errors in this multi-document YAML."
When to Use This Skill
Invoke this skill when:
- Validating Kubernetes YAML files before applying to a cluster
- Debugging YAML syntax or formatting errors
- Working with Custom Resource Definitions (CRDs) and need documentation
- Performing dry-run tests to catch admission controller errors
- Ensuring YAML follows Kubernetes best practices
- Understanding what validation errors exist in manifests (report-only, user fixes manually)
- The user asks to "validate", "lint", "check", or "test" Kubernetes YAML files
Read-Only Boundary (Mandatory)
This skill is strictly report-only:
- Do NOT modify any user files.
- Do NOT run Edit for fixes.
- Do NOT ask for permission to apply fixes.
- Do provide before/after snippets as suggestions in the report.
Deterministic Path Setup
Run with explicit paths so commands are repeatable:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
SKILL_DIR="$REPO_ROOT/devops-skills-plugin/skills/k8s-yaml-validator"
TARGET_FILE="$REPO_ROOT/<relative/path/to/file.yaml>"Path checks:
- If
REPO_ROOTis empty, stop and ask for repository root. - If
SKILL_DIRdoes not exist, stop and report path mismatch. - If
TARGET_FILEdoes not exist, stop and ask for the correct file.
Validation Workflow
Follow this sequential validation workflow. Each stage catches different types of issues:
Stage 0: Pre-Validation Setup (Deterministic Resource Count)
Before running validators, count documents using the bundled script:
python3 "$SKILL_DIR/scripts/count_yaml_documents.py" "$TARGET_FILE"Expected output (example):
{
"file": ".../manifests.yaml",
"documents": 3,
"separators": 2
}Gate rules:
- If
documents >= 3, loadreferences/validation_workflow.mdbefore Stage 1. - Always include the document count in the final report summary.
- If
python3is unavailable, use fallback:
awk 'BEGIN{d=0;seen=0} /^[[:space:]]*---[[:space:]]*$/ {if(seen){d++;seen=0}; next} /^[[:space:]]*#/ {next} NF{seen=1} END{if(seen)d++; print d}' "$TARGET_FILE"and mark the count as estimated in the report.
Stage 1: Tool Check
Before starting validation, verify required tools are installed:
bash "$SKILL_DIR/scripts/setup_tools.sh"Required tools:
- yamllint: YAML syntax and style linting
- kubeconform: Kubernetes schema validation with CRD support
- kubectl: Cluster dry-run testing (optional but recommended)
If tools are missing, display installation guidance from script output and continue with available tools. Document missing tools and skipped stages in the report.
Stage 2: YAML Syntax Validation
Validate YAML syntax and formatting using yamllint:
yamllint -c "$SKILL_DIR/assets/.yamllint" "$TARGET_FILE"Common issues caught:
- Indentation errors (tabs vs spaces)
- Trailing whitespace
- Line length violations
- Syntax errors
- Duplicate keys
Reporting approach:
- Report all syntax issues with file:line references
- For fixable issues, show suggested before/after code blocks
- Continue to next validation stage to collect all issues before reporting
Stage 3: CRD Detection and Documentation Lookup
Before schema validation, detect if the YAML contains Custom Resource Definitions:
bash "$SKILL_DIR/scripts/detect_crd_wrapper.sh" "$TARGET_FILE"The wrapper script automatically handles Python dependencies by creating a temporary virtual environment if PyYAML is not available.
Resilient Parsing: The script is resilient to syntax errors in individual documents. If a multi-document YAML file has some valid and some invalid documents, the script will:
- Parse valid documents and detect their CRDs
- Report errors for invalid documents but continue processing
- This matches kubeconform's behavior of validating 2/3 resources even when 1/3 has syntax errors
The script outputs JSON with resource information and parse status:
{
"resources": [
{
"kind": "Certificate",
"apiVersion": "cert-manager.io/v1",
"group": "cert-manager.io",
"version": "v1",
"isCRD": true,
"name": "example-cert"
}
],
"parseErrors": [
{
"document": 1,
"start_line": 2,
"error_line": 6,
"error": "mapping values are not allowed in this context"
}
],
"summary": {
"totalDocuments": 3,
"parsedSuccessfully": 2,
"parseErrors": 1,
"crdsDetected": 1
}
}For each detected CRD:
1. Try Context7 MCP first (preferred):
- Resolve library:
- Tool:
mcp__context7__resolve-library-id libraryName: CRD project name (example:cert-managerforcert-manager.io)- Query docs:
- Tool:
mcp__context7__query-docs libraryId: resolved library ID from previous stepquery: include CRD kind, group, and version (example:Certificate cert-manager.io v1 required fields in spec)
2. Fallback to `web.search_query` if Context7 fails or returns insufficient details:
Search query pattern:
"<kind>" "<group>" kubernetes CRD "<version>" documentation spec
Example:
"Certificate" "cert-manager.io" kubernetes CRD "v1" documentation spec3. Extract key information:
- Required fields in
spec - Field types and validation rules
- Examples from documentation
- Version-specific changes or deprecations
Secondary CRD Detection via kubeconform: If detect_crd_wrapper.sh cannot identify CRDs (for example, syntax errors in all documents), but kubeconform still validates a CRD resource, look up docs for that CRD anyway. Parse kubeconform output to identify validated CRDs and perform Context7/web.search_query lookups.
Why this matters: CRDs have custom schemas not available in standard Kubernetes validation tools. Understanding the CRD's spec requirements prevents validation errors and ensures correct resource configuration.
Stage 4: Schema Validation
Validate against Kubernetes schemas using kubeconform:
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-strict \
-ignore-missing-schemas \
-summary \
-verbose \
"$TARGET_FILE"Options explained:
-strict: Reject unknown fields (recommended for production - catches typos)-ignore-missing-schemas: Skip validation for CRDs without available schemas-kubernetes-version 1.30.0: Validate against specific K8s version
Common issues caught:
- Invalid apiVersion or kind
- Missing required fields
- Wrong field types
- Invalid enum values
- Unknown fields (with -strict)
For CRDs: If kubeconform reports "no schema found", this is expected. Use the documentation from Stage 3 to manually validate the spec fields.
kubeconform line number behavior — two distinct cases:
kubeconform does NOT report file-absolute line numbers. You must translate:
1. Parse errors (e.g. error converting YAML to JSON: yaml: line N):
Nis document-relative (line N within that document's content).- Convert to file-absolute:
file_line = doc_start_line + N - 1 doc_start_linecomes from thestart_linefield indetect_crd_wrapper.shoutput.- Example: document starts at file line 4, kubeconform says
yaml: line 5→
file-absolute line = 4 + 5 − 1 = line 8 (matches yamllint output).
2. Schema validation errors (e.g. got string, want integer):
- kubeconform reports JSON path only, no line number.
- Example:
at '/spec/template/spec/containers/0/ports/0/containerPort': got string, want integer - To find the line: search the YAML file for the field name (e.g.
containerPort) within
the relevant document section, using file-absolute line numbers from the surrounding context.
Always present line numbers as file-absolute in the validation report even when translating from kubeconform's document-relative output.
Stage 5: Cluster Dry-Run (if available)
IMPORTANT: Always try server-side dry-run first. Server-side validation catches more issues than client-side because it runs through admission controllers and webhooks.
Decision Tree:
1. Try server-side dry-run first:
kubectl apply --dry-run=server -f "$TARGET_FILE"
└─ If SUCCESS → Use results, continue to Stage 6
└─ If FAILS with connection error (e.g., "connection refused",
"unable to connect", "no configuration"):
│
├─ 2. Attempt client-side dry-run (parse-only fallback):
│ kubectl apply --dry-run=client --validate=false -f "$TARGET_FILE"
│
│ ├─ If SUCCESS:
│ │ Document in report: "Server-side validation skipped (no cluster access); client fallback ran in parse-only mode"
│ │
│ └─ If FAILS with discovery/openapi error (e.g., "unable to recognize",
│ "failed to download openapi", "couldn't get current server API group list"):
│ Document in report: "Dry-run skipped (cluster discovery unavailable)"
│ Continue to Stage 6
│
└─ If FAILS with validation error (e.g., "admission webhook denied",
"resource quota exceeded", "invalid value"):
└─ Record the error, continue to Stage 6
└─ If FAILS with parse error (e.g., "error converting YAML to JSON",
"yaml: line X: mapping values are not allowed"):
└─ Record the error, skip client-side dry-run (same error will occur)
Document in report: "Dry-run blocked by YAML syntax errors - fix syntax first"
Continue to Stage 6Note: Parse errors from earlier stages (yamllint, kubeconform) will also cause dry-run to fail. Do NOT attempt client-side dry-run as a fallback for parse errors - it will produce the same error. Parse errors must be fixed before dry-run validation can proceed.
Server-side dry-run catches:
- Admission controller rejections
- Policy violations (PSP, OPA, Kyverno, etc.)
- Resource quota violations
- Missing namespaces
- Invalid ConfigMap/Secret references
- Webhook validations
Client-side dry-run with `--validate=false` catches (fallback, when command succeeds):
- YAML/JSON conversion and request-construction issues
- Whether
kubectlcan process and submit the manifest shape in client mode - Note:
--validate=falsedisables schema/type/required-field validation and still does NOT catch admission controller or policy issues.
Document in your report which mode was used:
- If server-side: "Full cluster validation performed"
- If client-side with
--validate=false: "Limited parse-only validation (no cluster access) - schema and admission policies not checked" - If skipped: "Dry-run skipped - kubectl not available"
- If skipped after client fallback attempt: "Dry-run skipped (cluster discovery unavailable)"
For updates to existing resources:
kubectl diff -f "$TARGET_FILE"This shows what would change, helping catch unintended modifications.
Stage 6: Generate Detailed Validation Report (REPORT ONLY)
After completing all validation stages, generate a comprehensive report. This is a REPORT-ONLY stage.
NEVER do any of the following:
- Do NOT use the Edit tool to modify files
- Do NOT use AskUserQuestion to offer to fix issues
- Do NOT prompt the user asking if they want fixes applied
- Do NOT modify any YAML files
ALWAYS do the following:
- Generate a comprehensive validation report
- Show before/after code blocks as SUGGESTIONS only
- Let the user decide what to do after reviewing the report
- End with "Next Steps" for the user to take manually
1. Summarize all issues found across all stages in a table format:
| Severity | Stage | Location | Issue | Suggested Fix |
|----------|-------|----------|-------|---------------|
| Error | Syntax | file.yaml:5 | Indentation error | Use 2 spaces |
| Error | Schema | file.yaml:21 | Wrong type | Change to integer |
| Warning | Best Practice | file.yaml:30 | Missing labels | Add app label |2. Categorize by severity:
- Errors (must fix): Syntax errors, missing required fields, dry-run failures
- Warnings (should fix): Style issues, best practice violations
- Info (optional): Suggestions for improvement
3. Show before/after code blocks for each issue:
For every issue, display explicit before/after YAML snippets showing the suggested fix:
**Issue 1: deployment.yaml:21 - Wrong field type (Error)**
Current:- containerPort: "80"
Suggested Fix:- containerPort: 80
**Why:** containerPort must be an integer, not a string. Kubernetes will reject string values.
Reference: See k8s_best_practices.md "Invalid Values" section.4. Provide validation summary:
## Validation Report Summary
File: deployment.yaml
Resources Analyzed: 3 (Deployment, Service, Certificate)
| Stage | Status | Issues Found |
|-------|--------|--------------|
| YAML Syntax | ❌ Failed | 2 errors |
| CRD Detection | ✅ Passed | 1 CRD detected (Certificate) |
| Schema Validation | ❌ Failed | 1 error |
| Dry-Run | ❌ Failed | 1 error |
Total Issues: 4 errors, 2 warnings
## Detailed Findings
[List each issue with before/after code blocks as shown above]
## Next Steps
1. Fix the 4 errors listed above (deployment will fail without these)
2. Consider addressing the 2 warnings for best practices
3. Re-run validation after fixes to confirm resolution5. Do NOT modify files - this is a reporting tool only
- Present all findings clearly
- Let the user decide which fixes to apply
- User can request fixes after reviewing the report
Objective Stage Gates (Repeatable)
Use this table to keep stage decisions deterministic:
| Stage | Required | Command | Pass/Fail Criteria | Fallback |
|---|---|---|---|---|
| 0 Resource Count | Yes | python3 "$SKILL_DIR/scripts/count_yaml_documents.py" "$TARGET_FILE" | Pass when count output is produced and documents is recorded. | Use AWK estimator and mark estimated. |
| 1 Tool Check | Yes | bash "$SKILL_DIR/scripts/setup_tools.sh" | Pass when command runs and tool availability is known. | Continue with available tools and log skips. |
| 2 YAML Syntax | If yamllint available | yamllint -c "$SKILL_DIR/assets/.yamllint" "$TARGET_FILE" | Pass on exit code 0; fail on lint errors. | Skip with explicit reason if missing binary. |
| 3 CRD Detection | If python3 available | bash "$SKILL_DIR/scripts/detect_crd_wrapper.sh" "$TARGET_FILE" | Pass when JSON output includes summary. | Skip CRD extraction and rely on kubeconform clues. |
| 4 Schema | If kubeconform available | kubeconform command from Stage 4 | Pass when kubeconform reports valid resources. | Skip and record as coverage gap if missing binary. |
| 5 Dry-Run | If kubectl available | kubectl apply --dry-run=server -f "$TARGET_FILE" | Pass on successful server dry-run. | Attempt parse-only client fallback with --dry-run=client --validate=false; if discovery still fails, mark stage skipped. |
| 6 Report | Yes | Report generation | Pass when summary + per-issue snippets + next steps are provided. | No fallback; this stage is mandatory. |
Fallback Matrix
| Constraint | Action | Report Language |
|---|---|---|
python3 unavailable | Skip count_yaml_documents.py and CRD parser scripts. Use AWK count only. | Python runtime unavailable; CRD parser skipped, resource count is estimated. |
yamllint unavailable | Skip Stage 2; continue with schema/dry-run stages if available. | YAML lint skipped because yamllint is not installed. |
kubeconform unavailable | Skip Stage 4; run lint and dry-run only. | Schema validation skipped because kubeconform is not installed. |
kubectl unavailable | Skip Stage 5 entirely. | Dry-run skipped because kubectl is not installed. |
| No cluster connectivity | Run server-side first, then attempt parse-only client fallback with --dry-run=client --validate=false; if it still fails, skip dry-run and continue. | Server-side dry-run unavailable due cluster access; parse-only client-side dry-run attempted (schema checks disabled). |
| Client dry-run still requires discovery | Treat dry-run as unavailable and rely on lint + schema stages. | Dry-run skipped (cluster discovery unavailable); lint and schema results used. |
| External docs unavailable | Continue local validation and state documentation gap. | CRD documentation lookup deferred due tooling/network limitation. |
Best Practices Reference
For detailed Kubernetes YAML best practices, load the reference:
Read "$SKILL_DIR/references/k8s_best_practices.md"This reference includes:
- Metadata and label conventions
- Resource limits and requests
- Security context guidelines
- Probe configurations
- Common validation issues and fixes
When to load (ALWAYS load in these cases):
- Schema validation fails with type errors (e.g., string vs integer, invalid values)
- Schema validation reports missing required fields
- kubeconform reports invalid field values or unknown fields
- Dry-run fails with validation errors related to resources, probes, or security
- When explaining why a fix is needed (to provide context from best practices)
Detailed Validation Workflow Reference
For in-depth workflow details and error handling strategies, load the reference:
Read "$SKILL_DIR/references/validation_workflow.md"This reference includes:
- Detailed command options for each tool
- Error handling strategies
- Multi-resource file handling
- Complete workflow diagram
- Troubleshooting guide
When to load (ALWAYS load in these cases):
- File contains 3 or more resources (multi-document YAML)
- Validation produces errors you haven't seen before or can't immediately diagnose
- Need to understand the complete workflow for debugging
- Errors span multiple validation stages
Working with Multiple Resources
When a YAML file contains multiple resources (separated by ---):
1. Validate the entire file first with yamllint and kubeconform 2. If errors occur, identify which resource has issues by checking line numbers 3. For dry-run, the file is tested as a unit (Kubernetes processes in order) 4. Track issues per-resource when presenting findings to the user
Partial Parsing Behavior
When a multi-document YAML file has some valid and some invalid documents:
Expected behavior:
- The CRD detection script (
detect_crd.py) will parse valid documents and skip invalid ones - kubeconform will validate resources it can parse and report errors for unparseable ones
- The validation report should clearly show which documents parsed and which failed
Example scenario: A file with 3 documents where document 1 has a syntax error:
- Document 1 (Deployment): Syntax error at line 8
- Document 2 (Service): Valid
- Document 3 (Certificate CRD): Valid
Expected output:
- CRD detection: Finds Certificate CRD from document 3
- kubeconform: Reports error for document 1, validates documents 2 and 3
- Report: Shows syntax error for document 1, validation results for documents 2 and 3
In your report:
| Document | Resource | Parsing | Validation |
|----------|----------|---------|------------|
| 1 | Deployment | ❌ Syntax error (line 8) | Skipped |
| 2 | Service | ✅ Parsed | ✅ Valid |
| 3 | Certificate | ✅ Parsed | ✅ Valid |Line Number Reference Style:
- Always use file-absolute line numbers (line numbers relative to the start of the entire file)
- This matches what yamllint, kubeconform, and kubectl report
- Example: If a file has 3 documents and the error is in document 2 which starts at line 35, report as "line 42" (the absolute line in the file), not "line 7" (relative to document start)
- This consistency makes it easy for users to navigate directly to the error in their editor
This ensures users get maximum validation feedback even when some documents have issues.
Error Handling Strategies
Tool Not Available
- Run
bash "$SKILL_DIR/scripts/setup_tools.sh"to check availability - Provide installation instructions
- Skip optional stages but document what was skipped
- Continue with available tools
Cluster Access Issues
- Attempt parse-only client-side dry-run with
--dry-run=client --validate=false - Treat this fallback as transport/parsing signal only (
--validate=falsedisables schema/type/required-field checks) - If client dry-run still fails with API discovery/openapi errors, skip dry-run and rely on lint/schema stages
- Document limitations in validation report
CRD Documentation Not Found
- Document that documentation lookup failed
- Attempt validation with kubeconform CRD schemas
- Suggest manual CRD inspection:
kubectl get crd <crd-name>.group -o yaml
kubectl explain <kind>Validation Stage Failures
- Continue to next stage even if one fails
- Collect all errors before presenting to user
- Prioritize fixing earlier stage errors first
Communication Guidelines
When presenting validation results:
1. Be clear and concise about what was found 2. Explain why issues matter (e.g., "This will cause pod creation to fail") 3. Provide context from best practices when relevant 4. Group related issues (e.g., all missing label issues together) 5. Use file:line references for all issues 6. Show fix complexity - Include a complexity indicator in the issue header:
- [Simple]: Single-line fixes like indentation, typos, or value changes
- [Medium]: Multi-line changes or adding missing fields/sections
- [Complex]: Logic changes, restructuring, or changes affecting multiple resources
Example format in issue header:
**Issue 1: deployment.yaml:8 - Wrong indentation (Error) [Simple]**
**Issue 2: deployment.yaml:15-25 - Missing security context (Warning) [Medium]**
**Issue 3: deployment.yaml - Selector mismatch with Service (Error) [Complex]**7. Always provide a comprehensive report including:
- Summary table of all issues by stage
- Before/after code blocks for each issue
- Total count of errors and warnings
- Clear next steps for the user
8. NEVER offer to apply fixes - this is strictly a reporting tool
- Do not ask "Would you like me to fix this?"
- Do not use AskUserQuestion for fix confirmations
- Present the report and let the user take action
Performance Optimization
Parallel Tool Execution
For improved validation speed, some stages can be executed in parallel:
Can run in parallel (no dependencies):
yamllint(Stage 2) anddetect_crd_wrapper.sh(Stage 3) can run simultaneously- Both tools operate independently on the input file
- Results from both are needed before proceeding to schema validation
Example parallel execution:
# Run these in parallel (using & and wait, or parallel tool calls):
yamllint -c "$SKILL_DIR/assets/.yamllint" "$TARGET_FILE"
bash "$SKILL_DIR/scripts/detect_crd_wrapper.sh" "$TARGET_FILE"Must run sequentially:
- Stage 0 (Resource Count Check) → Before all other stages
- Stage 1 (Tool Check) → Before using any tools
- Stage 4 (Schema Validation) → After CRD detection (needs CRD info for context)
- Stage 5 (Dry-Run) → After schema validation
- Stage 6 (Report) → After all validation stages complete
When to parallelize:
- Files with more than 5 resources benefit most from parallel execution
- For small files (1-2 resources), sequential execution is fine
Version Awareness
Always consider Kubernetes version compatibility:
- Check for deprecated APIs (e.g.,
extensions/v1beta1→apps/v1) - For CRDs, ensure the apiVersion matches what's in the cluster
- Use
kubectl api-versionsto list available API versions in the cluster - Reference version-specific documentation when available
Test Coverage Guidance
The test/ directory contains example files to exercise all validation paths. Use these to verify skill behavior.
Test Files
| Test File | Purpose | Expected Behavior |
|---|---|---|
deployment-test.yaml | Valid standard K8s resource | All stages pass, no errors |
certificate-crd-test.yaml | Valid CRD resource | CRD detected, Context7 lookup performed, no errors |
comprehensive-test.yaml | Multi-resource with intentional YAML syntax error | Syntax error detected, partial parsing works, CRD found |
schema-errors-test.yaml | Valid YAML with intentional schema type errors | yamllint passes; kubeconform fails with 2 JSON-path errors (replicas, containerPort) |
Validation Paths to Test
1. Happy Path (All Valid)
- File:
deployment-test.yaml - Expected: All stages pass, report shows "0 errors, 0 warnings"
- Commands:
cd "$SKILL_DIR"
python3 scripts/count_yaml_documents.py test/deployment-test.yaml
yamllint -c assets/.yamllint test/deployment-test.yaml
bash scripts/detect_crd_wrapper.sh test/deployment-test.yaml
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-strict -ignore-missing-schemas -summary -verbose \
test/deployment-test.yaml
kubectl apply --dry-run=server -f test/deployment-test.yaml2. CRD Detection Path
- File:
certificate-crd-test.yaml - Expected: CRD detected,
mcp__context7__resolve-library-idandmcp__context7__query-docsused - Commands:
cd "$SKILL_DIR"
python3 scripts/count_yaml_documents.py test/certificate-crd-test.yaml
bash scripts/detect_crd_wrapper.sh test/certificate-crd-test.yaml
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-strict -ignore-missing-schemas -summary -verbose \
test/certificate-crd-test.yaml3. Syntax Error Path
- File:
comprehensive-test.yaml - Expected: yamllint catches error, kubeconform reports partial validation, dry-run blocked
- Commands:
cd "$SKILL_DIR"
python3 scripts/count_yaml_documents.py test/comprehensive-test.yaml
yamllint -c assets/.yamllint test/comprehensive-test.yaml
bash scripts/detect_crd_wrapper.sh test/comprehensive-test.yaml
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-strict -ignore-missing-schemas -summary -verbose \
test/comprehensive-test.yaml
kubectl apply --dry-run=server -f test/comprehensive-test.yaml4. Multi-Resource Partial Parsing
- File:
comprehensive-test.yaml(has 3 resources, 1 with syntax error) - Expected: 2/3 resources validated, parse error reported for document 1
- Commands:
cd "$SKILL_DIR"
python3 scripts/count_yaml_documents.py test/comprehensive-test.yaml
bash scripts/detect_crd_wrapper.sh test/comprehensive-test.yaml5. Schema Validation Error Path (type mismatches)
- File:
schema-errors-test.yaml - Expected: yamllint passes (valid YAML), kubeconform fails with 2 JSON-path schema errors
- Note: kubeconform reports JSON paths, not line numbers — locate fields manually in the YAML
- Commands:
cd "$SKILL_DIR"
python3 scripts/count_yaml_documents.py test/schema-errors-test.yaml
yamllint -c assets/.yamllint test/schema-errors-test.yaml
bash scripts/detect_crd_wrapper.sh test/schema-errors-test.yaml
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-strict -ignore-missing-schemas -summary -verbose \
test/schema-errors-test.yaml6. No Cluster Access Path
- Any valid file with no kubectl cluster configured
- Expected: Server-side dry-run fails; parse-only client-side fallback is attempted (no schema guarantees) and may still fail if API discovery is unavailable
- Commands:
cd "$SKILL_DIR"
KUBECONFIG=/tmp/nonexistent-kubeconfig kubectl apply --dry-run=server -f test/deployment-test.yaml
KUBECONFIG=/tmp/nonexistent-kubeconfig kubectl apply --dry-run=client --validate=false -f test/deployment-test.yaml7. Missing Tools Path
- Test by temporarily removing a tool from PATH
- Expected: setup_tools.sh reports missing tools and prints install instructions, validation continues with available tools
- Commands:
cd "$SKILL_DIR"
PATH="/usr/bin:/bin" bash scripts/setup_tools.shCreating New Test Files
When adding test files: 1. Name files descriptively: <scenario>-test.yaml 2. Document expected behavior in comments at top of file 3. Include intentional errors for error-path tests 4. Test both standard K8s resources and CRDs
Expected Report Structure
For any validation, the report should include:
- [ ] Summary table with issue counts by severity
- [ ] Stage-by-stage status table (passed/failed/skipped)
- [ ] Document parsing table (for multi-resource files)
- [ ] Before/after code blocks for each issue
- [ ] Fix complexity indicators ([Simple], [Medium], [Complex])
- [ ] File-absolute line numbers
- [ ] "Next Steps" section
Done Criteria
Validation is complete only when all conditions are true:
- Stage gates were evaluated in order and every skipped stage includes a reason.
- Resource count came from
count_yaml_documents.py(or documented AWK fallback). - CRD lookups used
mcp__context7__resolve-library-id+mcp__context7__query-docs, withweb.search_queryfallback only when needed. - Report-only boundary was preserved (no edits, no fix-application prompts).
- Output includes exact commands run, findings by severity, and manual next steps.
Resources
scripts/
detect_crd_wrapper.sh
- Wrapper script that handles Python dependency management
- Automatically creates temporary venv if PyYAML is not available
- Calls detect_crd.py to parse YAML files
- Usage:
bash "$SKILL_DIR/scripts/detect_crd_wrapper.sh" "$TARGET_FILE"
detect_crd.py
- Parses YAML files to identify Custom Resource Definitions
- Extracts kind, apiVersion, group, and version information
- Outputs JSON for programmatic processing
- Requires PyYAML (handled automatically by wrapper script)
- Can be called directly:
python3 "$SKILL_DIR/scripts/detect_crd.py" "$TARGET_FILE"
count_yaml_documents.py
- Deterministically counts non-empty YAML documents in a multi-doc file
- Returns JSON with document count and separators
- Use before Stage 1 to decide whether to load deep workflow reference
- Usage:
python3 "$SKILL_DIR/scripts/count_yaml_documents.py" "$TARGET_FILE"
setup_tools.sh
- Checks for required validation tools
- Provides installation instructions for missing tools
- Verifies versions of installed tools
- Usage:
bash "$SKILL_DIR/scripts/setup_tools.sh"
references/
k8s_best_practices.md
- Comprehensive guide to Kubernetes YAML best practices
- Covers metadata, labels, resource limits, security context
- Common validation issues and how to fix them
- Load when providing context for validation errors
validation_workflow.md
- Detailed validation workflow with all stages
- Command options and configurations
- Error handling strategies
- Complete workflow diagram
- Load for complex validation scenarios
assets/
.yamllint
- Pre-configured yamllint rules for Kubernetes YAML
- Follows Kubernetes conventions (2-space indentation, line length, etc.)
- Can be customized per project
- Usage:
yamllint -c "$SKILL_DIR/assets/.yamllint" "$TARGET_FILE"
# yamllint configuration for Kubernetes YAML files
# This config follows Kubernetes best practices
extends: default
rules:
# Allow longer lines for Kubernetes resources
line-length:
max: 120
level: warning
# Kubernetes uses 2-space indentation
# indent-sequences: consistent allows both K8s-style (- at same level) and
# standard style (- indented). Use 'whatever' for maximum flexibility.
indentation:
spaces: 2
indent-sequences: consistent
# Allow comments without space from content
comments:
min-spaces-from-content: 1
# Allow document start markers (helpful for multi-resource files)
document-start: disable
# Truthy values: Kubernetes uses YAML 1.1 parser which recognizes yes/no/on/off
# as boolean values. We allow these for compatibility, but recommend using
# explicit 'true'/'false' for clarity and YAML 1.2 compatibility.
# See: https://yaml.org/type/bool.html
truthy:
allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off']
level: warning
# Kubernetes YAMLs often have long lines in annotations/labels
comments-indentation: {}
# Allow empty values (common in K8s for optional fields)
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: false
# Detect duplicate keys in mappings (uses default: error level)
key-duplicates: {}
# Enforce no trailing whitespace (uses default: error level)
trailing-spaces: {}
# Be strict about new lines at end of file
new-line-at-end-of-file: enable
# K8s resources often have complex nested structures
braces:
max-spaces-inside: 1
brackets:
max-spaces-inside: 1Kubernetes YAML Best Practices
General YAML Best Practices
Formatting and Style
- Use 2 spaces for indentation (not tabs)
- Keep lines under 80 characters when possible
- Use lowercase for keys
- Quote string values containing special characters
- Always specify apiVersion and kind
- Include metadata.name for all resources
Resource Organization
- One resource per file for clarity (unless logically grouped)
- Use
---to separate multiple resources in a single file - Name files descriptively:
<resource-type>-<name>.yaml
Kubernetes-Specific Best Practices
Metadata
metadata:
name: my-app
namespace: production
labels:
app: my-app
version: v1.0.0
component: backend
managed-by: kubectl
annotations:
description: "Backend service for my-app"Labels and Selectors
- Always include
applabel - Use consistent label keys across resources
- Include version labels for rollout tracking
- Selectors must match pod labels exactly
Resource Limits and Requests
Always specify both requests and limits:
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"Probes
Always define liveness and readiness probes:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Security
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALLImage Management
image: registry.example.com/my-app:v1.2.3 # Always use specific tags
imagePullPolicy: IfNotPresent # Or Always for :latestCommon Validation Issues
Missing Required Fields
apiVersionandkindare always requiredmetadata.nameis required for all resourcesspec.selectormust be specified for Deployments/Servicesspec.template.spec.containersmust have at least one container
Selector Mismatches
Deployment selector must match pod template labels:
# Deployment
spec:
selector:
matchLabels:
app: my-app # Must match pod labels below
template:
metadata:
labels:
app: my-app # Must match selector aboveInvalid Values
- CPU: Use millicore notation (e.g., "500m") or fractional (e.g., "0.5")
- Memory: Use Mi, Gi notation (e.g., "512Mi")
- Port numbers: Must be 1-65535
- DNS names: Must be lowercase alphanumeric with hyphens
Namespace Issues
- Not all resources are namespaced (e.g., ClusterRole, PersistentVolume)
- Services must be in the same namespace as the pods they target
- Default namespace is "default" if not specified
CRD-Specific Considerations
API Version Compatibility
- Check the CRD version installed in the cluster
- Use the correct apiVersion for the CRD
- Be aware of deprecations (e.g., v1alpha1 → v1beta1 → v1)
Required Fields
- CRDs often have custom required fields in spec
- Check the CRD documentation for field requirements
- Use kubectl explain <kind> to see field documentation
Validation
- CRDs may have custom validation rules
- OpenAPI schema validation is stricter in newer K8s versions
- Use dry-run to catch validation errors before applying
Deprecation Warnings
Common Deprecated APIs
extensions/v1beta1→apps/v1(Deployments, DaemonSets)networking.k8s.io/v1beta1→networking.k8s.io/v1(Ingress)policy/v1beta1→policy/v1(PodDisruptionBudget)
Always use the latest stable API version.
Kubernetes YAML Validation Workflow
This document outlines the comprehensive validation workflow for Kubernetes YAML resources.
Validation Stages
Stage 0: Resource Count (Deterministic)
Purpose: Count non-empty YAML documents before running validators.
Command:
python3 scripts/count_yaml_documents.py <file.yaml>Fallback when Python is unavailable (estimated count):
awk 'BEGIN{d=0;seen=0} /^[[:space:]]*---[[:space:]]*$/ {if(seen){d++;seen=0}; next} /^[[:space:]]*#/ {next} NF{seen=1} END{if(seen)d++; print d}' <file.yaml>Stage 1: Tool Check
Purpose: Determine which validation stages are runnable in the current environment.
Command:
bash scripts/setup_tools.shIf required tools are missing, continue with available tools and report skipped stages.
Stage 2: YAML Syntax Validation (yamllint)
Purpose: Catch YAML syntax errors and style issues before Kubernetes-specific validation.
Command:
yamllint -c assets/.yamllint <file.yaml>Common Issues Detected:
- Indentation errors (tabs vs spaces)
- Line length violations
- Trailing spaces
- Missing document start markers
- Duplicate keys
- Syntax errors
Stage 3 (CRD detection and docs lookup) is covered in the dedicated section below.
Stage 4: Kubernetes Schema Validation (kubeconform)
Purpose: Validate against Kubernetes schemas and detect structural issues.
Basic Command:
kubeconform -summary <file.yaml>With CRD Support (recommended):
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-strict \
-ignore-missing-schemas \
-summary \
-verbose \
<file.yaml>Options:
-strict: Reject resources with unknown fields (catches typos - recommended for production)-ignore-missing-schemas: Skip validation for CRDs without available schemas-kubernetes-version <version>: Validate against specific K8s version (e.g., 1.30.0)-output json: Output results as JSON
Common Issues Detected:
- Invalid apiVersion
- Missing required fields
- Invalid field types
- Unknown fields (in strict mode)
- Invalid enum values
Stage 5: Cluster Dry-Run (kubectl)
Purpose: Validate against the actual cluster configuration, admission controllers, and policies.
Client-Side Dry Run:
kubectl apply --dry-run=client --validate=false -f <file.yaml>- Best-effort fallback when server-side dry-run is unavailable
- May still fail if API discovery is unavailable (for example, no reachable cluster)
- Does not catch admission controller or policy issues
Server-Side Dry Run:
kubectl apply --dry-run=server -f <file.yaml>- Full validation including admission controllers
- Validates against cluster-specific constraints
- Requires cluster access
- Catches issues like:
- Resource quota violations
- Policy violations (PSP, OPA, Kyverno)
- Admission webhook rejections
- Namespace existence
- ConfigMap/Secret references
Diff Mode (for updates):
kubectl diff -f <file.yaml>Shows what would change if applied to the cluster.
CRD Detection and Documentation Lookup (Stage 3)
Step 1: Detect CRDs
Use the wrapper script (handles missing PyYAML automatically):
bash scripts/detect_crd_wrapper.sh <file.yaml>Output example:
{
"resources": [
{
"kind": "Certificate",
"apiVersion": "cert-manager.io/v1",
"group": "cert-manager.io",
"version": "v1",
"isCRD": true,
"name": "example-cert"
}
],
"parseErrors": [],
"summary": {
"totalDocuments": 1,
"parsedSuccessfully": 1,
"parseErrors": 0,
"crdsDetected": 1
}
}Step 2: Lookup CRD Documentation
For each detected CRD:
1. Use context7 MCP (preferred):
- Resolve library ID:
mcp__context7__resolve-library-idwith the CRD group/project name - Fetch documentation:
mcp__context7__query-docswith the library ID - Focus on the specific version if available
2. Fallback to Web Search:
- Search query:
"<kind>" "<group>" kubernetes CRD "<version>" documentation - Example:
"Certificate" "cert-manager.io" kubernetes CRD "v1" documentation - Look for official documentation sites
- Check for API references and examples
Step 3: Validate Against CRD Schema
Once documentation is found:
- Check required fields in spec
- Verify field types and formats
- Validate enum values
- Check for version-specific changes
Complete Validation Workflow
┌─────────────────────────────────────────────────────────────┐
│ 0. Count Documents │
│ Run: python3 scripts/count_yaml_documents.py <file.yaml> │
│ Record: documents + separators │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 1. Check Tools │
│ Run: bash scripts/setup_tools.sh │
│ Continue with available tools │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. YAML Syntax Check │
│ Run: yamllint -c assets/.yamllint <file.yaml> │
│ Fix: Indentation, trailing spaces, syntax errors │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Detect CRDs │
│ Run: bash scripts/detect_crd_wrapper.sh <file.yaml> │
│ Parse: Extract kind, apiVersion, group │
└─────────────────────────────────────────────────────────────┘
↓
┌──────┴──────┐
│ │
[CRD?] [Standard Resource]
│ │
↓ ↓
┌──────────────────────┐ │
│ 4a. Lookup CRD Docs │ │
│ - context7 MCP │ │
│ - Web search │ │
│ - Version-aware │ │
└──────────────────────┘ │
│ │
└──────┬──────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. Schema Validation │
│ Run: kubeconform -summary <file.yaml> │
│ Fix: Required fields, types, unknown fields │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. Dry-Run (if cluster available) │
│ Run: kubectl apply --dry-run=server -f <file.yaml> │
│ Fix: Admission issues, quotas, policies │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 6. Generate Validation Report │
│ - Summarize all issues in table format │
│ - Show before/after code blocks for each issue │
│ - Do NOT modify files - report only │
└─────────────────────────────────────────────────────────────┘Error Handling
Tool Not Found
- Run
scripts/setup_tools.shto check tool availability - Provide installation instructions
- Skip optional validation stages if tools missing
Cluster Not Available
- Skip server-side dry-run
- Attempt client-side dry-run with
--dry-run=client --validate=false - If client-side still fails due discovery/openapi errors, skip dry-run and rely on kubeconform
- Warn user that dry-run coverage is limited or unavailable
CRD Documentation Not Found
- Document that CRD docs couldn't be found
- Attempt validation with kubeconform CRD schemas
- Suggest checking cluster for CRD definition:
kubectl get crd <crd-name> -o yamlMultiple Resources in One File
- Validate each resource separately
- Track which resource has issues
- Provide line numbers for error locations
Best Practices for Validation
1. Always validate in order: count → tool check → syntax → CRD detection → schema → dry-run 2. Collect all issues: Don't stop at first error - gather everything before reporting 3. For CRDs: Always look up documentation first 4. Version awareness: Check K8s version compatibility 5. Test with cluster: Server-side dry-run is the most reliable 6. Show before/after: Display code blocks showing suggested fixes 7. Provide context: Explain what each issue means and why the fix is needed 8. Report only: Do NOT modify files - let user decide which fixes to apply 9. Load best practices reference: When schema errors occur, load k8s_best_practices.md for context
Creating Validation Reports
Generate a comprehensive validation report with all findings. Do NOT modify files.
Report Components
1. Header with issue count
## Validation Report - 7 issues found (4 errors, 3 warnings)2. Issues Summary Table
| Severity | Stage | Location | Issue | Suggested Fix |
|----------|-------|----------|-------|---------------|
| Error | Syntax | file.yaml:8 | Wrong indentation | Use 2 spaces |
| Error | Schema | file.yaml:21 | Wrong type | Change to integer |
| Warning | Best Practice | file.yaml:30 | Missing labels | Add app label |3. Detailed Findings (for each issue)
- File:line reference
- Current code block
- Suggested fix code block
- Explanation of why it matters
4. Validation status by stage
- Show which stages passed/failed
- Note if any stages were skipped (e.g., no cluster access)
5. Next Steps
- List errors that must be fixed before deployment
- List warnings for best practices consideration
- Suggest re-running validation after fixes
Example Report Format
## Validation Report - 7 issues found
File: deployment.yaml
Resources Analyzed: 3 (Deployment, Service, Certificate)
| Stage | Status | Issues |
|-------|--------|--------|
| YAML Syntax | ❌ Failed | 2 errors |
| CRD Detection | ✅ Passed | 1 CRD found |
| Schema Validation | ❌ Failed | 2 errors |
| Dry-Run | ❌ Failed | 1 error |
### Issue 1: deployment.yaml:8 - Wrong indentation (Error)
Current:labels:
Suggested Fix:labels:
**Why:** Kubernetes YAML requires 2-space indentation.
### Issue 2: deployment.yaml:21 - Wrong field type (Error)
Current:- containerPort: "80"
Suggested Fix:- containerPort: 80
**Why:** containerPort must be an integer, not a string.
[... more issues ...]
## Next Steps
1. Fix the 4 errors listed above (deployment will fail without these)
2. Consider addressing the 3 warnings for best practices
3. Re-run validation to confirm all issues resolvedReport Best Practices
- Be specific: List every issue with exact location
- Show both current and suggested: Always show before/after code blocks
- Explain impact: Help user understand why each issue matters
- Group by file: When multiple files are involved
- Prioritize by severity: Errors first, then warnings, then info
- Provide file references: Always include file:line for traceability
- Clear next steps: Tell user exactly what to do
#!/usr/bin/env python3
"""
Count non-empty YAML documents in a file using document separators.
This script is intentionally parser-independent so it still works when the YAML
contains syntax errors.
"""
import json
import sys
from pathlib import Path
import re
DOC_START_RE = re.compile(r"^---[ \t]*$")
DOC_END_RE = re.compile(r"^\.\.\.[ \t]*$")
def count_yaml_documents(content: str) -> tuple[int, int]:
"""Return (documents, separators)."""
documents = 0
separators = 0
seen_yaml_content = False
for line in content.splitlines():
if DOC_START_RE.match(line):
separators += 1
if seen_yaml_content:
documents += 1
seen_yaml_content = False
continue
if DOC_END_RE.match(line):
if seen_yaml_content:
documents += 1
seen_yaml_content = False
continue
stripped = line.strip()
if not stripped:
continue
# Ignore comment-only lines so header comments before the first '---'
# are not counted as a document.
if line.lstrip().startswith("#"):
continue
seen_yaml_content = True
if seen_yaml_content:
documents += 1
return documents, separators
def main() -> int:
if len(sys.argv) != 2:
print("Usage: count_yaml_documents.py <yaml-file>", file=sys.stderr)
return 1
file_path = Path(sys.argv[1])
if not file_path.exists():
print(f"File not found: {file_path}", file=sys.stderr)
return 1
if not file_path.is_file():
print(f"Not a regular file: {file_path}", file=sys.stderr)
return 1
try:
content = file_path.read_text(encoding="utf-8")
except OSError as exc:
print(f"Failed to read file: {exc}", file=sys.stderr)
return 1
documents, separators = count_yaml_documents(content)
output = {
"file": str(file_path.resolve()),
"documents": documents,
"separators": separators,
}
print(json.dumps(output, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())
#!/bin/bash
# Wrapper script for detect_crd.py that handles PyYAML dependency
# Creates a temporary venv if PyYAML is not available
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYTHON_SCRIPT="$SCRIPT_DIR/detect_crd.py"
# Check if we have arguments
if [ $# -lt 1 ]; then
echo "Usage: detect_crd_wrapper.sh <yaml-file>" >&2
exit 1
fi
YAML_FILE="$1"
# Try to run with system Python first
if python3 -c "import yaml" 2>/dev/null; then
# PyYAML is available, run directly
python3 "$PYTHON_SCRIPT" "$YAML_FILE"
exit $?
fi
# PyYAML not available, create temporary venv
TEMP_VENV=$(mktemp -d -t k8s-yaml-validator.XXXXXX)
trap "rm -rf $TEMP_VENV" EXIT
echo "PyYAML not found in system Python. Creating temporary environment..." >&2
# Create venv and install PyYAML
python3 -m venv "$TEMP_VENV" >&2
source "$TEMP_VENV/bin/activate" >&2
pip install --quiet pyyaml >&2
# Run the script
python3 "$PYTHON_SCRIPT" "$YAML_FILE"
# Cleanup happens automatically via trap
#!/usr/bin/env python3
"""
Detect Custom Resource Definitions (CRDs) in Kubernetes YAML files.
Extracts kind, apiVersion, and group information for CRD documentation lookup.
This script is resilient to syntax errors in individual documents within
multi-document YAML files. It will parse valid documents and report errors
for invalid ones, allowing CRD detection to proceed for parseable resources.
"""
import json
import re
import sys
from pathlib import Path
try:
import yaml
except ImportError:
print("Error: PyYAML is not installed. Please run: pip install pyyaml", file=sys.stderr)
print("Or use the wrapper script: bash scripts/detect_crd_wrapper.sh", file=sys.stderr)
sys.exit(1)
def _has_yaml_content(content: str) -> bool:
"""Return True only if content has at least one non-empty, non-comment line."""
for line in content.split('\n'):
stripped = line.strip()
if stripped and not stripped.startswith('#'):
return True
return False
def split_yaml_documents(content):
"""
Split YAML content into individual documents.
Handles document separators (---) properly.
"""
# Split on document separator, keeping track of line numbers
documents = []
current_doc = []
current_start_line = 1
line_num = 0
for line in content.split('\n'):
line_num += 1
if line.strip() == '---':
if current_doc:
doc_content = '\n'.join(current_doc)
if doc_content.strip() and _has_yaml_content(doc_content):
documents.append({
'content': doc_content,
'start_line': current_start_line
})
current_doc = []
current_start_line = line_num + 1
else:
current_doc.append(line)
# Don't forget the last document
if current_doc:
doc_content = '\n'.join(current_doc)
if doc_content.strip() and _has_yaml_content(doc_content):
documents.append({
'content': doc_content,
'start_line': current_start_line
})
return documents
def parse_yaml_file(file_path):
"""
Parse a YAML file that may contain multiple documents.
This function is resilient to syntax errors in individual documents.
It parses each document separately and continues even if some fail,
matching the behavior of kubeconform which can validate 2/3 resources
even when 1/3 has syntax errors.
Returns:
tuple: (list of parsed documents, list of parse errors)
"""
try:
with open(file_path, 'r') as f:
content = f.read()
except Exception as e:
print(f"Error reading file: {e}", file=sys.stderr)
return [], [{'error': str(e), 'document': 0}]
# First, try parsing the entire file at once (fast path).
# Filter out None documents produced by bare '---' separators so that
# totalDocuments is consistent with count_yaml_documents.py.
try:
documents = [d for d in yaml.safe_load_all(content) if d is not None]
return documents, []
except yaml.YAMLError:
# If full parsing fails, try document-by-document parsing
pass
# Split into individual documents and parse each separately
doc_parts = split_yaml_documents(content)
documents = []
errors = []
for i, doc_info in enumerate(doc_parts, 1):
try:
parsed = yaml.safe_load(doc_info['content'])
if parsed is not None:
documents.append(parsed)
except yaml.YAMLError as e:
error_msg = str(e)
# Extract line number from error if available
line_match = re.search(r'line (\d+)', error_msg)
error_line = doc_info['start_line']
if line_match:
error_line = doc_info['start_line'] + int(line_match.group(1)) - 1
errors.append({
'document': i,
'start_line': doc_info['start_line'],
'error_line': error_line,
'error': error_msg
})
print(f"Warning: Document {i} (starting at line {doc_info['start_line']}) has syntax errors: {error_msg}", file=sys.stderr)
if errors:
print(f"Parsed {len(documents)} of {len(doc_parts)} documents successfully. {len(errors)} document(s) had errors.", file=sys.stderr)
return documents, errors
def is_standard_k8s_resource(api_version, kind):
"""Check if a resource is a standard Kubernetes resource."""
standard_groups = {
# Core API group
'v1': True,
# Apps group
'apps/v1': True,
# Batch group
'batch/v1': True,
'batch/v1beta1': True,
# Networking group
'networking.k8s.io/v1': True,
'networking.k8s.io/v1beta1': True,
# Policy group
'policy/v1': True,
'policy/v1beta1': True,
# RBAC group
'rbac.authorization.k8s.io/v1': True,
'rbac.authorization.k8s.io/v1beta1': True,
# Storage group
'storage.k8s.io/v1': True,
'storage.k8s.io/v1beta1': True,
# Autoscaling group
'autoscaling/v1': True,
'autoscaling/v2': True,
'autoscaling/v2beta1': True,
'autoscaling/v2beta2': True,
# API Extensions group (for CRD definitions themselves)
'apiextensions.k8s.io/v1': True,
'apiextensions.k8s.io/v1beta1': True,
# Certificates group
'certificates.k8s.io/v1': True,
'certificates.k8s.io/v1beta1': True,
# Admission Registration group
'admissionregistration.k8s.io/v1': True,
'admissionregistration.k8s.io/v1beta1': True,
# Coordination group (Leases)
'coordination.k8s.io/v1': True,
# Discovery group (EndpointSlices)
'discovery.k8s.io/v1': True,
'discovery.k8s.io/v1beta1': True,
# Events group
'events.k8s.io/v1': True,
'events.k8s.io/v1beta1': True,
# Flow Control group (v1 is GA since K8s 1.29, v1beta3 deprecated in 1.32)
'flowcontrol.apiserver.k8s.io/v1': True,
'flowcontrol.apiserver.k8s.io/v1beta1': True,
'flowcontrol.apiserver.k8s.io/v1beta2': True,
'flowcontrol.apiserver.k8s.io/v1beta3': True,
# Storage Migration group (K8s 1.30+)
'storagemigration.k8s.io/v1alpha1': True,
# Node group (RuntimeClass)
'node.k8s.io/v1': True,
'node.k8s.io/v1beta1': True,
# Scheduling group (PriorityClass)
'scheduling.k8s.io/v1': True,
'scheduling.k8s.io/v1beta1': True,
# Snapshot Storage group (VolumeSnapshots)
'snapshot.storage.k8s.io/v1': True,
'snapshot.storage.k8s.io/v1beta1': True,
# Networking alpha (AdminNetworkPolicy - K8s 1.30+)
'networking.k8s.io/v1alpha1': True,
# Certificates alpha (ClusterTrustBundle - K8s 1.30+)
'certificates.k8s.io/v1alpha1': True,
# Resource group (ResourceClaims - K8s 1.26+)
'resource.k8s.io/v1alpha2': True,
'resource.k8s.io/v1alpha3': True,
# Internal API Server group
'internal.apiserver.k8s.io/v1alpha1': True,
# API Registration group
'apiregistration.k8s.io/v1': True,
'apiregistration.k8s.io/v1beta1': True,
# Authentication group
'authentication.k8s.io/v1': True,
'authentication.k8s.io/v1beta1': True,
# Authorization group
'authorization.k8s.io/v1': True,
'authorization.k8s.io/v1beta1': True,
}
# Check if it's a standard group
return api_version in standard_groups
def extract_resource_info(doc):
"""Extract resource information from a Kubernetes resource document."""
if not doc or not isinstance(doc, dict):
return None
kind = doc.get('kind')
api_version = doc.get('apiVersion')
if not kind or not api_version:
return None
# Extract group from apiVersion (e.g., "cert-manager.io/v1" -> "cert-manager.io")
group = api_version.split('/')[0] if '/' in api_version else 'core'
version = api_version.split('/')[-1]
is_crd = not is_standard_k8s_resource(api_version, kind)
return {
'kind': kind,
'apiVersion': api_version,
'group': group,
'version': version,
'isCRD': is_crd,
'name': doc.get('metadata', {}).get('name', 'unnamed')
}
def main():
if len(sys.argv) < 2:
print("Usage: detect_crd.py <yaml-file>", file=sys.stderr)
sys.exit(1)
file_path = sys.argv[1]
if not Path(file_path).exists():
print(f"File not found: {file_path}", file=sys.stderr)
sys.exit(1)
documents, parse_errors = parse_yaml_file(file_path)
resources = []
for doc in documents:
resource_info = extract_resource_info(doc)
if resource_info:
resources.append(resource_info)
# Build output with both resources and any parse errors
output = {
'resources': resources,
'parseErrors': parse_errors,
'summary': {
'totalDocuments': len(documents) + len(parse_errors),
'parsedSuccessfully': len(documents),
'parseErrors': len(parse_errors),
'crdsDetected': sum(1 for r in resources if r.get('isCRD', False))
}
}
# Output as JSON for easy parsing
print(json.dumps(output, indent=2))
if __name__ == '__main__':
main()
#!/bin/bash
# Check for required validation tools and provide installation instructions
set -e
echo "Checking for Kubernetes YAML validation tools..."
echo
MISSING_TOOLS=()
# Check for yamllint
if ! command -v yamllint &> /dev/null; then
echo "❌ yamllint not found"
MISSING_TOOLS+=("yamllint")
else
echo "✅ yamllint found: $(yamllint --version)"
fi
# Check for kubeconform
if ! command -v kubeconform &> /dev/null; then
echo "❌ kubeconform not found"
MISSING_TOOLS+=("kubeconform")
else
echo "✅ kubeconform found: $(kubeconform -v)"
fi
# Check for kubectl
if ! command -v kubectl &> /dev/null; then
echo "❌ kubectl not found"
MISSING_TOOLS+=("kubectl")
else
echo "✅ kubectl found: $(kubectl version --client 2>/dev/null | head -1)"
fi
# Check for yq (optional but helpful)
if ! command -v yq &> /dev/null; then
echo "⚠️ yq not found (optional, but helpful for YAML manipulation)"
else
echo "✅ yq found: $(yq --version)"
fi
echo
if [ ${#MISSING_TOOLS[@]} -eq 0 ]; then
echo "✅ All required tools are installed!"
exit 0
else
echo "❌ Missing tools: ${MISSING_TOOLS[*]}"
echo
echo "Installation instructions:"
echo
for tool in "${MISSING_TOOLS[@]}"; do
case $tool in
yamllint)
echo "📦 yamllint:"
echo " macOS: brew install yamllint"
echo " Linux: pip install yamllint"
echo " Ubuntu: apt-get install yamllint"
echo
;;
kubeconform)
echo "📦 kubeconform:"
echo " macOS: brew install kubeconform"
echo " Linux: Download from https://github.com/yannh/kubeconform/releases"
echo " Or use: go install github.com/yannh/kubeconform/cmd/kubeconform@latest"
echo
;;
kubectl)
echo "📦 kubectl:"
echo " macOS: brew install kubectl"
echo " Linux: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/"
echo " Or use: curl -LO https://dl.k8s.io/release/\$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/\$(uname -s | tr '[:upper:]' '[:lower:]')/\$(uname -m)/kubectl"
echo
;;
esac
done
exit 1
fi
#!/usr/bin/env python3
"""
Regression tests for count_yaml_documents.py and related skill guidance.
Run from repository root:
python3 devops-skills-plugin/skills/k8s-yaml-validator/scripts/test_count_yaml_documents.py
"""
import json
import subprocess
import sys
import unittest
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
sys.path.insert(0, str(SCRIPT_DIR))
from count_yaml_documents import count_yaml_documents # noqa: E402
class TestCountYamlDocuments(unittest.TestCase):
def test_single_document_without_separators(self):
content = """\
apiVersion: v1
kind: ConfigMap
metadata:
name: single
"""
self.assertEqual(count_yaml_documents(content), (1, 0))
def test_multi_document_with_top_level_separator(self):
content = """\
apiVersion: v1
kind: ConfigMap
metadata:
name: one
---
apiVersion: v1
kind: Service
metadata:
name: two
"""
self.assertEqual(count_yaml_documents(content), (2, 1))
def test_indented_separator_in_literal_block_is_not_a_document_separator(self):
content = """\
apiVersion: v1
kind: ConfigMap
metadata:
name: script
data:
script: |
echo start
---
echo end
---
apiVersion: v1
kind: Service
metadata:
name: service
"""
self.assertEqual(count_yaml_documents(content), (2, 1))
def test_comment_only_document_and_explicit_end_marker(self):
content = """\
---
# Comment-only document should not count
---
apiVersion: v1
kind: ConfigMap
metadata:
name: with-end-marker
...
"""
self.assertEqual(count_yaml_documents(content), (1, 2))
def test_mixed_valid_and_invalid_documents_count_deterministically(self):
content = """\
apiVersion: v1
kind: ConfigMap
metadata:
name: valid-a
---
apiVersion: v1
kind Deployment
metadata:
name: invalid-b
---
apiVersion: v1
kind: Service
metadata:
name: valid-c
"""
self.assertEqual(count_yaml_documents(content), (3, 2))
def test_edge_case_fixture_counts_match_expected(self):
fixture = SKILL_DIR / "test" / "document-counter-edge-cases.yaml"
self.assertTrue(fixture.exists(), "Expected edge-case fixture to exist")
command = [sys.executable, str(SCRIPT_DIR / "count_yaml_documents.py"), str(fixture)]
result = subprocess.run(command, capture_output=True, text=True, check=True)
payload = json.loads(result.stdout)
self.assertEqual(payload["documents"], 2)
self.assertEqual(payload["separators"], 3)
class TestDryRunGuidanceRegression(unittest.TestCase):
def test_validate_false_guidance_is_explicitly_parse_only(self):
skill_doc_path = SKILL_DIR / "SKILL.md"
skill_doc = skill_doc_path.read_text(encoding="utf-8")
self.assertIn(
"`--validate=false` disables schema/type/required-field validation",
skill_doc,
)
self.assertIn(
"Limited parse-only validation (no cluster access) - schema and admission policies not checked",
skill_doc,
)
self.assertNotIn("- Basic schema validation", skill_doc)
if __name__ == "__main__":
unittest.main()
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: default
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
# Test file for k8s-yaml-validator skill
# Contains intentional errors to test all validation stages
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web-app
version: v1.0.0
spec:
replicas: "3"
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: nginx:latest
ports:
- containerPort: "80"
resources:
requests:
memory: 64Mi
cpu: 250m
limits:
memory: 128Mi
cpu: 500m
---
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
spec:
selector:
app: web-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: web-app-tls
namespace: default
spec:
secretName: web-app-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- web-app.example.com
- www.web-app.example.comapiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: nginx-deployment
app.kubernetes.io/version: "1.25.3"
app.kubernetes.io/part-of: my-app
app.kubernetes.io/managed-by: kubectl
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: nginx
template:
metadata:
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: nginx-deployment
app.kubernetes.io/version: "1.25.3"
app.kubernetes.io/part-of: my-app
app.kubernetes.io/managed-by: kubectl
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: nginx
image: nginx:1.25.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
# Edge-case fixture for count_yaml_documents.py
---
# Comment-only document should not be counted.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: doc-counter-regression
data:
script: |
echo start
---
echo middle
...
echo end
...
---
apiVersion: v1
kind: Service
metadata:
name: doc-counter-service
spec:
selector:
app: doc-counter-regression
ports:
- port: 80
targetPort: 8080
...
# Test file for k8s-yaml-validator skill - schema validation errors
# Purpose: Exercise kubeconform schema type-mismatch detection on syntactically
# valid YAML. The comprehensive-test.yaml cannot cover this path because its
# schema errors are shadowed by a YAML parse error in Document 1.
#
# Intentional errors:
# - spec.replicas: "3" → must be integer, not string
# - containerPort: "80" → must be integer, not string
#
# Expected behavior:
# - yamllint: PASS (valid YAML syntax)
# - detect_crd: 0 CRDs detected
# - kubeconform: FAIL (2 schema type errors reported as JSON paths)
# - dry-run: blocked by schema errors
apiVersion: apps/v1
kind: Deployment
metadata:
name: schema-error-app
namespace: default
labels:
app: schema-error-app
spec:
replicas: "3"
selector:
matchLabels:
app: schema-error-app
template:
metadata:
labels:
app: schema-error-app
spec:
containers:
- name: app
image: nginx:1.25.3
ports:
- containerPort: "80"
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Related skills
FAQ
What does k8s-yaml-validator check?
k8s-yaml-validator checks Kubernetes YAML manifests for schema correctness, syntax problems, and policy issues before apply or CI deploy. It helps teams catch misconfigurations that would cause failed rollouts or admission controller rejects.
When should teams run k8s-yaml-validator?
Teams should run k8s-yaml-validator during pull-request review or CI stages before kubectl apply. The skill targets pre-deploy validation so Deployment, Service, and Ingress files fail fast in pipelines instead of breaking production clusters.