In this post, I want to show another piece of Python code, now, to validate a YAML file against a JSON Schema.
There are very good Schema JSON generators, I personally use Hackolade Community Studio a lot: https://studio.hackolade.com/ there is a free version, it has the immense advantage of being able to see and work in the Json class as if it were a Database class/table, import a schema or a Yaml/Json so that this class is generated, work visually on the properties and attributes and then export the finished Json Schema.
For this task, I will use Python modules:
PyYaml: https://pypi.org/project/PyYAML/
jsonschema: https://pypi.org/project/jsonschema/
I will leave here a Yaml file that is the initial version of the DNS Blueprint and its valid Schema JSon, as I will use.
devops-db.info.yaml:
version: 1
group: infrastructure
tech: dns
service: devops-db.info
description: Blueprint with DNS zones from devops-db.info
nameservers:
- name: ns1.devops-db.info
class: IN
type: A
destination: 172.21.5.72
admin: admin.devops-db.info
serial_number: 2022122800
time_to_refresh: 12h
time_to_retry: 15m
time_to_expire: 3w
minimum_ttl: 2h
zones:
- host: ldap
class: IN
type: A
destination: 172.21.5.150
- host: registry
class: IN
type: A
destination: 172.21.5.151
devops-db.info.json
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"title": "devops-db.info",
"properties": {
"version": {
"type": "integer",
"minimum": 1
},
"group": {
"type": "string",
"enum": [
"infrastructure"
]
},
"tech": {
"type": "string",
"enum": [
"dns"
]
},
"service": {
"type": "string",
"maxLength": 40,
"minLength": 1
},
"description": {
"type": "string"
},
"nameservers": {
"type": "array",
"additionalItems": true,
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 20,
"minLength": 1
},
"class": {
"type": "string",
"enum": [
"IN"
]
},
"type": {
"type": "string",
"enum": [
"A"
]
},
"destination": {
"type": "string",
"format": "ipv4"
}
},
"additionalProperties": true,
"required": [
"name",
"class",
"type",
"destination"
]
}
},
"admin": {
"type": "string"
},
"serial_number": {
"type": "integer"
},
"time_to_refresh": {
"type": "string"
},
"time_to_retry": {
"type": "string"
},
"time_to_expire": {
"type": "string"
},
"minimum_ttl": {
"type": "string"
},
"zones": {
"type": "array",
"additionalItems": true,
"items": {
"type": "object",
"properties": {
"host": {
"type": "string",
"maxLength": 20,
"minLength": 1
},
"class": {
"type": "string",
"enum": [
"IN"
]
},
"type": {
"type": "string",
"enum": [
"A"
]
},
"destination": {
"type": "string",
"format": "ipv4"
}
},
"additionalProperties": true,
"required": [
"host",
"class",
"type",
"destination"
]
}
}
},
"additionalProperties": true,
"required": [
"version",
"group",
"tech",
"service",
"description",
"nameservers",
"admin",
"serial_number",
"time_to_refresh",
"time_to_retry",
"time_to_expire",
"minimum_ttl",
"zones"
]
}
See that in this Schema, I just do some simple validations, some enums, minimum and maximum values, minLength and maxLength and also IPV4, in addition, of course, if the parameter is required and its type.
Let’s go to the Python code, this script opens the YAML file, converts it to Json. Then load the Schema file and apply jsonschema validation against the 2020-12 Standard ( https://json-schema.org/draft/2020-12/json-schema-validation ).
If ok, I just print the OK message, otherwise, I show the list of errors, reason and where the error is.
import jsonschema
import yaml
import json
from datetime import datetime
str_DateTime = ''
with open('devops-db.info.json', 'r') as tmp_file:
obj_schema = json.load(tmp_file)
with open('devops-db.info.yaml', 'r') as tmp_json_stream:
try:
json_data = yaml.full_load(tmp_json_stream)
except yaml.YAMLError as exception:
raise exception
try:
obj_validator = jsonschema.Draft202012Validator(obj_schema, format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER)
obj_errors = obj_validator.iter_errors(json_data)
lst_errors = []
for error in obj_errors:
lst_errors.append(error)
except jsonschema.exceptions.ValidationError as obj_exceptions:
print(obj_exceptions)
str_DateTime = datetime.now().strftime('%H:%M:%S')
if len(lst_errors) == 0:
print(str_DateTime + ' - Blueprint OK.')
else:
for item_error in lst_errors:
print(str_DateTime + ' - Blueprint validation error: ' + item_error.message + ' / Reason: ' + str(item_error.schema) + ' / Where: ' + str(list(item_error.absolute_path)))
This is an example of returning if everything went well.
12:40:41 - Blueprint OK.
I’m going to force some errors in the second zone entry:
- host: registry
class: OUT
type: B
destination: 172.21.5.151.XXX
The result of the script should be:
13:02:30 - Blueprint validation error: 'OUT' is not one of ['IN'] / Reason: {'type': 'string', 'enum': ['IN']} / Where: ['zones', 1, 'class']
13:02:30 - Blueprint validation error: 'B' is not one of ['A'] / Reason: {'type': 'string', 'enum': ['A']} / Where: ['zones', 1, 'type']
13:02:30 - Blueprint validation error: '172.21.5.151.XXX' is not a 'ipv4' / Reason: {'type': 'string', 'format': 'ipv4'} / Where: ['zones', 1, 'destination']
Leave a Reply