From 9d96cfbbe1e4d468de3a13de1fe49ff7364952b8 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Tue, 29 Aug 2023 16:31:21 +0100 Subject: [PATCH] Include links to documentation for schema validation errors (#3684) --- .readthedocs.yml | 1 - src/ansiblelint/rules/schema.py | 43 +++++++++++++------ src/ansiblelint/schemas/__store__.json | 2 +- .../schemas/ansible-lint-config.json | 1 + .../schemas/ansible-navigator-config.json | 1 + src/ansiblelint/schemas/ansible.json | 1 + .../schemas/execution-environment.json | 3 +- src/ansiblelint/schemas/galaxy.json | 1 + src/ansiblelint/schemas/inventory.json | 2 +- src/ansiblelint/schemas/main.py | 23 +++++++++- src/ansiblelint/schemas/meta.json | 1 + src/ansiblelint/schemas/molecule.json | 1 + src/ansiblelint/schemas/requirements.json | 1 + src/ansiblelint/schemas/role-arg-spec.json | 23 ++-------- src/ansiblelint/schemas/vars.json | 1 + 15 files changed, 67 insertions(+), 38 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 8262c4e6bae..78a84d92993 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,7 +13,6 @@ build: - pip install --user tox - python3 -m tox -e docs -- --strict --site-dir=_readthedocs/html/ python: - system_packages: false install: - method: pip path: tox diff --git a/src/ansiblelint/rules/schema.py b/src/ansiblelint/rules/schema.py index bdc13a98f5f..7ca8ce669ad 100644 --- a/src/ansiblelint/rules/schema.py +++ b/src/ansiblelint/rules/schema.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +import re import sys from typing import TYPE_CHECKING, Any @@ -193,25 +194,27 @@ def matchyaml(self, file: Lintable) -> list[MatchError]: pytest.param( "examples/.collection/galaxy.yml", "galaxy", - ["'GPL' is not one of"], + [r"^'GPL' is not one of.*https://"], id="galaxy", ), pytest.param( "examples/roles/invalid_requirements_schema/meta/requirements.yml", "requirements", - ["{'foo': 'bar'} is not valid under any of the given schemas"], + [ + r"^{'foo': 'bar'} is not valid under any of the given schemas.*https://", + ], id="requirements", ), pytest.param( "examples/roles/invalid_meta_schema/meta/main.yml", "meta", - ["False is not of type 'string'"], + [r"^False is not of type 'string'.*https://"], id="meta", ), pytest.param( "examples/playbooks/vars/invalid_vars_schema.yml", "vars", - ["'123' does not match any of the regexes"], + [r"^'123' does not match any of the regexes.*https://"], id="vars", ), pytest.param( @@ -223,14 +226,18 @@ def matchyaml(self, file: Lintable) -> list[MatchError]: pytest.param( "examples/ee_broken/execution-environment.yml", "execution-environment", - ["{'foo': 'bar'} is not valid under any of the given schemas"], + [ + r"^{'foo': 'bar'} is not valid under any of the given schemas.*https://", + ], id="execution-environment-broken", ), ("examples/meta/runtime.yml", "meta-runtime", []), pytest.param( "examples/broken_collection_meta_runtime/meta/runtime.yml", "meta-runtime", - ["Additional properties are not allowed ('foo' was unexpected)"], + [ + r"^Additional properties are not allowed \('foo' was unexpected\).*https://", + ], id="meta-runtime-broken", ), pytest.param( @@ -242,7 +249,9 @@ def matchyaml(self, file: Lintable) -> list[MatchError]: pytest.param( "examples/inventory/broken_dev_inventory.yml", "inventory", - ["Additional properties are not allowed ('foo' was unexpected)"], + [ + r"^Additional properties are not allowed \('foo' was unexpected\).*https://", + ], id="inventory-broken", ), pytest.param( @@ -260,7 +269,9 @@ def matchyaml(self, file: Lintable) -> list[MatchError]: pytest.param( "examples/broken/.ansible-lint", "ansible-lint-config", - ["Additional properties are not allowed ('foo' was unexpected)"], + [ + r"^Additional properties are not allowed \('foo' was unexpected\).*https://", + ], id="ansible-lint-config-broken", ), pytest.param( @@ -272,7 +283,9 @@ def matchyaml(self, file: Lintable) -> list[MatchError]: pytest.param( "examples/broken/ansible-navigator.yml", "ansible-navigator-config", - ["Additional properties are not allowed ('ansible' was unexpected)"], + [ + r"^Additional properties are not allowed \('ansible' was unexpected\).*https://", + ], id="ansible-navigator-config-broken", ), pytest.param( @@ -284,20 +297,24 @@ def matchyaml(self, file: Lintable) -> list[MatchError]: pytest.param( "examples/roles/broken_argument_specs/meta/argument_specs.yml", "role-arg-spec", - ["Additional properties are not allowed ('foo' was unexpected)"], + [ + r"^Additional properties are not allowed \('foo' was unexpected\).*https://", + ], id="role-arg-spec-broken", ), pytest.param( "examples/changelogs/changelog.yaml", "changelog", - ["Additional properties are not allowed ('foo' was unexpected)"], + [ + r"^Additional properties are not allowed \('foo' was unexpected\).*https://", + ], id="changelog", ), pytest.param( "examples/rulebooks/rulebook-fail.yml", "rulebook", [ - "Additional properties are not allowed ('that_should_not_be_here' was unexpected)", + r"^Additional properties are not allowed \('that_should_not_be_here' was unexpected\).*https://", ], id="rulebook", ), @@ -336,7 +353,7 @@ def test_schema(file: str, expected_kind: str, expected: list[str]) -> None: assert len(results) == len(expected), results for idx, result in enumerate(results): assert result.filename.endswith(file) - assert expected[idx] in result.message + assert re.match(expected[idx], result.message) assert result.tag == f"schema[{expected_kind}]" @pytest.mark.parametrize( diff --git a/src/ansiblelint/schemas/__store__.json b/src/ansiblelint/schemas/__store__.json index 889acb8ed07..c96ff7dadc0 100644 --- a/src/ansiblelint/schemas/__store__.json +++ b/src/ansiblelint/schemas/__store__.json @@ -4,7 +4,7 @@ "url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/ansible-lint-config.json" }, "ansible-navigator-config": { - "etag": "dd0f0dea68266ae61e5a8d6aed0a1279fdee16f2da4911bc27970241df80f798", + "etag": "815582d2c4b252906bf83d1da9667dedf16dbd9b6149cfceea205c4848c78f53", "url": "https://raw.githubusercontent.com/ansible/ansible-navigator/main/src/ansible_navigator/data/ansible-navigator.json" }, "changelog": { diff --git a/src/ansiblelint/schemas/ansible-lint-config.json b/src/ansiblelint/schemas/ansible-lint-config.json index 33dd10d57ca..8c6128a3f1e 100644 --- a/src/ansiblelint/schemas/ansible-lint-config.json +++ b/src/ansiblelint/schemas/ansible-lint-config.json @@ -17,6 +17,7 @@ "$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/ansible-lint-config.json", "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": false, + "description": "https://ansible.readthedocs.io/projects/lint/configuring/", "examples": [ ".ansible-lint", ".config/ansible-lint.yml", diff --git a/src/ansiblelint/schemas/ansible-navigator-config.json b/src/ansiblelint/schemas/ansible-navigator-config.json index e81a878f4e1..494a56d550e 100644 --- a/src/ansiblelint/schemas/ansible-navigator-config.json +++ b/src/ansiblelint/schemas/ansible-navigator-config.json @@ -1,6 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": false, + "description": "See https://ansible.readthedocs.io/projects/navigator/settings/", "properties": { "ansible-navigator": { "additionalProperties": false, diff --git a/src/ansiblelint/schemas/ansible.json b/src/ansiblelint/schemas/ansible.json index f16abf1e4ed..5a6ea7cc515 100644 --- a/src/ansiblelint/schemas/ansible.json +++ b/src/ansiblelint/schemas/ansible.json @@ -1196,6 +1196,7 @@ "$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/ansible.json", "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": false, + "description": "https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html", "examples": [], "title": "Ansible Schemas Bundle 22.4", "type": ["array", "object"] diff --git a/src/ansiblelint/schemas/execution-environment.json b/src/ansiblelint/schemas/execution-environment.json index 4720a93859c..7d44ab38c59 100644 --- a/src/ansiblelint/schemas/execution-environment.json +++ b/src/ansiblelint/schemas/execution-environment.json @@ -302,7 +302,8 @@ }, "$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/execution-environment.json", "$schema": "http://json-schema.org/draft-07/schema", - "description": "See \nV1: https://docs.ansible.com/automation-controller/latest/html/userguide/ee_reference.html\nV3: https://ansible-builder.readthedocs.io/en/latest/definition/", + "description": "See https://ansible-builder.readthedocs.io/en/latest/definition/ for V3 or https://docs.ansible.com/automation-controller/latest/html/userguide/ee_reference.html for older V1 format.\n", + "documentation_url": "https://ansible.readthedocs.io/projects/builder/en/latest/definition/", "examples": ["execution-environment.yml"], "oneOf": [{ "$ref": "#/$defs/v3" }, { "$ref": "#/$defs/v1" }], "title": "Ansible Execution Environment Schema v1/v3" diff --git a/src/ansiblelint/schemas/galaxy.json b/src/ansiblelint/schemas/galaxy.json index 6381f28de69..03b74cc63c0 100644 --- a/src/ansiblelint/schemas/galaxy.json +++ b/src/ansiblelint/schemas/galaxy.json @@ -525,6 +525,7 @@ "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": false, "examples": ["galaxy.yml"], + "markdownDescription": "https://docs.ansible.com/ansible/latest/dev_guide/collections_galaxy_meta.html", "properties": { "authors": { "items": { diff --git a/src/ansiblelint/schemas/inventory.json b/src/ansiblelint/schemas/inventory.json index 80333ce84be..06cf2cafe16 100644 --- a/src/ansiblelint/schemas/inventory.json +++ b/src/ansiblelint/schemas/inventory.json @@ -45,7 +45,7 @@ "$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/inventory.json", "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": true, - "description": "Ansible Inventory Schema", + "description": "See https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html", "examples": [ "inventory.yaml", "inventory.yml", diff --git a/src/ansiblelint/schemas/main.py b/src/ansiblelint/schemas/main.py index 590aea33557..add87f97998 100644 --- a/src/ansiblelint/schemas/main.py +++ b/src/ansiblelint/schemas/main.py @@ -3,6 +3,7 @@ import json import logging +import re from typing import TYPE_CHECKING import jsonschema @@ -20,18 +21,36 @@ def validate_file_schema(file: Lintable) -> list[str]: """Return list of JSON validation errors found.""" + schema = {} if file.kind not in JSON_SCHEMAS: return [f"Unable to find JSON Schema '{file.kind}' for '{file.path}' file."] try: # convert yaml to json (keys are converted to strings) yaml_data = yaml_load_safe(file.content) json_data = json.loads(json.dumps(yaml_data)) + schema = _schema_cache[file.kind] jsonschema.validate( instance=json_data, - schema=_schema_cache[file.kind], + schema=schema, ) except yaml.constructor.ConstructorError as exc: return [f"Failed to load YAML file '{file.path}': {exc.problem}"] except ValidationError as exc: - return [exc.message] + message = exc.message + documentation_url = "" + for k in ("description", "markdownDescription"): + if k in schema: + # Find standalone URLs and also markdown urls. + match = re.search( + r"\[.*?\]\((https?://[^\s]+)\)|https?://[^\s]+", + schema[k], + ) + if match: + documentation_url = match[0] if match[0] else match[1] + break + if documentation_url: + if not message.endswith("."): + message += "." + message += f" See {documentation_url}" + return [message] return [] diff --git a/src/ansiblelint/schemas/meta.json b/src/ansiblelint/schemas/meta.json index 384d113bb31..8842ead0cb5 100644 --- a/src/ansiblelint/schemas/meta.json +++ b/src/ansiblelint/schemas/meta.json @@ -1447,6 +1447,7 @@ }, "$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/meta.json", "$schema": "http://json-schema.org/draft-07/schema", + "description": "https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#using-role-dependencies", "examples": ["meta/main.yml"], "properties": { "additionalProperties": false, diff --git a/src/ansiblelint/schemas/molecule.json b/src/ansiblelint/schemas/molecule.json index d957f083ffb..21f16105b30 100644 --- a/src/ansiblelint/schemas/molecule.json +++ b/src/ansiblelint/schemas/molecule.json @@ -512,6 +512,7 @@ "$id": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/molecule.json", "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": false, + "description": "https://ansible.readthedocs.io/projects/molecule/configuration/", "examples": ["molecule/*/molecule.yml"], "properties": { "dependency": { diff --git a/src/ansiblelint/schemas/requirements.json b/src/ansiblelint/schemas/requirements.json index dc7ded629c3..ef8d2a45cea 100644 --- a/src/ansiblelint/schemas/requirements.json +++ b/src/ansiblelint/schemas/requirements.json @@ -130,6 +130,7 @@ "$ref": "#/$defs/RequirementsV2Model" } ], + "description": "https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-roles-and-collections-from-the-same-requirements-yml-file", "examples": ["requirements.yml"], "title": "Ansible Requirements Schema" } diff --git a/src/ansiblelint/schemas/role-arg-spec.json b/src/ansiblelint/schemas/role-arg-spec.json index 433993ef7ac..23ce4439325 100644 --- a/src/ansiblelint/schemas/role-arg-spec.json +++ b/src/ansiblelint/schemas/role-arg-spec.json @@ -148,26 +148,11 @@ }, "option": { "additionalProperties": false, - "aliases": { - "items": { + "markdownDescription": "See [argument-spec](https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec)", + "properties": { + "apply_defaults": { "type": "string" }, - "type": "array" - }, - "apply_defaults": { - "type": "string" - }, - "deprecated_aliases": { - "items": { - "$ref": "#/$defs/deprecated_alias" - }, - "type": "array" - }, - "markdownDescription": "xxx", - "options": { - "$ref": "#/$defs/option" - }, - "properties": { "choices": { "type": "array" }, @@ -237,7 +222,7 @@ "$schema": "http://json-schema.org/draft-07/schema", "additionalProperties": false, "examples": ["meta/argument_specs.yml"], - "markdownDescription": "Add entry point, usually `main`.\nSee [role-argument-validation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-argument-validation)", + "markdownDescription": "See [role-argument-validation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-argument-validation)", "properties": { "argument_specs": { "additionalProperties": { diff --git a/src/ansiblelint/schemas/vars.json b/src/ansiblelint/schemas/vars.json index c0b66e889d0..44acb1043f1 100644 --- a/src/ansiblelint/schemas/vars.json +++ b/src/ansiblelint/schemas/vars.json @@ -17,6 +17,7 @@ "type": "null" } ], + "description": "https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html", "examples": [ "playbooks/vars/*.yml", "vars/*.yml",