Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion nac_validate/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ def version_callback(value: bool) -> None:
]


YamllintOn = Annotated[
bool,
typer.Option(
"--yamllint-on",
help="Enable yamllint validation.",
envvar="NAC_VALIDATE_YAMLLINT_ON",
),
]


Version = Annotated[
bool,
typer.Option(
Expand All @@ -142,13 +152,16 @@ def main(
rules: Rules = DEFAULT_RULES,
output: Output = None,
non_strict: NonStrict = False,
yamllint_on: YamllintOn = False,
version: Version = False,
) -> None:
"""A CLI tool to perform syntactic and semantic validation of YAML files."""
configure_logging(verbosity)

try:
validator = nac_validate.validator.Validator(schema, rules)
validator = nac_validate.validator.Validator(
schema, rules, enable_yamllint=yamllint_on
)
validator.validate_syntax(paths, not non_strict)
validator.validate_semantics(paths)
if output:
Expand Down
45 changes: 44 additions & 1 deletion nac_validate/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import importlib.util
import logging
import os
import subprocess # nosec B404
import sys
import warnings
from inspect import signature
Expand All @@ -29,7 +30,10 @@


class Validator:
def __init__(self, schema_path: Path, rules_path: Path):
def __init__(
self, schema_path: Path, rules_path: Path, enable_yamllint: bool = False
):
self.enable_yamllint = enable_yamllint
self.data: dict[str, Any] | None = None
self.schema = None
if os.path.exists(schema_path):
Expand Down Expand Up @@ -71,6 +75,41 @@ def __init__(self, schema_path: Path, rules_path: Path):
f"Rules directory not found: {rules_path}"
)

def _run_yamllint(self, file_path: Path) -> None:
"""Run yamllint validation on a file"""
if file_path.suffix not in [".yaml", ".yml"]:
return

logger.debug(f"Running yamllint on {file_path}")

try:
# NAC-specific yamllint configuration - minimal validation with only new-lines and anchors
config = "{extends: relaxed, rules: {key-duplicates: disable, new-line-at-end-of-file: disable, hyphens: disable, indentation: disable, colons: disable, commas: disable, empty-lines: disable, line-length: disable, trailing-spaces: disable, new-lines: enable}}"

result = subprocess.run( # nosec B603 B607
["yamllint", "-d", config, str(file_path)],
capture_output=True,
text=True,
)

logger.debug(f"Yamllint exit code: {result.returncode}")

if result.returncode != 0:
# Parse yamllint output - log errors but don't block other validations
for line in result.stdout.strip().split("\n"):
if line.strip() and ":" in line:
# Extract filename and error details
if file_path.name in line:
continue # Skip filename line
msg = f"Yamllint error: {line.strip()}"
logger.error(msg)
# Note: NOT adding to self.errors to allow other validations to continue

except FileNotFoundError:
logger.warning("yamllint not found - skipping yamllint validation")
except Exception as e:
logger.warning(f"yamllint validation failed for {file_path}: {e}")

def _validate_syntax_file(self, file_path: Path, strict: bool = True) -> None:
"""Run syntactic validation for a single file"""
if os.path.isfile(file_path) and file_path.suffix in [".yaml", ".yml"]:
Expand Down Expand Up @@ -107,6 +146,10 @@ def _validate_syntax_file(self, file_path: Path, strict: bool = True) -> None:
logger.error(msg)
self.errors.append(msg)

# Run yamllint after other validations (if enabled)
if self.enable_yamllint:
self._run_yamllint(file_path)

def _get_named_path(self, data: dict[str, Any], path: str) -> str:
"""Convert a numeric path to a named path for better error messages."""
path_segments = path.split(".")
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies = [
"typer>=0.17.4",
"yamale>=6.0.0",
"jmespath>=1.0.0",
"yamllint>=1.35.1",
]

[project.urls]
Expand Down