Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
43 changes: 43 additions & 0 deletions .github/workflows/validate-schemas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Validate JSON Schemas

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
validate-schemas:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Python 3.12.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to Python 3.12 in commit b9c70e5. Also updated the Python version constraint in pyproject.toml to ^3.9, < 3.13 to support Python 3.12.


- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install jsonschema PyYAML
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of installing packages like this can we manage develop dependencies within pyproject.toml instead? Currently the repo is setup to use poetry but we can go ahead and change that to uv instead as that part of the repo isn't currently in use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit b9c70e5. Dependencies are now managed in pyproject.toml under [tool.poetry.group.dev.dependencies] and the workflow uses poetry install --with dev to install them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use uv instead of poetry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to uv for dependency management in commit f741199. The workflow now uses uv sync --extra dev and uv run pytest instead of poetry commands. Also simplified pyproject.toml to use standard Python project format and replaced poetry.lock with uv.lock.


- name: Make validation script executable
run: chmod +x validate_schemas.py

- name: Validate JSON schema files
run: python validate_schemas.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this with pytest instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted to pytest in commit b9c70e5. The validation is now implemented as pytest tests that provide better reporting and can be run with poetry run pytest test_schema_validation.py -v.


- name: Check for JSON syntax in all JSON files
run: |
echo "πŸ” Double-checking JSON syntax with jq..."
find . -name "*.json" -type f | while read file; do
echo "Checking $file"
if ! jq empty "$file" 2>/dev/null; then
echo "❌ Invalid JSON in $file"
exit 1
fi
done
echo "βœ… All JSON files have valid syntax"
69 changes: 69 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Temporary files
/tmp/
*.tmp
*.temp

# Python cache
__pycache__/
*.py[cod]
*$py.class
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
*.manifest
*.spec

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ kind: Menu

Everything under the `schemas/` directory is automatically published to `https://schema.infrahub.app` to simplify the integration with external tools that requires a public URL

## Validation

This repository includes automated validation to ensure all JSON files are valid JSON schemas and any `.infrahub.yml` files conform to the repository configuration schema.

### Running Validation Locally

To validate all schema files locally:

```shell
python validate_schemas.py
```

This script will:
- Validate that all `.json` files contain valid JSON and are valid JSON schemas
- Validate that any `.infrahub.yml` files are valid YAML and conform to the repository config schema
- Report any validation errors with detailed error messages

### Continuous Integration

The validation runs automatically on every push and pull request via GitHub Actions. The CI workflow:
- Validates all JSON schema files using the validation script
- Double-checks JSON syntax using `jq`
- Fails the build if any validation errors are found

## How to update a Schema

