-
Notifications
You must be signed in to change notification settings - Fork 1
Add CI job to validate JSON schema files and .infrahub.yml configurations #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
14f82b3
99fdaa8
b9c70e5
f741199
0cbe00f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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' | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install jsonschema PyYAML | ||
|
||
|
|
||
| - name: Make validation script executable | ||
| run: chmod +x validate_schemas.py | ||
|
|
||
| - name: Validate JSON schema files | ||
| run: python validate_schemas.py | ||
|
||
|
|
||
| - 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" | ||
| 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 |
| 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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.13to support Python 3.12.