diff --git a/.github/workflows/self-test.yaml b/.github/workflows/self-test.yaml index 8a56f2e..8a63750 100644 --- a/.github/workflows/self-test.yaml +++ b/.github/workflows/self-test.yaml @@ -1,25 +1,29 @@ +--- name: Self Test -on: +'on': pull_request: - branches: [ main ] + branches: ['main'] jobs: runValidation: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Check if directory is not scanned, due to recursive is False (should PASS) - id: validation - uses: lyubick/action-YAML-schema-validator@test + - name: Run PyTest(s) + run: | + pip install -r requirements.txt + pip install -r requirements-test.txt + pytest test/ -rxXs + - name: Check YAML files inside directory + uses: ./ + continue-on-error: true with: - json-schema-file: test/json_schema.json - yaml-file-dir: test/ - - name: Check if directory is scanned, due to recursive is True (should FAIL) - id: invalidation + json-schema-file: test/schema/json_schema.json + yaml-file-dir: test/YAMLs/ + - name: Check JSON files inside directory + uses: ./ continue-on-error: true - uses: lyubick/action-YAML-schema-validator@test with: - json-schema-file: test/json_schema.json - yaml-file-dir: test/ - recursive: true + json-schema-file: test/schema/json_schema.json + yaml-file-dir: test/JSONs/ diff --git a/README.md b/README.md index 79cab88..99898d9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# action-YAML-schema-validator +# YAML/JSON File(s) Validation against JSON Schema ## Overview -This action can validate YAML files against provided JSON Schema. In case if YAML file is in full compliance -with provided JSON Schema action will exit gracefully, crash (unhandled exception) will occur otherwise. +This action can validate YAML or JSON files against provided JSON Schema. In case if provided file is in full compliance +with provided JSON Schema, action will exit gracefully, crash (unhandled exception) will occur otherwise. The JSON Schema format used, should be in compliance with https://json-schema.org/. @@ -14,20 +14,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Check my YAML file with my JSON schema + - name: Check my YAML/JSON file with my JSON schema id: validation - uses: lyubick/action-YAML-schema-validator@v1 + uses: lyubick/action-YAML-schema-validator@v2 with: json-schema-file: path/to/my/cool/schema.json - yaml-file-dir: path/to/my/cool/yaml/file.yaml + yaml-json-file-dir: path/to/my/cool/yaml/file.yaml recursive: false ``` One should provide two parameters: - `json-schema-file`, points to legit JSON Schema file -- `yaml-file-dir`, is a comma separated list that contains - - Single YAML files - - Directories that will be parsed for `.yaml` and `.yml` files -- `recursive`, True/False depending on if recursive scan for YAML files in directory required +- `yaml-json-file-dir`, is a comma separated list that contains + - Single YAML or JSON files + - Directories that will be parsed for `.yaml` or `.yml` or `.json` files +- `recursive`, True/False depending on if recursive scan for YAML or JSON files in directory required ## Results ### Success @@ -56,15 +56,32 @@ Traceback ... } } ``` -### YAML Valid file (compliant with Schema) + +### Valid file (compliant with Schema) +#### YAML ```yaml field1: Value1 ``` +#### JSON +```json +{ + "field1": "Value1" +} +``` -### YAML Invalid file (not compliant with Schema) +### Invalid file (not compliant with Schema) +#### YAML ```yaml field2: Value2 ``` + +#### JSON +```json +{ + "field2": "Value1" +} +``` + Validating this file will cause error (Exception) thus failing an action ```text Failed validating 'additionalProperties' in schema: diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/action.yaml b/action.yaml index 1b25deb..23ba037 100644 --- a/action.yaml +++ b/action.yaml @@ -1,5 +1,7 @@ -name: 'YAML file validation against JSON Schema' -description: 'Validates YAML file schema against provided JSON Schema file' +--- +name: 'YAML/JSON validation against JSON Schema' +description: | + Validates YAML or JSON file(s) against provided JSON Schema file branding: icon: check-circle color: green @@ -7,11 +9,15 @@ inputs: json-schema-file: description: 'JSON Schema file to validate against' required: true - yaml-file-dir: - description: 'Comma separated list of YAML files and/or directories that require validation' + yaml-json-file-dir: + description: | + Comma separated list of YAML or JSON files and/or directories that require + validation. required: true recursive: - description: 'True/False, provide True if recursive scan for YAML files in directory required' + description: | + True/False, provide True if recursive scan for YAML or JSON files + in directory required. required: false default: 'false' runs: diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..e56fe35 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,3 @@ +jsonschema==4.17.3 +pyyaml==6.0 +pytest==7.3.2 diff --git a/test/JSONs/yaml_invalid.json b/test/JSONs/yaml_invalid.json new file mode 100644 index 0000000..5ec2904 --- /dev/null +++ b/test/JSONs/yaml_invalid.json @@ -0,0 +1,3 @@ +{ + "field2": "Value2" +} diff --git a/test/JSONs/yaml_valid.json b/test/JSONs/yaml_valid.json new file mode 100644 index 0000000..228ea13 --- /dev/null +++ b/test/JSONs/yaml_valid.json @@ -0,0 +1,3 @@ +{ + "field1": "Value1" +} diff --git a/test/yamls/yaml_invalid.yaml b/test/YAMLs/yaml_invalid.yaml similarity index 78% rename from test/yamls/yaml_invalid.yaml rename to test/YAMLs/yaml_invalid.yaml index 1606904..c27d867 100644 --- a/test/yamls/yaml_invalid.yaml +++ b/test/YAMLs/yaml_invalid.yaml @@ -1 +1,2 @@ +--- field2: Value2 diff --git a/test/yamls/yaml_valid.yaml b/test/YAMLs/yaml_valid.yaml similarity index 78% rename from test/yamls/yaml_valid.yaml rename to test/YAMLs/yaml_valid.yaml index 1d2f68e..531964b 100644 --- a/test/yamls/yaml_valid.yaml +++ b/test/YAMLs/yaml_valid.yaml @@ -1 +1,2 @@ +--- field1: Value1 diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/json_schema.json b/test/schema/json_schema.json similarity index 100% rename from test/json_schema.json rename to test/schema/json_schema.json diff --git a/test/test_sanity.py b/test/test_sanity.py new file mode 100644 index 0000000..f6225e1 --- /dev/null +++ b/test/test_sanity.py @@ -0,0 +1,63 @@ +from pathlib import Path + +import validator +import jsonschema + + +class Test: + abs_path = Path(__file__).parent + + def test1_load_schema(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + assert schema['title'] == 'TestConfig' + assert schema['description'] == 'Test Basic YAML File' + + def test2_get_files(self): + files = validator.get_yaml_json_files_list(f'{self.abs_path}/YAMLs', is_recursive=True) + assert len(files) == 2 + + def test3_validate_valid_file(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + files = validator.get_yaml_json_files_list(f'{self.abs_path}/YAMLs/yaml_valid.yaml', False) + assert validator.validate_files(files, schema) + + def test4_validate_invalid_file(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + files = validator.get_yaml_json_files_list(f'{self.abs_path}/YAMLs/yaml_invalid.yaml', False) + try: + validator.validate_files(files, schema) + assert False + except jsonschema.exceptions.ValidationError as exc: + assert exc.instance == {'field2': 'Value2'} + + def test5_validate_valid_file_json(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + files = validator.get_yaml_json_files_list(f'{self.abs_path}/JSONs/yaml_valid.json', False) + assert validator.validate_files(files, schema) + + def test6_validate_invalid_file_json(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + files = validator.get_yaml_json_files_list(f'{self.abs_path}/JSONs/yaml_invalid.json', False) + try: + validator.validate_files(files, schema) + assert False + except jsonschema.exceptions.ValidationError as exc: + assert exc.instance == {'field2': 'Value2'} + + def test7_validate_folder_yaml(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + files = validator.get_yaml_json_files_list(f'{self.abs_path}/YAMLs/', False) + try: + validator.validate_files(files, schema) + assert False + except jsonschema.exceptions.ValidationError as exc: + assert exc.instance == {'field2': 'Value2'} + + def test8_validate_folder_json(self): + schema = validator.load_schema(f'{self.abs_path}/schema/json_schema.json') + files = validator.get_yaml_json_files_list(f'{self.abs_path}/JSONs/', False) + try: + validator.validate_files(files, schema) + assert False + except jsonschema.exceptions.ValidationError as exc: + assert exc.instance == {'field2': 'Value2'} diff --git a/validator.py b/validator.py index 32ead44..dc268ad 100644 --- a/validator.py +++ b/validator.py @@ -7,45 +7,52 @@ from jsonschema import validate from jsonschema.exceptions import ValidationError -if __name__ == '__main__': - args = sys.argv[1:] - - schema_file = args[0] - if os.path.exists(schema_file): - if os.path.isfile(schema_file): - with open(args[0], 'r') as stream: - schema = json.loads("".join(stream.readlines())) +def load_schema(schema_file_path: str) -> json: + if os.path.exists(schema_file_path): + if os.path.isfile(schema_file_path): + with open(schema_file_path, 'r') as stream: + return json.loads("".join(stream.readlines())) else: raise f'Provided JSON Schema is not a file! Please provide legit JSON Schema file.' else: raise f'Provided JSON Schema file does not exist!' - yaml_input = list(args[1].split(',')) - recursive = args[2].lower() == 'true' +def get_yaml_json_files_list(files_paths_list: str, is_recursive: bool) -> list[str]: + yaml_input = list(files_paths_list.split(',')) yaml_files = [] for yaml_object in yaml_input: if os.path.isdir(yaml_object): yaml_files.extend( list( - filter( - lambda f: str(f).endswith('.yaml') or str(f).endswith('.yml'), - pathlib.Path(yaml_object).glob('**/*' if recursive else '*') + map( + lambda f: str(f), # Convert all paths to string, instead of Posix Path + filter( + lambda f: str(f).endswith('.yaml') or str(f).endswith('.yml') or str(f).endswith('.json'), + pathlib.Path(yaml_object).glob('**/*' if is_recursive else '*') + ) ) ) ) elif os.path.isfile(yaml_object): yaml_files.append(yaml_object) + return yaml_files + + +def validate_files(yaml_files: list, json_schema: json): for yaml_file in yaml_files: if os.path.exists(yaml_file): if os.path.isfile(yaml_file): with open(yaml_file, 'r') as stream: - yaml_json = json.loads(json.dumps(yaml.safe_load(stream))) + if not yaml_file.endswith('.json'): + yaml_json = json.loads(json.dumps(yaml.safe_load(stream))) + else: + yaml_json = json.load(stream) try: - validate(instance=yaml_json, schema=schema) + validate(instance=yaml_json, schema=json_schema) except ValidationError as exc: print(f'File `{yaml_file}` failed validation with >>>`{exc}`<<<', file=sys.stderr) raise exc @@ -53,3 +60,12 @@ raise f'Provided YAML file is not a file! Please provide legit YAML file.' else: raise f'Provided YAML file does not exist!' + return True + + +if __name__ == '__main__': + args = sys.argv[1:] + + schema = load_schema(args[0]) + files = get_yaml_json_files_list(args[1], args[2].lower() == 'true') + validate_files(files, schema)