Generate the new schemas, using the invoke tool from the main [Infrahub repository](https://github.com/opsmill/infrahub).
Expand All @@ -54,3 +78,9 @@ Example:
cd schemas/infrahub/schema
ln -f -s 0.12.0.json latest.json
```

After updating schemas, run the validation to ensure they are correct:

```shell
python validate_schemas.py
```
198 changes: 198 additions & 0 deletions validate_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
Validation script for JSON schema files and .infrahub.yml configuration files.
This script validates that:
1. All .json files are valid JSON and valid JSON schemas
2. All .infrahub.yml files are valid YAML and conform to the repository config schema
"""

import json
import sys
import glob
import os
from pathlib import Path
from typing import List, Tuple, Dict, Any

try:
import jsonschema
from jsonschema import Draft7Validator, Draft202012Validator
except ImportError:
print("jsonschema package not found. Please install it with: pip install jsonschema")
sys.exit(1)

try:
import yaml
except ImportError:
print("PyYAML package not found. Please install it with: pip install PyYAML")
sys.exit(1)


def validate_json_file(file_path: str) -> Tuple[bool, str]:
"""Validate that a file contains valid JSON."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
json.load(f)
return True, ""
except json.JSONDecodeError as e:
return False, f"Invalid JSON: {e}"
except Exception as e:
return False, f"Error reading file: {e}"


def validate_json_schema(file_path: str) -> Tuple[bool, str]:
"""Validate that a JSON file is a valid JSON schema."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
schema = json.load(f)

# Try to create a validator - this will fail if it's not a valid schema
# First try Draft 2020-12, then fall back to Draft 7
try:
Draft202012Validator.check_schema(schema)
except jsonschema.SchemaError:
try:
Draft7Validator.check_schema(schema)
except jsonschema.SchemaError as e:
return False, f"Invalid JSON schema: {e}"

return True, ""
except json.JSONDecodeError as e:
return False, f"Invalid JSON: {e}"
except Exception as e:
return False, f"Error validating schema: {e}"


def validate_yaml_file(file_path: str) -> Tuple[bool, str, Dict[Any, Any]]:
"""Validate that a file contains valid YAML and return the parsed content."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = yaml.safe_load(f)
return True, "", content
except yaml.YAMLError as e:
return False, f"Invalid YAML: {e}", {}
except Exception as e:
return False, f"Error reading file: {e}", {}


def validate_infrahub_yml_against_schema(yml_content: Dict[Any, Any], schema_path: str) -> Tuple[bool, str]:
"""Validate .infrahub.yml content against the repository config schema."""
try:
with open(schema_path, 'r', encoding='utf-8') as f:
schema = json.load(f)

validator = Draft202012Validator(schema)
if not validator.is_valid(yml_content):
errors = list(validator.iter_errors(yml_content))
error_messages = [f" - {error.message} at {'.'.join(str(x) for x in error.absolute_path)}" for error in errors]
return False, f"Schema validation failed:\n" + "\n".join(error_messages)

return True, ""
except Exception as e:
return False, f"Error validating against schema: {e}"


def find_latest_repository_config_schema() -> str:
"""Find the latest repository config schema file."""
schema_dir = "schemas/python-sdk/repository-config"
if not os.path.exists(schema_dir):
return ""

# Look for develop.json first, then latest.json, then the highest version
develop_path = os.path.join(schema_dir, "develop.json")
if os.path.exists(develop_path):
return develop_path

latest_path = os.path.join(schema_dir, "latest.json")
if os.path.exists(latest_path):
return latest_path

# Find the highest version number
version_files = glob.glob(os.path.join(schema_dir, "*.json"))
if version_files:
return sorted(version_files)[-1] # Last in alphabetical order should be highest version

return ""


def main():
"""Main validation function."""
repo_root = Path(__file__).parent
os.chdir(repo_root)

errors = []

print("πŸ” Validating JSON schema files...")

# Find all JSON files
json_files = glob.glob("**/*.json", recursive=True)

for json_file in sorted(json_files):
print(f" Checking {json_file}...")

# First validate it's valid JSON
is_valid_json, json_error = validate_json_file(json_file)
if not is_valid_json:
errors.append(f"❌ {json_file}: {json_error}")
continue

# Then validate it's a valid JSON schema
is_valid_schema, schema_error = validate_json_schema(json_file)
if not is_valid_schema:
errors.append(f"❌ {json_file}: {schema_error}")
else:
print(f" βœ… Valid JSON schema")

print(f"\nπŸ” Validating .infrahub.yml files...")

# Find all .infrahub.yml files
infrahub_yml_files = []
infrahub_yml_files.extend(glob.glob("**/.infrahub.yml", recursive=True))
infrahub_yml_files.extend(glob.glob("**/infrahub.yml", recursive=True))
infrahub_yml_files.extend(glob.glob("**/*.infrahub.yml", recursive=True))

if not infrahub_yml_files:
print(" No .infrahub.yml files found to validate")
else:
# Find the repository config schema
repo_config_schema = find_latest_repository_config_schema()
if not repo_config_schema:
print(" ⚠️ Warning: Could not find repository config schema to validate .infrahub.yml files")
else:
print(f" Using schema: {repo_config_schema}")

for yml_file in sorted(infrahub_yml_files):
print(f" Checking {yml_file}...")

# Validate YAML syntax
is_valid_yaml, yaml_error, yml_content = validate_yaml_file(yml_file)
if not is_valid_yaml:
errors.append(f"❌ {yml_file}: {yaml_error}")
continue

# Validate against schema if available
if repo_config_schema:
is_valid_against_schema, schema_error = validate_infrahub_yml_against_schema(yml_content, repo_config_schema)
if not is_valid_against_schema:
errors.append(f"❌ {yml_file}: {schema_error}")
else:
print(f" βœ… Valid against repository config schema")
else:
print(f" βœ… Valid YAML (schema validation skipped)")

# Report results
print(f"\nπŸ“Š Validation Summary:")
print(f" JSON files checked: {len(json_files)}")
print(f" .infrahub.yml files checked: {len(infrahub_yml_files)}")

if errors:
print(f"\n❌ Found {len(errors)} validation errors:")
for error in errors:
print(f" {error}")
sys.exit(1)
else:
print(f"\nβœ… All files passed validation!")
sys.exit(0)


if __name__ == "__main__":
main()