Skip to content

Commit

Permalink
Ignore missing properties for kubernetes and improve logging (#8)
Browse files Browse the repository at this point in the history
* Change logging behaviour and add k8s extension

Changes:
 - verbose CLI flag enables debug logging,
 - print short information on failure, rather than traceback,
 - print absolute paths for schemas,
 - add k8s 'x-kubernetes-preserve-unknown-fields' handling.

* Fix CI errors and add k8s test

---------

Co-authored-by: Julien Mailleret <[email protected]>
  • Loading branch information
rethil and jmlrt authored Jan 2, 2025
1 parent ceb42f1 commit 248d173
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ jobs:
shell: bash
- run: check-yamlschema -v tests/test.yaml .pre-commit-config.yaml .pre-commit-hooks.yaml .github/workflows/pre-commit.yml
shell: bash
- run: check-yamlschema --k8s -v tests/test-k8s.yaml
shell: bash
94 changes: 77 additions & 17 deletions check_yamlschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,46 @@
import yaml


def is_remote_schema(schema_url):
return schema_url.startswith("http://") or schema_url.startswith("https://")


def get_absolute_path(file, schema_path):
if not schema_path or is_remote_schema(schema_path):
return schema_path

current_dir = os.path.dirname(os.path.realpath(file))
schema_path = os.path.join(current_dir, schema_path)
abs_path = os.path.abspath(schema_path)
return abs_path


def get_validator(with_k8s_extension):
basic_validator = jsonschema.validators.Draft202012Validator

if not with_k8s_extension:
return basic_validator

basic_additional_props_validator = basic_validator.VALIDATORS[
"additionalProperties"
]

def additional_props_hook(validator, value, instance, schema):
if "x-kubernetes-preserve-unknown-fields" in schema:
if type(schema["properties"]) is dict:
for prop in list(instance.keys()):
if prop not in schema["properties"]:
del instance[prop]

return basic_additional_props_validator(validator, value, instance, schema)

custom_validator = jsonschema.validators.extend(
basic_validator, validators={"additionalProperties": additional_props_hook}
)

return custom_validator


def load_yaml_documents(file_path):
with open(file_path) as file:
content = file.read()
Expand All @@ -32,9 +72,10 @@ def load_yaml_documents(file_path):
doc_dict = {
"content": yaml.safe_load(doc),
"comment": comment,
"schema_url": schema_url,
"schema_url": get_absolute_path(file_path, schema_url),
}
yaml_documents.append(doc_dict)

return yaml_documents


Expand All @@ -44,16 +85,14 @@ def download_schema(schema_url):
return response.json()


def validate_document(file, document, schema_url):
if schema_url.startswith("http://") or schema_url.startswith("https://"):
def validate_document(file, document, schema_url, validator):
if is_remote_schema(schema_url):
schema = download_schema(schema_url)
else:
current_dir = os.path.dirname(os.path.realpath(file))
schema_path = os.path.join(current_dir, schema_url)

with open(schema_path) as file:
with open(schema_url) as file:
schema = json.load(file)
jsonschema.validate(instance=document, schema=schema)

validator(schema).validate(document)


def main():
Expand All @@ -63,24 +102,45 @@ def main():
)
parser.add_argument("files", nargs="+", help="Path to YAML files to validate")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("--k8s", action="store_true")
args = parser.parse_args()

if args.verbose:
logging.getLogger().setLevel(logging.INFO)
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.ERROR)

validator = get_validator(args.k8s)
fails_count = 0

for file in args.files:
yaml_documents = load_yaml_documents(file)
logging.info("File %s: %s documents", file, len(yaml_documents))

for index, doc in enumerate(yaml_documents):
if doc["schema_url"] is not None:
validate_document(file, doc["content"], doc["schema_url"])
logging.info(
"- Document %s validated according to %s", index, doc["schema_url"]
)
try:
validate_document(
file, doc["content"], doc["schema_url"], validator
)
logging.debug(
f"{file} document {index}: validated according to {doc["schema_url"]}"
)
except jsonschema.exceptions.ValidationError as exception:
logging.error(
f"{file} document {index}: validation failed according to {doc["schema_url"]}:\n"
f"{exception.message}"
)
logging.debug(exception, stack_info=True)
fails_count += 1
except Exception as exception:
logging.error(
f"{file} document {index}: validation failed:\n {exception}"
)
fails_count += 1
else:
logging.info(
"- Document %s has no JSON schema defined in comments", index
)
logging.debug(f"{file}: no JSON schema defined in comments")

return fails_count


if __name__ == "__main__":
Expand Down
29 changes: 29 additions & 0 deletions tests/extraEnvs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"description": "Part of VMAgent jsonschema. CRD at https://github.com/VictoriaMetrics/helm-charts/blob/master/charts/victoria-metrics-operator/charts/crds/crds/crd.yaml",
"type": "object",
"properties": {
"extraEnvs": {
"description": "ExtraEnvs that will be passed to the application container",
"items": {
"description": "EnvVar represents an environment variable present in a Container.",
"properties": {
"name": {
"description": "Name of the environment variable. Must be a C_IDENTIFIER.",
"type": "string"
},
"value": {
"description": "Variable references $(VAR_NAME) are expanded\nusing the previously defined environment variables in the container and\nany service environment variables. If a variable cannot be resolved,\nthe reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e.\n\"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\nEscaped references will never be expanded, regardless of whether the variable\nexists or not.\nDefaults to \"\".",
"type": "string"
}
},
"required": [
"name"
],
"type": "object",
"x-kubernetes-preserve-unknown-fields": true,
"additionalProperties": false
},
"type": "array"
}
}
}
9 changes: 9 additions & 0 deletions tests/test-k8s.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# yaml-language-server: $schema=./extraEnvs.json
extraEnvs:
- name: GOMEMLIMIT
valueFrom:
resourceFieldRef:
containerName: vmagent
divisor: "1"
resource: limits.memory

0 comments on commit 248d173

Please sign in to comment.